├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.rst ├── images ├── filter_data.png ├── gui.png ├── modify_graph.png ├── open_file.png ├── play_data.png └── show_quadrotor.png ├── requirements.txt ├── setup.cfg ├── setup.py └── src ├── .gitignore ├── __init__.py ├── analysis.py ├── config.txt ├── docs └── help.html ├── icons ├── analysis_graph.gif ├── analysis_graph_pressed.gif ├── help.gif ├── help_pressed.gif ├── info.gif ├── info_pressed.gif ├── loading.gif ├── open.gif ├── params.gif ├── params_pressed.gif ├── pause.jpg ├── play.jpg ├── quadrotor.gif ├── quadrotor_focus.gif ├── quadrotor_pressed.gif ├── readme.txt ├── rotor_vector.gif ├── rotor_vector_pressed.gif ├── stop.jpg ├── trace.gif └── trace_pressed.gif ├── models ├── drone_base.mtl ├── drone_base.obj ├── drone_propeller1.mtl ├── drone_propeller1.obj ├── drone_propeller2.mtl ├── drone_propeller2.obj ├── drone_propeller3.mtl ├── drone_propeller3.obj ├── drone_propeller4.mtl ├── drone_propeller4.obj ├── drone_simple.mtl └── drone_simple.obj ├── objloader.py ├── parameters.xml └── widgets.py /.gitignore: -------------------------------------------------------------------------------- 1 | test/ 2 | *.pyc 3 | .project 4 | .pydevproject 5 | .settings/ 6 | build/ 7 | dist/ 8 | *.log 9 | pyFlightAnalysis.egg-info/ 10 | src/config.txt 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 LP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.md 3 | recursive-include images * 4 | recursive-include src/icons * 5 | recursive-include src/models * 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyFlightAnalysis 2 | ================ 3 | 4 | A PX4 flight log (ulog) visual analysis tool, inspired by *FlightPlot*. 5 | 6 | .. figure:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/gui.png 7 | :alt: pyFlightAnalysis GUI 8 | 9 | pyFlightAnalysis GUI 10 | 11 | *pyFlightAnalysis* is written in Python, and depends on *pyqtgraph* (which is based on *PyQt*), *pyOpenGL*, *pyulog*, and a number of other widely used scientific packages including *numpy*, *matplotlib*, etc. 12 | 13 | For other log analysis tools see `dev.px4.io `__ 14 | 15 | Installation 16 | ------------ 17 | 18 | pyFlightAnalysis only supports *Python 3.x*. To use pyFlightAnalysis, you can either clone the repository and run the tool directly from source, or you can install *pyFlightAnalysis* (from source or using the PyPi Python package manager) and then run it. In either case you will first need to install PyQt (as shown below). 19 | 20 | Install PyQt 21 | ^^^^^^^^^^^^ 22 | 23 | PyQt5 can be installed directly from pip: 24 | 25 | .. code:: bash 26 | 27 | pip install PyQt5 28 | 29 | 30 | Run from Source 31 | ^^^^^^^^^^^^^^^ 32 | 33 | After installing PyQt, enter the following commands to install other dependencies: 34 | 35 | .. code:: bash 36 | 37 | pip install pyqtgraph pyOpenGL pyulog matplotlib numpy 38 | 39 | Once you have installed these packages you can clone the source files: 40 | 41 | .. code:: bash 42 | 43 | # In folder where you want put the source code 44 | git clone https://github.com/Marxlp/pyFlightAnalysis.git 45 | 46 | Then run the *analysis.py* source files: 47 | 48 | .. code:: bash 49 | 50 | cd pyFlightAnalysis/src 51 | python analysis.py 52 | 53 | Install and Run 54 | ^^^^^^^^^^^^^^^ 55 | 56 | You can install *pyFlightAnalysis* from either source or PyPi (after first installing PyQt as described above): 57 | 58 | .. code:: bash 59 | 60 | # Install from pypi 61 | pip install pyFlightAnalysis 62 | 63 | Or 64 | 65 | .. code:: bash 66 | 67 | # Install from source 68 | git clone https://github.com/Marxlp/pyFlightAnalysis.git 69 | python setup.py install 70 | 71 | After installing *pyFlightAnalysis* you can run it as shown: 72 | 73 | .. code:: bash 74 | 75 | analysis 76 | 77 | 78 | Features 79 | -------- 80 | 81 | - Dynamic filter for displaying data 82 | - 3D visulization for attitude and position of drone 83 | - Easily replay with pyqtgraph's ROI (Region Of Interest) 84 | 85 | Usage 86 | ----- 87 | 88 | Video Tutorial: 89 | ^^^^^^^^^^^^^^^ 90 | 91 | `Brief usage tutorial of pyFlightAnalysis `__ 92 | 93 | Literacy Tutorial: 94 | ^^^^^^^^^^^^^^^^^^ 95 | 96 | 1. Open log file (currently only support .ulg format) by clicked |open file|. 97 | 2. Choose data by using filter |filter data| and double click to add it. 98 | 3. Change color or toggle visibility |change color or toggle visibility|. 99 | 4. Scroll the middle wheel of mouse to zoom, press down and drag to move the curve. 100 | 5. Click |show quadrotor| to show 3D viewer ( currently may not be robust). 101 | 6. Press |play data| to play ( you'd better open the 3D viewer to show the animation). 102 | 103 | Issues 104 | ------ 105 | 106 | If you have installed PyQt4 and pyqtgraph but get the error below: 107 | 108 | .. code:: bash 109 | 110 | ImportError: cannot import name QtOpenGL 111 | 112 | try 113 | 114 | .. code:: bash 115 | 116 | >>> sudo apt-get install python-qt4-gl 117 | 118 | License 119 | ------- 120 | 121 | `MIT `__ 122 | 123 | .. |open file| image:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/open_file.png 124 | .. |filter data| image:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/filter_data.png 125 | .. |change color or toggle visibility| image:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/modify_graph.png 126 | .. |show quadrotor| image:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/show_quadrotor.png 127 | .. |play data| image:: https://github.com/Marxlp/pyFlightAnalysis/blob/master/images/play_data.png 128 | 129 | -------------------------------------------------------------------------------- /images/filter_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/filter_data.png -------------------------------------------------------------------------------- /images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/gui.png -------------------------------------------------------------------------------- /images/modify_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/modify_graph.png -------------------------------------------------------------------------------- /images/open_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/open_file.png -------------------------------------------------------------------------------- /images/play_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/play_data.png -------------------------------------------------------------------------------- /images/show_quadrotor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/images/show_quadrotor.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy >= 1.0.0 2 | PyOpenGL >= 3.1.0 3 | pyqtgraph >= 0.10.0 4 | matplotlib >= 2.1.0 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup,find_packages 2 | 3 | setup( 4 | name='pyFlightAnalysis', 5 | version='1.1.0b', 6 | description='Flight log data analysis visualization tool', 7 | long_description=open('README.rst').read(), 8 | url='https://github.com/Marxlp/pyFlightAnalysis', 9 | author='Marx Liu', 10 | author_email='smartlazyman@gmail.com', 11 | license='MIT', 12 | classifiers=[ 13 | 'Development Status :: 4 - Beta', 14 | 'Intended Audience :: Science/Research', 15 | 'License :: OSI Approved :: MIT License', 16 | 'Programming Language :: Python :: 2.7', 17 | ], 18 | keywords='px4 log analysis', 19 | packages=['pyflightanalysis'], 20 | package_dir={'pyflightanalysis':'src'}, 21 | package_data={'pyflightanalysis':['models/*','icons/*']}, 22 | install_requires=[ 23 | "pyqtgraph >= 0.10.0", 24 | "numpy >= 1.0.0", 25 | "PyOpenGL >= 3.1.0", 26 | "pyulog >= 0.5.0", 27 | ], 28 | entry_points={ 29 | 'console_scripts':[ 30 | 'analysis=pyflightanalysis.analysis:main', 31 | ], 32 | }, 33 | project_urls={ 34 | 'Source':'https://github.com/Marxlp/pyFlightAnalysis', 35 | }, 36 | ) 37 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | /widgets.pyc 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/__init__.py -------------------------------------------------------------------------------- /src/analysis.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | """ 3 | creator: Marx Liu 4 | """ 5 | from __future__ import division 6 | import sys 7 | import os 8 | import time 9 | import pkg_resources 10 | from collections import OrderedDict 11 | import random 12 | import numpy as np 13 | from pyulog.core import ULog 14 | from pyqtgraph.Qt import QtCore, QtGui 15 | import pyqtgraph as pg 16 | # from hypothesis.strategies import none 17 | pg.setConfigOption('background', 'w') 18 | pg.setConfigOption('foreground', 'k') 19 | 20 | import pdb 21 | sys.path.append(os.path.dirname(os.path.abspath(__file__))) 22 | 23 | from widgets import (QuadrotorWin, InfoWin, ParamsWin, 24 | TabWidget, CurveModifyWin, Checkbox, 25 | ThreadQDialog, PropertyLabel, AnalysisGraphWin, 26 | HelpWin) 27 | 28 | __version__ = '1.1.0b' 29 | pyqtSignal = QtCore.pyqtSignal 30 | 31 | try: 32 | _fromUtf8 = QtCore.QString.fromUtf8 33 | except AttributeError: 34 | def _fromUtf8(s): 35 | return s 36 | 37 | try: 38 | _encoding = QtGui.QApplication.UnicodeUTF8 39 | def _translate(context, text, disambig): 40 | return QtGui.QApplication.translate(context, text, disambig, _encoding) 41 | except AttributeError: 42 | def _translate(context, text, disambig): 43 | return QtGui.QApplication.translate(context, text, disambig) 44 | 45 | resource_package = __name__ 46 | 47 | def get_source_name(file_path_name): 48 | return pkg_resources.resource_filename(resource_package, file_path_name) 49 | 50 | basepath = os.path.dirname(__file__) 51 | 52 | def show_curve_property_diag(id, parent): 53 | def func(event): 54 | print('curve was double clicked') 55 | print(event) 56 | win = CurveModifyWin(id, parent) 57 | win.show() 58 | QtGui.QApplication.processEvents() 59 | print('end') 60 | 61 | return func 62 | 63 | def show_curve_property_diag_(id, info_data): 64 | def func(event): 65 | print('curve was double clicked') 66 | print(event) 67 | win = InfoWin(info_data) 68 | win.show() 69 | print('end') 70 | return func 71 | 72 | def load_file_first_info(): 73 | msg = QtGui.QMessageBox() 74 | msg.setText('Please open an ULog file first.') 75 | msg.setWindowTitle('Info') 76 | msg.setStandardButtons(QtGui.QMessageBox.Ok) 77 | msg.buttonClicked.connect(msg.close) 78 | msg.exec_() 79 | 80 | 81 | class TableView(QtGui.QTableWidget): 82 | """ 83 | A simple table to demonstrate the QComboBox delegate. 84 | """ 85 | def __init__(self, *args, **kwargs): 86 | QtGui.QTableView.__init__(self, *args, **kwargs) 87 | 88 | 89 | class MainWindow(QtGui.QMainWindow): 90 | 91 | deletePressed = pyqtSignal(bool) 92 | quadrotorStateChanged = pyqtSignal(object) 93 | motorSpeedChanged = pyqtSignal(object) 94 | quadrotorStateReseted = pyqtSignal(bool) 95 | SCALE_FACTOR = 80 96 | 97 | def __init__(self): 98 | """ 99 | Frame of GUI 100 | =========================== 101 | | ToolBar____ ____ | 102 | |_______|tab1|tab2|_______| 103 | | | | | 104 | |plot| | graph1 | 105 | |list| |------------------| 106 | |----|<| | 107 | |data| | graph2 | 108 | |list| | | 109 | =========================== 110 | """ 111 | super(MainWindow, self).__init__() 112 | 113 | self.log_data_list = None 114 | self.log_file_name = None 115 | self.data_dict = None 116 | self.log_info_data = None 117 | self.log_params_data = None 118 | self.log_changed_params = [] 119 | 120 | self.main_widget = QtGui.QWidget(self) 121 | self.mainlayout = QtGui.QHBoxLayout() 122 | self.main_widget.setLayout(self.mainlayout) 123 | 124 | # ToolBar 125 | self.toolbar = self.addToolBar('FileManager') 126 | self.basic_tool_group = QtGui.QActionGroup(self) 127 | ## load log file 128 | self.loadfile_action = QtGui.QAction(QtGui.QIcon(get_source_name('icons/open.gif')), 'Open log file', self) 129 | self.loadfile_action.setShortcut('Ctrl+O') 130 | self.loadfile_action.triggered.connect(self.callback_open_log_file) 131 | self.toolbar.addAction(self.loadfile_action) 132 | ## plot quadrotor in 3d graph 133 | self.show_quadrotor_3d = QtGui.QAction(QtGui.QIcon(get_source_name('icons/quadrotor.gif')), 'show 3d viewer', self) 134 | self.show_quadrotor_3d.setShortcut('Ctrl+Shift+Q') 135 | self.show_quadrotor_3d.triggered.connect(self.callback_show_quadrotor) 136 | self.toolbar.addAction(self.show_quadrotor_3d) 137 | ## show quadrotor info 138 | self.show_info = QtGui.QAction(QtGui.QIcon(get_source_name('icons/info.gif')), 'show log info', self) 139 | self.show_info.setShortcut('Ctrl+I') 140 | self.show_info.triggered.connect(self.callback_show_info_pane) 141 | self.toolbar.addAction(self.show_info) 142 | self.info_pane_showed = False 143 | ## show quadrotor param 144 | self.show_params = QtGui.QAction(QtGui.QIcon(get_source_name('icons/params.gif')), 'show params', self) 145 | self.show_params.setShortcut('Ctrl+P') 146 | self.show_params.triggered.connect(self.callback_show_parameters_pane) 147 | self.toolbar.addAction(self.show_params) 148 | self.params_pane_showed = False 149 | ## show some default analysis graphs 150 | self.show_analysis_graphs = QtGui.QAction(QtGui.QIcon(get_source_name('icons/analysis_graph.gif')), 151 | 'show default analysis graph control pane', self) 152 | self.show_analysis_graphs.setShortcut('Ctrl+G') 153 | self.show_analysis_graphs.triggered.connect(self.callback_show_analysis_graph_pane) 154 | self.toolbar.addAction(self.show_analysis_graphs) 155 | self.analysis_graphs_showed = False 156 | self.analysis_graphs_pane = None 157 | ## show help 158 | self.show_help = QtGui.QAction(QtGui.QIcon(get_source_name('icons/help.gif')), 159 | 'show help', self) 160 | self.show_help.setShortcut('Ctrl+H') 161 | self.show_help.triggered.connect(self.callback_show_help_pane) 162 | self.toolbar.addAction(self.show_help) 163 | self.help_pane_showed = False 164 | self.help_pane = None 165 | 166 | self.basic_tool_group.addAction(self.loadfile_action) 167 | self.basic_tool_group.addAction(self.show_quadrotor_3d) 168 | self.basic_tool_group.addAction(self.show_info) 169 | self.basic_tool_group.addAction(self.show_params) 170 | self.basic_tool_group.addAction(self.show_analysis_graphs) 171 | self.basic_tool_group.addAction(self.show_help) 172 | 173 | ## show some analysis graph 174 | # Left plot item widget 175 | self.plot_data_frame = QtGui.QFrame(self) 176 | self.plot_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) 177 | # self.plot_data_layout_H = QtGui.QHBoxLayout(self.plot_data_frame) 178 | self.plot_data_layout_V = QtGui.QVBoxLayout(self.plot_data_frame) 179 | ## Data Plotting [id, filesystem, ] 180 | self.data_plotting = OrderedDict() 181 | ### There exists a Default graph 182 | self.line_ID = 0 183 | lbl_ploting_data = QtGui.QLabel('Data Plotting') 184 | self.plotting_data_tableView = TableView(self.plot_data_frame) 185 | self.plotting_data_tableView.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked | 186 | QtGui.QAbstractItemView.SelectedClicked) 187 | self.plotting_data_tableView.setSortingEnabled(False) 188 | self.plotting_data_tableView.horizontalHeader().setStretchLastSection(True) 189 | self.plotting_data_tableView.resizeColumnsToContents() 190 | self.plotting_data_tableView.setColumnCount(3) 191 | self.plotting_data_tableView.setColumnWidth(0, 200) 192 | self.plotting_data_tableView.setColumnWidth(1, 60) 193 | self.plotting_data_tableView.setColumnWidth(2, 50) 194 | self.plotting_data_tableView.setHorizontalHeaderLabels(['Label', 'Visible', 'Curve Style']) 195 | self.id = 0 196 | lbl_ploting_data.setBuddy(self.plotting_data_tableView) 197 | self.plot_data_layout_V.addWidget(lbl_ploting_data) 198 | self.plot_data_layout_V.addWidget(self.plotting_data_tableView) 199 | 200 | edit_layout = QtGui.QHBoxLayout() 201 | self.delete_btn = QtGui.QPushButton('Delete') 202 | self.delete_btn.clicked.connect(self.callback_del_plotting_data) 203 | self.clear_btn = QtGui.QPushButton('Clear') 204 | self.clear_btn.clicked.connect(self.callback_clear_plotting_data) 205 | edit_layout.addWidget(self.clear_btn) 206 | edit_layout.addWidget(self.delete_btn) 207 | self.plot_data_layout_V.addLayout(edit_layout) 208 | 209 | ## Data in the log file 210 | self.list_data_frame = QtGui.QFrame(self) 211 | self.list_data_frame.setMinimumWidth(400) 212 | self.list_data_frame.setMaximumWidth(600) 213 | self.list_data_frame.resize(400, 500) 214 | self.list_data_frame.setFrameShape(QtGui.QFrame.StyledPanel) 215 | self.list_data_layout = QtGui.QVBoxLayout(self.list_data_frame) 216 | ### line to search item 217 | self.choose_item_lineEdit = QtGui.QLineEdit(self.list_data_frame) 218 | self.choose_item_lineEdit.setPlaceholderText('filter by data name') 219 | self.choose_item_lineEdit.textChanged.connect(self.callback_filter) 220 | ### tree to show data to plot 221 | self.item_list_treeWidget = QtGui.QTreeWidget(self.list_data_frame) 222 | self.item_list_treeWidget.clear() 223 | self.item_list_treeWidget.setColumnCount(3) 224 | self.item_list_treeWidget.setColumnWidth(0, 160) 225 | self.item_list_treeWidget.setHeaderLabels(['Flight Data', 'Type', 'Length']) 226 | self.item_list_treeWidget.itemDoubleClicked.connect(self.callback_tree_double_clicked) 227 | self.item_list_treeWidget.resizeColumnToContents(2) 228 | self.list_data_layout.addWidget(self.choose_item_lineEdit) 229 | self.list_data_layout.addWidget(self.item_list_treeWidget) 230 | 231 | # Right plot item 232 | self.graph_frame = QtGui.QFrame(self) 233 | self.default_tab = TabWidget(self.graph_frame) 234 | self.graph_frame.setFrameShape(QtGui.QFrame.StyledPanel) 235 | self.animation_layout = QtGui.QVBoxLayout(self.graph_frame) 236 | 237 | ## quadrotor 3d 238 | self.quadrotor_win = QuadrotorWin(self) 239 | self.quadrotor_win.closed.connect(self.quadrotor_win_closed_event) 240 | self.quadrotor_win.hide() 241 | self.first_load = True 242 | self.quadrotor_widget_isshowed = False 243 | 244 | ## default plot 245 | self.default_graph_widget_t = pg.GraphicsLayoutWidget() 246 | self.default_graph_widget_2d = pg.GraphicsLayoutWidget() 247 | self.default_graph_widget_3d = pg.GraphicsLayoutWidget() 248 | self.default_tab.addTab(self.default_graph_widget_t, 't') 249 | self.default_tab.addTab(self.default_graph_widget_2d, '2D') 250 | self.default_tab.addTab(self.default_graph_widget_3d, '3D') 251 | ### a hidable ROI region 252 | self.detail_graph = self.default_graph_widget_t.addPlot(row=0, col=0) 253 | self.detail_graph.setAutoVisible(True) 254 | self.detail_graph.hide() 255 | ### main graph to plot curves 256 | self.main_graph_t = self.default_graph_widget_t.addPlot(row=1, col=0) 257 | self.main_graph_t.showGrid(x=True, y=True) 258 | self.main_graph_t.keyPressEvent = self.keyPressed 259 | self.deletePressed.connect(self.callback_del_plotting_data) 260 | self.main_graph_t.addLegend() 261 | ROI_action = QtGui.QAction('show/hide ROI graph', self.main_graph_t) 262 | ROI_action.triggered.connect(self.callback_ROI_triggered) 263 | self.main_graph_t.scene().contextMenu.append(ROI_action) 264 | self.ROI_region = pg.LinearRegionItem() 265 | self.ROI_region.setZValue(10) 266 | self.ROI_region.hide() 267 | self.ROI_showed = False 268 | ### main graph 269 | self.main_graph_2d = self.default_graph_widget_2d.addPlot(row=0, col=0) 270 | self.main_graph_2d.showGrid(x=True, y=True) 271 | self.main_graph_3d = self.default_graph_widget_3d.addPlot(row=0, col=0) 272 | 273 | def update(): 274 | self.ROI_region.setZValue(10) 275 | minX, maxX = self.ROI_region.getRegion() 276 | self.detail_graph.setXRange(minX, maxX, padding=0) 277 | 278 | self.ROI_region.sigRegionChanged.connect(update) 279 | 280 | def updateRegion(window, viewRange): 281 | rgn = viewRange[0] 282 | self.ROI_region.setRegion(rgn) 283 | self.detail_graph.sigRangeChanged.connect(updateRegion) 284 | 285 | self.main_graph_t.addItem(self.ROI_region, ignoreBounds=True) 286 | 287 | ## vertical line 288 | self.vLine = pg.InfiniteLine(angle=90, movable=False) 289 | self.vLine.hide() 290 | self.main_graph_t.addItem(self.vLine, ignoreBounds=True) 291 | self.vLine_detail = pg.InfiniteLine(angle=90, movable=False) 292 | self.vLine_detail.hide() 293 | self.detail_graph.addItem(self.vLine_detail, ignoreBounds=True) 294 | 295 | ## flag whether there is a curve clicked after last clicked event 296 | self.curve_clicked = False 297 | self.curve_clicked_time = time.time() 298 | self.curve_highlighted = [] 299 | self.animation_layout.addWidget(self.default_tab) 300 | ## time line 301 | self.time_line_frame = QtGui.QFrame(self) 302 | self.time_line_frame.setMaximumHeight(45) 303 | self.time_line_frame.setMinimumHeight(45) 304 | self.time_line_layout = QtGui.QHBoxLayout(self.time_line_frame) 305 | time_line_lbl = QtGui.QLabel('x') 306 | time_line_lbl.setToolTip('set play speed') 307 | speed_combo = QtGui.QComboBox() 308 | speed_combo.addItems(['1', '2', '4', '8']) 309 | self.speed_factor = 500 310 | self.time_line_layout.addWidget(time_line_lbl) 311 | self.time_line_layout.addWidget(speed_combo) 312 | speed_combo.currentIndexChanged.connect(self.callback_speed_combo_indexChanged) 313 | self.current_factor = 500/1 314 | self.time_line_button_play = QtGui.QPushButton(self.time_line_frame) 315 | self.time_line_button_play.setEnabled(False) 316 | self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) 317 | self.time_line_play = False 318 | self.time_line_button_play.clicked.connect(self.callback_play_clicked) 319 | self.time_line_button_stop = QtGui.QPushButton(self.time_line_frame) 320 | self.time_line_button_stop.setEnabled(False) 321 | self.time_line_button_stop.setIcon(QtGui.QIcon(get_source_name("icons/stop.jpg"))) 322 | self.time_line_button_stop.clicked.connect(self.callback_stop_clicked) 323 | self.time_line_layout.addWidget(self.time_line_button_play) 324 | self.time_line_layout.addWidget(self.time_line_button_stop) 325 | self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal) 326 | self.time_slider.setRange(0, 100) 327 | #### index for time_stamp 328 | self.time_line_layout.addWidget(self.time_slider) 329 | 330 | ## timer 331 | self.timer = QtCore.QTimer() 332 | self.timer.timeout.connect(self.animation_update) 333 | self.current_time = 0 334 | self.dt = 50 335 | 336 | self.splitter1 = QtGui.QSplitter(QtCore.Qt.Vertical) 337 | self.splitter1.addWidget(self.plot_data_frame) 338 | self.splitter1.addWidget(self.list_data_frame) 339 | 340 | self.splitter2 = QtGui.QSplitter(QtCore.Qt.Vertical) 341 | self.splitter2.addWidget(self.graph_frame) 342 | self.splitter2.addWidget(self.time_line_frame) 343 | 344 | self.splitter3 = QtGui.QSplitter(QtCore.Qt.Horizontal) 345 | self.splitter3.addWidget(self.splitter1) 346 | self.splitter3.addWidget(self.splitter2) 347 | self.mainlayout.addWidget(self.splitter3) 348 | self.setCentralWidget(self.main_widget) 349 | self.setGeometry(200, 200, 1000, 800) 350 | self.setWindowTitle("pyFlightAnalysis") 351 | self.quadrotorStateChanged.connect(self.quadrotor_win.callback_update_quadrotor_pos) 352 | self.quadrotorStateReseted.connect(self.quadrotor_win.callback_quadrotor_state_reset) 353 | 354 | def keyPressed(self, event): 355 | """Key Pressed function for graph""" 356 | if event.key() == QtCore.Qt.Key_Delete: 357 | self.deletePressed.emit(True) 358 | elif event.key() == QtCore.Qt.Key_R: 359 | # ROI graph can also be triggered by press 'r' 360 | self.callback_ROI_triggered() 361 | 362 | @staticmethod 363 | def getIndex(data, item): 364 | for ind, d in enumerate(data): 365 | if d > item: 366 | return ind 367 | 368 | return len(data) - 1 369 | 370 | @staticmethod 371 | def quat_to_euler(q0, q1, q2, q3): 372 | #321 373 | angles = [] 374 | for i in range(len(q0)): 375 | roll = 180/np.pi * np.arctan2(2.0 * (q0[i] * q1[i] + q2[i] * q3[i]), 1.0 - 2.0 * (q1[i]**2 + q2[i]**2)) 376 | pitch = 180/np.pi * np.arcsin(2.0 * (q0[i] * q2[i] - q3[i] * q1[i])) 377 | yaw = 180/np.pi * np.arctan2(2.0 * (q0[i] * q3[i] + q1[i] * q2[i]), 1.0 - 2.0 * (q2[i]**2 + q3[i]**2)) 378 | angles.append([roll, pitch, yaw]) 379 | return angles 380 | 381 | def callback_open_log_file(self): 382 | config_path = os.path.join(os.getcwd(), get_source_name('config.txt')) 383 | if os.path.exists(config_path): 384 | with open(config_path, 'r') as conf: 385 | path_hist = conf.readline() 386 | path = path_hist.split(':')[-1] 387 | else: 388 | path = '' 389 | if not path: 390 | from os.path import expanduser 391 | path = expanduser('~') 392 | 393 | filename = QtGui.QFileDialog.getOpenFileName(self, 'Open Log File', path, 'Log Files (*.ulg)') 394 | # On Window, filename will be a tuple (fullpath, filter) 395 | if isinstance(filename, tuple): 396 | filename = filename[0] 397 | if filename: 398 | try: 399 | self.log_file_name = filename 400 | self.id = 0 401 | self.load_data() 402 | self.load_data_tree() 403 | self.analysis_graph_list = OrderedDict() 404 | self.update_graph_after_log_changed() 405 | self.time_line_button_play.setEnabled(True) 406 | # write the file path to config.txt 407 | with open(get_source_name('config.txt'), 'w') as conf: 408 | conf.write('last_path:' + os.path.split(filename)[0]) 409 | except Exception as ex: 410 | print(ex) 411 | 412 | def callback_play_clicked(self): 413 | """Time line play""" 414 | self.time_line_play = not self.time_line_play 415 | if self.log_file_name is not None: 416 | if self.time_line_play: 417 | self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/pause.jpg"))) 418 | self.time_line_button_stop.setEnabled(True) 419 | if self.ROI_showed: 420 | region = self.ROI_region.getRegion() 421 | self.vLine.setPos(region[0]) 422 | self.vLine_detail.setPos(region[0]) 423 | else: 424 | self.vLine.setPos(self.time_range[0]) 425 | self.vLine_detail.setPos(self.time_range[0]) 426 | self.vLine.show() 427 | self.vLine_detail.show() 428 | # start timer 429 | self.timer.start(self.dt) 430 | else: 431 | self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) 432 | self.time_line_button_stop.setEnabled(False) 433 | self.timer.stop() 434 | 435 | def callback_stop_clicked(self): 436 | self.time_line_play = False 437 | self.timer.stop() 438 | self.time_line_button_play.setIcon(QtGui.QIcon(get_source_name("icons/play.jpg"))) 439 | self.time_line_button_stop.setEnabled(False) 440 | self.time_slider.setValue(0) 441 | self.time_index = 0 442 | self.vLine.hide() 443 | self.vLine_detail.hide() 444 | self.quadrotorStateReseted.emit(True) 445 | 446 | def animation_update(self): 447 | """update the quadrotor state""" 448 | dV = 100.0/(self.time_range[1] - self.time_range[0]) 449 | 450 | if self.ROI_showed: 451 | start, end = self.ROI_region.getRegion() 452 | t = self.current_time + start 453 | # emit data 454 | indexes = list(map(self.getIndex, [self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output], [t, t, t])) 455 | state_data = [self.position_history[indexes[0]], 456 | self.attitude_history[indexes[1]], self.output_history[indexes[2]]] 457 | self.quadrotorStateChanged.emit(state_data) 458 | # update slider 459 | self.time_slider.setValue(int(dV * (self.current_time + start - self.time_range[0]))) 460 | # update vLine pos 461 | self.vLine.setPos(t) 462 | self.vLine_detail.setPos(t) 463 | if self.current_time > (end - start): 464 | self.current_time = 0 465 | self.quadrotorStateReseted.emit(True) 466 | else: 467 | t = self.current_time + self.time_range[0] 468 | self.time_slider.setValue(int(dV * self.current_time)) 469 | # update quadrotor position and attitude and motor speed 470 | indexes = list(map(self.getIndex, [self.time_stamp_position, self.time_stamp_attitude, self.time_stamp_output], [t, t, t])) 471 | state_data = [self.position_history[indexes[0]], 472 | self.attitude_history[indexes[1]], self.output_history[indexes[2]]] 473 | # print('state:',state_data) 474 | self.quadrotorStateChanged.emit(state_data) 475 | # update vLine pos 476 | self.vLine.setPos(t) 477 | self.vLine_detail.setPos(t) 478 | # if arrive end just replay 479 | if self.current_time > (self.time_range[1] - self.time_range[0]): 480 | self.current_time = 0 481 | self.quadrotorStateReseted.emit(True) 482 | 483 | self.current_time += self.dt/self.current_factor 484 | 485 | 486 | def callback_show_quadrotor(self): 487 | if self.quadrotor_widget_isshowed: 488 | self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor.gif'))) 489 | self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed 490 | self.quadrotor_win.hide() 491 | self.update() 492 | else: 493 | self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed 494 | self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor_pressed.gif'))) 495 | splash = ThreadQDialog(self.quadrotor_win.quadrotor_widget, self.quadrotor_win) 496 | splash.run() 497 | self.quadrotor_win.show() 498 | self.update() 499 | 500 | def callback_show_info_pane(self): 501 | if self.log_info_data is not None: 502 | if self.info_pane_showed: 503 | self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info.gif'))) 504 | self.info_pane.close() 505 | del self.info_pane 506 | else: 507 | self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info_pressed.gif'))) 508 | self.info_pane = InfoWin(self.log_info_data) 509 | self.info_pane.closed.connect(self.callback_show_info_pane_closed) 510 | self.info_pane.show() 511 | self.info_pane_showed = not self.info_pane_showed 512 | else: 513 | load_file_first_info() 514 | 515 | def callback_show_info_pane_closed(self, closed): 516 | if closed: 517 | self.show_info.setIcon(QtGui.QIcon(get_source_name('icons/info.gif'))) 518 | 519 | def callback_show_parameters_pane(self): 520 | if self.log_params_data is not None: 521 | if self.params_pane_showed: 522 | self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params.gif'))) 523 | self.params_pane.close() 524 | del self.params_pane 525 | else: 526 | self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params_pressed.gif'))) 527 | self.params_pane = ParamsWin(self.log_params_data, self.log_changed_params) 528 | self.params_pane.closed.connect(self.callback_show_params_pane_closed) 529 | self.params_pane.show() 530 | self.params_pane_showed = not self.params_pane_showed 531 | else: 532 | load_file_first_info() 533 | 534 | def callback_show_params_pane_closed(self, closed): 535 | if closed: 536 | self.params_pane_showed = False 537 | self.show_params.setIcon(QtGui.QIcon(get_source_name('icons/params.gif'))) 538 | 539 | def callback_show_analysis_graph_pane(self): 540 | if self.log_data_list is not None: 541 | if self.analysis_graphs_showed: 542 | self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph.gif'))) 543 | self.analysis_graphs_pane.hide() 544 | else: 545 | self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph_pressed.gif'))) 546 | if self.analysis_graphs_pane is None: 547 | self.analysis_graphs_pane = AnalysisGraphWin(self) 548 | self.analysis_graphs_pane.closed.connect(self.callback_analysis_graphs_pane_closed) 549 | self.analysis_graphs_pane.sigChecked.connect(self.callback_analysis_graph_data_checked) 550 | self.analysis_graphs_pane.sigUnchecked.connect(self.callback_analysis_graph_data_unchecked) 551 | self.analysis_graphs_pane.show() 552 | self.analysis_graphs_showed = not self.analysis_graphs_showed 553 | else: 554 | load_file_first_info() 555 | 556 | def callback_analysis_graphs_pane_closed(self, closed): 557 | if closed: 558 | self.analysis_graphs_showed = False 559 | self.show_analysis_graphs.setIcon(QtGui.QIcon(get_source_name('icons/analysis_graph.gif'))) 560 | 561 | def callback_analysis_graph_data_checked(self, curve_name_with_data): 562 | color_list = [(255, 0, 0), 563 | (0, 255, 0), 564 | (0, 0, 255), 565 | (0, 255, 255), 566 | (255, 0, 255), 567 | (155, 0, 160), 568 | (0, 155, 155)] 569 | curve_name, data = curve_name_with_data 570 | data_type = data[0] 571 | if curve_name not in self.analysis_graph_list: 572 | new_graph = pg.GraphicsLayoutWidget() 573 | ax = new_graph.addPlot(row=0, col=0) 574 | ax.addLegend() 575 | self.analysis_graph_list[curve_name] = new_graph 576 | self.default_tab.addTab(new_graph, curve_name) 577 | self.default_tab.setCurrentWidget(new_graph) 578 | for ind, curve_data in enumerate(data[1:]): 579 | ax.plot(curve_data[0], curve_data[1], pen=color_list[ind%len(color_list)], name=curve_data[2]) 580 | 581 | def callback_analysis_graph_data_unchecked(self, graph_name): 582 | tab_index = 0 583 | for i in range(self.default_tab.count()): 584 | if self.default_tab.widget(i) == self.analysis_graph_list[graph_name]: 585 | tab_index = i 586 | break 587 | self.default_tab.removeTab(tab_index) 588 | self.analysis_graph_list.pop(graph_name) 589 | 590 | def callback_show_help_pane(self): 591 | if self.help_pane_showed: 592 | self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help.gif'))) 593 | self.help_pane.hide() 594 | else: 595 | if self.help_pane is None: 596 | self.help_pane = HelpWin() 597 | self.help_pane.closed.connect(self.callback_help_pane_closed) 598 | self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help_pressed.gif'))) 599 | self.help_pane.show() 600 | self.help_pane_showed = not self.help_pane_showed 601 | 602 | def callback_help_pane_closed(self, closed): 603 | if closed: 604 | self.help_paned_showed = False 605 | self.show_help.setIcon(QtGui.QIcon(get_source_name('icons/help.gif'))) 606 | 607 | def callback_speed_combo_indexChanged(self, index): 608 | self.current_factor = self.speed_factor / 2**index 609 | 610 | def callback_filter(self, filtertext): 611 | """Accept filter and update the tree widget""" 612 | filtertext = str(filtertext) 613 | if self.data_dict is not None: 614 | if filtertext == '': 615 | self.load_data_tree() 616 | else: 617 | self.item_list_treeWidget.clear() 618 | for key, values_name in self.data_dict.items(): 619 | values_satisfied = [] 620 | if filtertext in key: 621 | for value in values_name: 622 | values_satisfied.append(value) 623 | else: 624 | for value in values_name: 625 | if filtertext in value[0]: 626 | values_satisfied.append(value) 627 | if values_satisfied: 628 | param_name = QtGui.QTreeWidgetItem(self.item_list_treeWidget, [key]) 629 | self.item_list_treeWidget.expandItem(param_name) 630 | for data_name in values_satisfied: 631 | self.item_list_treeWidget.expandItem( 632 | QtGui.QTreeWidgetItem(param_name, [data_name[0], data_name[1], data_name[2]])) 633 | 634 | 635 | def callback_graph_clicked(self, event): 636 | """ set the curve highlighted to be normal """ 637 | print('graph clicked') 638 | if self.curve_clicked: 639 | if event.modifiers() == QtCore.Qt.ControlModifier: 640 | pass 641 | else: 642 | for curve in self.curve_highlighted[:-1]: 643 | curve.setShadowPen(pg.mkPen((200, 200, 200), width=1, cosmetic=True)) 644 | self.curve_highlighted = self.curve_highlighted[-1:] 645 | 646 | if len(self.curve_highlighted) > 0 and not self.curve_clicked: 647 | for curve in self.curve_highlighted: 648 | curve.setShadowPen(pg.mkPen((120, 120, 120), width=1, cosmetic=True)) 649 | self.curve_highlighted = [] 650 | self.plotting_data_tableView.setCurrentCell(0, 0) 651 | 652 | self.curve_clicked = False 653 | 654 | def callback_tree_double_clicked(self, item, col): 655 | """Add clicked item to Data plotting area""" 656 | def expand_name(item): 657 | if item.parent() is None: 658 | return str(item.text(0)) 659 | else: 660 | return expand_name(item.parent()) + '->' + str(item.text(0)) 661 | # When click high top label, no action will happened 662 | if item.parent() is None: 663 | return 664 | item_label = expand_name(item) 665 | row = len(self.data_plotting) 666 | self.plotting_data_tableView.insertRow(row) 667 | 668 | # Label 669 | self.plotting_data_tableView.setCellWidget(row, 0, QtGui.QLabel(item_label)) 670 | 671 | # Curve Visible 672 | chk = Checkbox(self.id, '') 673 | chk.setChecked(True) 674 | chk.sigStateChanged.connect(self.callback_visible_changed) 675 | self.plotting_data_tableView.setCellWidget(row, 1, chk) 676 | 677 | # Curve Color 678 | ## rgb, prefer deep color 679 | color = [random.randint(0, 150) for _ in range(3)] 680 | color_text = '#{0[0]:02x}{0[1]:02x}{0[2]:02x}'.format(color) 681 | ## Curve Marker 682 | marker = None 683 | lbl = PropertyLabel(self.id, self, 684 | "{1} {2}".format(color_text,'▇▇',str(marker))) 685 | lbl.sigPropertyChanged.connect(self.callback_property_changed) 686 | self.plotting_data_tableView.setCellWidget(row, 2, lbl) 687 | 688 | data_index = list(list(self.data_dict.keys())).index(item_label.split('->')[0]) 689 | data_name = item_label.split('->')[-1] 690 | 691 | ## ms to s 692 | t = self.log_data_list[data_index].data['timestamp']/10**6 693 | data = self.log_data_list[data_index].data[data_name] 694 | if len(self.data_plotting) == 0: 695 | label_style = {'color': '#EEEEEE', 'font-size':'14pt'} 696 | self.main_graph_t.setLabel('bottom', 't(s)', **label_style) 697 | curve = self.main_graph_t.plot(t, data, symbol=marker, pen=color, clickable=True, name=item_label) 698 | curve.curve.setClickable(True) 699 | # functional method 700 | curve.mouseDoubleClickEvent = show_curve_property_diag(self.id, self) 701 | # whether show the curve 702 | showed = True 703 | self.data_plotting[self.id] = [item_label, showed, curve] 704 | # increase the id 705 | self.id += 1 706 | self.update_ROI_graph() 707 | self.default_tab.setCurrentWidget(self.default_graph_widget_t) 708 | 709 | def callback_curve_clicked(self, curve): 710 | """""" 711 | print('curve clicked') 712 | self.curve_clicked = True 713 | dt = time.time() - self.curve_clicked_time 714 | self.curve_clicked_time = time.time() 715 | if dt < 0.3: 716 | win = CurveModifyWin() 717 | curves = [data[2] for data in self.data_plotting.values()] 718 | ind = curves.index(curve) 719 | curve.setShadowPen(pg.mkPen((70, 70, 70), width=5, cosmetic=True)) 720 | self.curve_highlighted.append(curve) 721 | self.plotting_data_tableView.setCurrentCell(ind, 0) 722 | 723 | def callback_del_plotting_data(self): 724 | """""" 725 | indexes = self.plotting_data_tableView.selectedIndexes() 726 | rows_del = set([ind.row() for ind in indexes]) 727 | rows_all = set(range(len(self.data_plotting))) 728 | rows_reserved = list(rows_all - rows_del) 729 | data_plotting = OrderedDict() 730 | keys, values = list(self.data_plotting.keys()), list(self.data_plotting.values()) 731 | for row in rows_reserved: 732 | data_plotting[keys[row]] = values[row] 733 | self.data_plotting = data_plotting 734 | self.update_graph() 735 | 736 | def callback_visible_changed(self, chk): 737 | """""" 738 | state = True if chk.checkState() == QtCore.Qt.Checked else False 739 | self.data_plotting[chk.id][1] = state 740 | self.update_graph() 741 | 742 | def callback_color_changed(self, btn): 743 | color = [c*255 for c in btn.color('float')[:-1]] 744 | self.data_plotting[btn.id][2].opts['pen'] = color 745 | self.update_graph() 746 | 747 | def callback_marker_changed(self, mkr): 748 | self.data_plotting[mkr.id][2].opts['symbol'] = mkr.marker 749 | self.update_graph() 750 | 751 | def keyPressEvent(self, event, *args, **kwargs): 752 | print(event) 753 | if event.key() == QtCore.Qt.Key_S: 754 | print('S pressed') 755 | if self.splitter1.isHidden(): 756 | self.splitter1.show() 757 | else: 758 | self.splitter1.hide() 759 | elif event.key() == QtCore.Qt.Key_D: 760 | print('D pressed') 761 | for curve in self.curve_highlighted: 762 | del(curve) 763 | return QtGui.QMainWindow.keyPressEvent(self, event, *args, **kwargs) 764 | 765 | def update_graph(self): 766 | # update tableView 767 | # clear 768 | self.plotting_data_tableView.setRowCount(0) 769 | # add 770 | for ind, (item_id, item) in enumerate(self.data_plotting.items()): 771 | self.plotting_data_tableView.insertRow(ind) 772 | self.plotting_data_tableView.setCellWidget(ind, 0, QtGui.QLabel(item[0])) 773 | chkbox = Checkbox(item_id, '') 774 | chkbox.setChecked(item[1]) 775 | chkbox.sigStateChanged.connect(self.callback_visible_changed) 776 | self.plotting_data_tableView.setCellWidget(ind, 1, chkbox) 777 | curve = item[2] 778 | color = curve.opts['pen'] 779 | if isinstance(color, QtGui.QColor): 780 | color = color.red(), color.green(), color.blue() 781 | marker = curve.opts['symbol'] 782 | marker_dict = OrderedDict([(None,'None'), ('s','☐'), ('t','▽'), ('o','○'), ('+','+')]) 783 | color_text = '#{0[0]:02x}{0[1]:02x}{0[2]:02x}'.format(color) 784 | lbl_txt = "{1} {2}".format(color_text,'▇▇',str(marker_dict[marker])) 785 | lbl = PropertyLabel(item_id, self, lbl_txt) 786 | lbl.sigPropertyChanged.connect(self.callback_property_changed) 787 | self.plotting_data_tableView.setCellWidget(ind, 2, lbl) 788 | 789 | # update curve 790 | # remove curves in graph 791 | items_to_be_removed = [] 792 | for item in self.main_graph_t.items: 793 | if isinstance(item, pg.PlotDataItem): 794 | items_to_be_removed.append(item) 795 | for item in items_to_be_removed: 796 | self.main_graph_t.removeItem(item) 797 | 798 | self.main_graph_t.legend.scene().removeItem(self.main_graph_t.legend) 799 | self.main_graph_t.addLegend() 800 | # redraw curves 801 | for ind, (item_id, item) in enumerate(self.data_plotting.items()): 802 | label, showed, curve = item 803 | color = curve.opts['pen'] 804 | if isinstance(color, QtGui.QColor): 805 | color = color.red(), color.green(), color.blue() 806 | data = curve.xData, curve.yData 807 | marker = curve.opts['symbol'] 808 | symbolSize = curve.opts['symbolSize'] 809 | if showed: 810 | curve = self.main_graph_t.plot(data[0], data[1], symbol=marker, pen=color, name=label, symbolSize=symbolSize) 811 | self.data_plotting[item_id][2] = curve 812 | self.update_ROI_graph() 813 | 814 | def callback_property_changed(self): 815 | self.update_graph() 816 | 817 | def callback_clear_plotting_data(self): 818 | """""" 819 | self.data_plotting = OrderedDict() 820 | self.curve_highlighted = [] 821 | self.update_graph() 822 | 823 | def callback_graph_index_combobox_changed(self, index): 824 | """Add clicked config graph to Data plotting area""" 825 | print(index) 826 | # if index == self.graph_number: 827 | # # choose new 828 | # self.graph_number += 1 829 | # # add a graph 830 | # graph_widget = pg.GraphicsLayoutWidget() 831 | # graph_widget.addPlot(row=0, col=0) 832 | # self.graph_lines_dict.setdefault(graph_widget, 0) 833 | # for data in self.data_plotting: 834 | # data[1].clear() 835 | # for i in range(1, self.graph_number + 1): 836 | # data[1].addItem(str(i)) 837 | # data[1].addItem('New') 838 | # else: 839 | # # change current curve's graph 840 | # pass 841 | 842 | def callback_visible_checkBox(self, checked): 843 | """Set the curve visible or invisible""" 844 | if checked: 845 | pass 846 | else: 847 | pass 848 | 849 | def callback_ROI_triggered(self): 850 | """Show the graph""" 851 | if self.ROI_showed: 852 | self.detail_graph.hide() 853 | self.ROI_region.hide() 854 | self.ROI_showed = not self.ROI_showed 855 | else: 856 | self.update_ROI_graph() 857 | self.detail_graph.show() 858 | self.ROI_region.show() 859 | self.ROI_showed = not self.ROI_showed 860 | 861 | def update_ROI_graph(self): 862 | items_to_be_removed = [] 863 | for item in self.detail_graph.items: 864 | if isinstance(item, pg.PlotDataItem): 865 | items_to_be_removed.append(item) 866 | 867 | for item in items_to_be_removed: 868 | self.detail_graph.removeItem(item) 869 | 870 | items = self.main_graph_t.items 871 | for item in items: 872 | if isinstance(item, pg.PlotDataItem): 873 | self.detail_graph.plot(item.xData, item.yData, symbol=item.opts['symbol'], pen=item.opts['pen']) 874 | 875 | def load_data(self): 876 | log_data = ULog(str(self.log_file_name)) 877 | self.log_info_data = {index:value for index,value in log_data.msg_info_dict.items() if 'perf_' not in index} 878 | self.log_info_data['SW version'] = log_data.get_version_info_str() 879 | self.log_params_data = log_data.initial_parameters 880 | self.log_params_data = OrderedDict([(key, self.log_params_data[key]) for key in sorted(self.log_params_data)]) 881 | self.log_data_list = log_data.data_list 882 | self.data_dict = OrderedDict() 883 | for d in self.log_data_list: 884 | data_items_list = [f.field_name for f in d.field_data] 885 | data_items_list.remove('timestamp') 886 | data_items_list.insert(0, 'timestamp') 887 | data_items = [(item, str(d.data[item].dtype), str(len(d.data[item]))) for item in data_items_list] 888 | # add suffix to distinguish same name 889 | i = 0 890 | name = d.name 891 | while True: 892 | if i > 0: 893 | name = d.name + '_' + str(i) 894 | if name in self.data_dict: 895 | i += 1 896 | else: 897 | break 898 | self.data_dict.setdefault(name, data_items[1:]) 899 | # pdb.set_trace() 900 | # attitude 901 | index = list(self.data_dict.keys()).index('vehicle_attitude') 902 | self.time_stamp_attitude = self.log_data_list[index].data['timestamp']/10**6 903 | q0 = self.log_data_list[index].data['q[0]'] 904 | q1 = self.log_data_list[index].data['q[1]'] 905 | q2 = self.log_data_list[index].data['q[2]'] 906 | q3 = self.log_data_list[index].data['q[3]'] 907 | self.attitude_history = self.quat_to_euler(q0, q1, q2, q3) 908 | index = list(self.data_dict.keys()).index('vehicle_attitude_setpoint') 909 | self.time_stamp_attitude_setpoint = self.log_data_list[index].data['timestamp']/10**6 910 | q0_d = self.log_data_list[index].data['q_d[0]'] 911 | q1_d = self.log_data_list[index].data['q_d[1]'] 912 | q2_d = self.log_data_list[index].data['q_d[2]'] 913 | q3_d = self.log_data_list[index].data['q_d[3]'] 914 | self.attitude_setpoint_history = self.quat_to_euler(q0_d, q1_d, q2_d, q3_d) 915 | # position 916 | index = list(self.data_dict.keys()).index('vehicle_local_position') 917 | self.time_stamp_position = self.log_data_list[index].data['timestamp']/10**6 918 | x = self.log_data_list[index].data['x'] 919 | y = self.log_data_list[index].data['y'] 920 | z = self.log_data_list[index].data['z'] 921 | self.position_history = [(x[i]*self.SCALE_FACTOR, y[i]*self.SCALE_FACTOR, 922 | z[i]*self.SCALE_FACTOR) for i in range(len(x))] 923 | # motor rotation 924 | index = list(self.data_dict.keys()).index('actuator_outputs') 925 | self.time_stamp_output = self.log_data_list[index].data['timestamp']/10**6 926 | output0 = self.log_data_list[index].data['output[0]'] 927 | output1 = self.log_data_list[index].data['output[1]'] 928 | output2 = self.log_data_list[index].data['output[2]'] 929 | output3 = self.log_data_list[index].data['output[3]'] 930 | self.output_history = [(output0[i], output1[i], output2[i], output3[i]) for i in range(len(output0))] 931 | 932 | # get common time range 933 | self.time_range = max([self.time_stamp_attitude[0], self.time_stamp_output[0], self.time_stamp_position[0]]), \ 934 | min([self.time_stamp_attitude[-1], self.time_stamp_output[-1], self.time_stamp_position[-1]]) 935 | self.data_loaded = True 936 | 937 | def load_data_tree(self): 938 | # update the tree list table 939 | self.item_list_treeWidget.clear() 940 | for key, values in self.data_dict.items(): 941 | param_name = QtGui.QTreeWidgetItem(self.item_list_treeWidget, [key]) 942 | self.item_list_treeWidget.expandItem(param_name) 943 | for data_name in values: 944 | self.item_list_treeWidget.expandItem( 945 | QtGui.QTreeWidgetItem(param_name, [data_name[0], data_name[1], data_name[2]])) 946 | param_name.setExpanded(False) 947 | 948 | def update_graph_after_log_changed(self): 949 | # after load_data_tree 950 | data_plotting = OrderedDict() 951 | if self.data_plotting: 952 | data_keys, data_values = list(self.data_dict.keys()), list(self.data_dict.values()) 953 | for item_id, item in self.data_plotting.items(): 954 | item_label, showed, curve = item 955 | t, data = curve.xData, curve.yData 956 | parent_name, data_name = item_label.split('->') 957 | found = False 958 | if parent_name in data_keys: 959 | for item in data_values[data_keys.index(parent_name)]: 960 | if data_name == item[0]: 961 | found = True 962 | if found: 963 | data_index = list(list(self.data_dict.keys())).index(parent_name) 964 | t = self.log_data_list[data_index].data['timestamp']/10**6 965 | data = self.log_data_list[data_index].data[data_name] 966 | curve.setData(t, data) 967 | data_plotting[item_id] = [item_label, showed, curve] 968 | self.data_plotting = data_plotting 969 | self.update_graph() 970 | 971 | def quadrotor_win_closed_event(self, closed): 972 | if closed: 973 | self.quadrotor_widget_isshowed = not self.quadrotor_widget_isshowed 974 | self.show_quadrotor_3d.setIcon(QtGui.QIcon(get_source_name('icons/quadrotor.gif'))) 975 | 976 | def draw_predefined_graph(self, name): 977 | 978 | def add_context_action(ax): 979 | def callback(*args, **kargs): 980 | for item in ax.items(): 981 | if isinstance(item, pg.PlotDataItem): 982 | if item.opts['symbol'] is None: 983 | item.setData(item.xData, item.yData, symbol='s') 984 | else: 985 | item.setData(item.xData, item.yData, symbol=None) 986 | return callback 987 | 988 | if name == 'XY_Estimation': 989 | graph_xy = pg.GraphicsLayoutWidget() 990 | self.default_tab.addTab(graph_xy, name) 991 | ax = graph_xy.addPlot(row=0, col=0) 992 | show_marker_action = QtGui.QAction('show/hide marker', graph_xy) 993 | show_marker_action.triggered.connect(add_context_action(ax)) 994 | data_index = list(list(self.data_dict.keys())).index('vehicle_local_position') 995 | x = self.log_data_list[data_index].data['x'] 996 | y = self.log_data_list[data_index].data['y'] 997 | # plot the xy trace line in red 998 | ax.plot(x, y, pen=(255, 0, 0)) 999 | 1000 | def closeEvent(self, *args, **kwargs): 1001 | if self.analysis_graphs_showed: 1002 | self.analysis_graphs_pane.close() 1003 | if self.params_pane_showed: 1004 | self.params_pane.close() 1005 | if self.info_pane_showed: 1006 | self.info_pane.close() 1007 | if self.help_pane_showed: 1008 | pass 1009 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 1010 | 1011 | 1012 | def main(): 1013 | def func(): 1014 | print(app.focusWidget()) 1015 | app = QtGui.QApplication(sys.argv) 1016 | app.focusChanged.connect(func) 1017 | mainwin = MainWindow() 1018 | mainwin.show() 1019 | print(app.focusWidget()) 1020 | sys.exit(app.exec_()) 1021 | 1022 | if __name__ == '__main__': 1023 | main() 1024 | -------------------------------------------------------------------------------- /src/config.txt: -------------------------------------------------------------------------------- 1 | last_path:/home/marxlp/Downloads -------------------------------------------------------------------------------- /src/docs/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | pyFlightAnalysis Help 4 | 5 | 6 |

Introduction

7 | This is a simple introduction to the pyFlightAnalysis app. pyFlightAnalysis is mainly an visualization tool to help analyse flight data generated by PX4 flight control system. 8 | Functions pyFlightAnalysis provided include plot data by time or by other data. 9 | 10 |

Brief Usage Tutorial

11 | There are five buttons on toolbar, function as opening log file, showing 3D animation of quadricopter, showing px4 information recorded in the log file, showing parameters setted on the px4, showing analysis graph pane, showing this help page. 12 | Although it's easy to get started using pyFlightAnalysis, there are still some features need to be point out: 13 |

1 There is a ROI graph which can be triggered through context menu button on "t tab" 14 |

2 The default plotting data pane will changed in response to clicking three default plotting tabs

15 |

3 Currently, the app is developed intensively, so keep focus on the project page on github.

16 | To find other information or to report issues, go to pyFlightAnalysis on github 17 | 18 | -------------------------------------------------------------------------------- /src/icons/analysis_graph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/analysis_graph.gif -------------------------------------------------------------------------------- /src/icons/analysis_graph_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/analysis_graph_pressed.gif -------------------------------------------------------------------------------- /src/icons/help.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/help.gif -------------------------------------------------------------------------------- /src/icons/help_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/help_pressed.gif -------------------------------------------------------------------------------- /src/icons/info.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/info.gif -------------------------------------------------------------------------------- /src/icons/info_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/info_pressed.gif -------------------------------------------------------------------------------- /src/icons/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/loading.gif -------------------------------------------------------------------------------- /src/icons/open.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/open.gif -------------------------------------------------------------------------------- /src/icons/params.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/params.gif -------------------------------------------------------------------------------- /src/icons/params_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/params_pressed.gif -------------------------------------------------------------------------------- /src/icons/pause.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/pause.jpg -------------------------------------------------------------------------------- /src/icons/play.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/play.jpg -------------------------------------------------------------------------------- /src/icons/quadrotor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/quadrotor.gif -------------------------------------------------------------------------------- /src/icons/quadrotor_focus.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/quadrotor_focus.gif -------------------------------------------------------------------------------- /src/icons/quadrotor_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/quadrotor_pressed.gif -------------------------------------------------------------------------------- /src/icons/readme.txt: -------------------------------------------------------------------------------- 1 | pressed with back ground (181,181,181) 2 | -------------------------------------------------------------------------------- /src/icons/rotor_vector.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/rotor_vector.gif -------------------------------------------------------------------------------- /src/icons/rotor_vector_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/rotor_vector_pressed.gif -------------------------------------------------------------------------------- /src/icons/stop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/stop.jpg -------------------------------------------------------------------------------- /src/icons/trace.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/trace.gif -------------------------------------------------------------------------------- /src/icons/trace_pressed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Marxlp/pyFlightAnalysis/c9c5fa40eb12d5236b9cf29ac882d361ba03cfb0/src/icons/trace_pressed.gif -------------------------------------------------------------------------------- /src/models/drone_base.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 9 3 | 4 | newmtl blinn1SG.001 5 | Ns 94.117647 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 1.000000 0.000000 0.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 0.000000 12 | illum 6 13 | 14 | newmtl blinn2SG.001 15 | Ns 94.117647 16 | Ka 0.000000 0.000000 0.000000 17 | Kd 1.000000 1.000000 0.000000 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 0.000000 22 | illum 6 23 | 24 | newmtl blinn3SG.001 25 | Ns 94.117647 26 | Ka 0.000000 0.000000 0.000000 27 | Kd 0.000000 1.000000 0.000000 28 | Ks 0.500000 0.500000 0.500000 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.000000 31 | d 0.000000 32 | illum 6 33 | 34 | newmtl initialShadingGroup.001 35 | Ns 94.117647 36 | Ka 0.000000 0.000000 0.000000 37 | Kd 0.500000 0.500000 0.500000 38 | Ks 0.500000 0.500000 0.500000 39 | Ke 0.000000 0.000000 0.000000 40 | Ni 1.000000 41 | d 0.000000 42 | illum 6 43 | 44 | newmtl lambert3SG.001 45 | Ns 94.117647 46 | Ka 0.000000 0.000000 0.000000 47 | Kd 0.760000 0.330000 0.000000 48 | Ks 0.500000 0.500000 0.500000 49 | Ke 0.000000 0.000000 0.000000 50 | Ni 1.000000 51 | d 0.000000 52 | illum 6 53 | 54 | newmtl lambert4SG.001 55 | Ns 94.117647 56 | Ka 0.000000 0.000000 0.000000 57 | Kd 0.000000 0.000000 1.000000 58 | Ks 0.500000 0.500000 0.500000 59 | Ke 0.000000 0.000000 0.000000 60 | Ni 1.000000 61 | d 0.000000 62 | illum 6 63 | 64 | newmtl lambert5SG.001 65 | Ns 94.117647 66 | Ka 0.000000 0.000000 0.000000 67 | Kd 1.000000 0.000000 1.000000 68 | Ks 0.500000 0.500000 0.500000 69 | Ke 0.000000 0.000000 0.000000 70 | Ni 1.000000 71 | d 0.000000 72 | illum 6 73 | 74 | newmtl lambert6SG.001 75 | Ns 94.117647 76 | Ka 0.000000 0.000000 0.000000 77 | Kd 0.100000 0.410000 0.210000 78 | Ks 0.500000 0.500000 0.500000 79 | Ke 0.000000 0.000000 0.000000 80 | Ni 1.000000 81 | d 0.000000 82 | illum 6 83 | 84 | newmtl lambert7SG.001 85 | Ns 94.117647 86 | Ka 0.000000 0.000000 0.000000 87 | Kd 0.000000 0.000000 0.000000 88 | Ks 0.500000 0.500000 0.500000 89 | Ke 0.000000 0.000000 0.000000 90 | Ni 1.000000 91 | d 1.000000 92 | illum 2 93 | -------------------------------------------------------------------------------- /src/models/drone_propeller1.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl lambert2SG 5 | Ns 96.078431 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 0.000000 1.000000 1.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 0.000000 12 | illum 6 13 | -------------------------------------------------------------------------------- /src/models/drone_propeller1.obj: -------------------------------------------------------------------------------- 1 | # Blender v2.76 (sub 0) OBJ File: '' 2 | # www.blender.org 3 | mtllib drone_propeller1.mtl 4 | o main_motor_prop2_polySurface6 5 | v -11.410919 1.172727 -11.135193 6 | v -11.443974 1.167181 -10.978996 7 | v -11.432232 1.163438 -10.836441 8 | v -11.352676 1.159034 -10.657936 9 | v -11.206818 1.155066 -10.527054 10 | v -11.089355 1.154798 -10.475960 11 | v -10.974680 1.154016 -10.455427 12 | v -10.771194 1.156635 -10.485970 13 | v -10.612305 1.159455 -10.588043 14 | v -10.518219 1.164041 -10.708344 15 | v -10.466690 1.167544 -10.848038 16 | v -11.372345 1.428052 -11.034760 17 | v -11.413235 1.311592 -10.912907 18 | v -11.359484 1.447415 -10.830200 19 | v -11.362457 1.364342 -10.769417 20 | v -13.773676 1.118523 -2.546692 21 | v -13.552971 1.184776 -2.610168 22 | v -13.584898 1.207492 -2.413441 23 | v -13.554726 1.220588 -2.635119 24 | v -10.969763 1.452866 -10.521315 25 | v -10.838089 1.312500 -10.500916 26 | v -10.845520 1.492997 -10.550659 27 | v -10.721161 1.437989 -10.585754 28 | v -10.647217 1.439759 -10.647949 29 | v -10.590668 1.456158 -10.726624 30 | v -10.528751 1.419219 -10.852661 31 | v -11.316280 1.573168 -11.017517 32 | v -11.347393 1.519683 -10.929565 33 | v -11.290133 1.621109 -10.906441 34 | v -11.320984 1.547173 -10.839035 35 | v -11.276535 1.598095 -10.798630 36 | v -11.227566 1.595414 -10.716057 37 | v -11.195707 1.538289 -10.642386 38 | v -11.086914 1.634247 -10.638428 39 | v -11.063225 1.557618 -10.579941 40 | v -10.970271 1.711801 -10.665770 41 | v -10.923553 1.550099 -10.560955 42 | v -10.820892 1.588410 -10.604507 43 | v -10.727821 1.623373 -10.685631 44 | v -10.677597 1.625111 -10.740173 45 | v -10.664093 1.566388 -10.699509 46 | v -10.585880 1.562340 -10.852661 47 | v -10.780273 1.154825 -10.710342 48 | v -10.683220 1.156091 -10.839844 49 | v -10.946237 1.156667 -10.637095 50 | v -11.141916 1.157919 -10.694761 51 | v -11.249497 1.161162 -10.862339 52 | v -11.245970 1.167255 -11.053947 53 | v -10.730820 1.149109 -10.769379 54 | v -10.784554 1.142136 -10.726814 55 | v -10.767532 1.148780 -10.776031 56 | v -10.717667 1.150774 -10.855576 57 | v -10.903507 1.145144 -10.667171 58 | v -10.871185 1.146384 -10.710487 59 | v -10.966333 1.145610 -10.699793 60 | v -11.000048 1.141260 -10.674303 61 | v -11.042042 1.146323 -10.716763 62 | v -11.094978 1.145875 -10.705067 63 | v -11.110530 1.147026 -10.756823 64 | v -11.181276 1.143231 -10.786334 65 | v -11.163055 1.147638 -10.815517 66 | v -11.227789 1.152032 -10.880306 67 | v -11.194984 1.149477 -10.889511 68 | v -11.231913 1.149364 -10.962732 69 | v -11.201438 1.152510 -10.968173 70 | v -11.220909 1.155968 -11.045654 71 | v -11.180687 1.153970 -11.056152 72 | v -10.745377 1.164115 -10.806885 73 | v -10.706776 1.166851 -10.896210 74 | v -10.813232 1.161969 -10.741943 75 | v -10.884232 1.160754 -10.709717 76 | v -10.965309 1.160176 -10.700875 77 | v -11.077559 1.160522 -10.733095 78 | v -11.177507 1.150737 -10.771770 79 | v -11.160957 1.162709 -10.815506 80 | v -11.191757 1.164655 -10.887146 81 | v -11.195597 1.168436 -11.009140 82 | v -11.106674 1.409096 -10.538815 83 | v -11.222946 1.356400 -10.588676 84 | v -11.363354 1.291551 -10.727772 85 | v -11.428625 1.242159 -10.912772 86 | v -11.424309 1.272690 -10.949997 87 | v -10.705597 1.718456 -10.834991 88 | v -10.789993 1.714866 -10.724838 89 | v -10.885178 1.713073 -10.678543 90 | v -11.097027 1.713233 -10.709701 91 | v -11.197292 1.716233 -10.828491 92 | v -11.215424 1.720660 -10.984470 93 | v -11.034416 1.509989 -10.553073 94 | v -11.167519 1.458621 -10.584229 95 | v -11.297081 1.472897 -10.714691 96 | v -13.710449 1.104462 -2.890625 97 | v -13.716431 1.132797 -2.806152 98 | v -13.362671 1.291496 -2.679138 99 | v -13.427544 1.263506 -2.479736 100 | v -10.737885 1.529877 -10.615448 101 | v -11.270786 1.414221 -10.652119 102 | v -11.400040 1.358714 -10.628952 103 | v -10.749634 1.542420 -10.502930 104 | v -10.797363 1.599671 -10.555664 105 | v -11.650269 1.213120 -10.304932 106 | v -11.640293 1.289808 -10.220087 107 | v -11.625244 1.272156 -10.333496 108 | v -11.626709 1.240143 -10.375977 109 | v -10.819580 1.602631 -10.325195 110 | v -10.873291 1.548432 -10.081055 111 | v -10.940063 1.596848 -9.974121 112 | v -10.921631 1.574967 -9.944824 113 | v -13.690079 1.154134 -2.806641 114 | v -13.166016 1.327393 -3.210938 115 | v -12.800293 1.342896 -4.122070 116 | v -13.083984 1.300781 -3.359375 117 | v -12.773300 1.329318 -4.233815 118 | v -13.324463 1.258881 -2.724609 119 | v -13.372314 1.239716 -2.669250 120 | v -13.724762 1.097981 -2.769409 121 | v -13.658691 1.090561 -3.093750 122 | v -13.340652 1.121794 -4.427780 123 | v -13.008187 1.286072 -4.366200 124 | v -12.794434 1.383911 -4.226562 125 | v -12.306358 1.414771 -5.895625 126 | v -12.275829 1.435159 -5.836098 127 | v -12.373047 1.375489 -5.421875 128 | v -12.147978 1.427731 -6.158302 129 | v -12.578726 1.385501 -4.817733 130 | v -12.199745 1.371628 -6.095835 131 | v -12.539959 1.311072 -5.329171 132 | v -12.688953 1.235492 -5.595686 133 | v -13.072418 1.229769 -4.089437 134 | v -13.194633 1.128953 -4.675842 135 | v -13.256836 1.088959 -4.777344 136 | v -13.164021 1.097030 -5.067074 137 | v -13.060547 1.194956 -5.108245 138 | v -13.147927 1.158791 -5.063668 139 | v -12.574567 1.318671 -5.824196 140 | v -11.857450 1.402402 -8.050844 141 | v -11.732422 1.494080 -7.574219 142 | v -11.632812 1.504486 -7.829590 143 | v -11.609245 1.462089 -7.822764 144 | v -11.798828 1.446289 -7.253906 145 | v -11.660156 1.438477 -7.710938 146 | v -11.741319 1.382965 -7.964978 147 | v -11.975683 1.242582 -8.616560 148 | v -12.598125 1.141202 -7.010260 149 | v -12.258301 1.114991 -8.719727 150 | v -12.724674 1.149208 -6.827185 151 | v -12.280518 1.128785 -8.649414 152 | v -11.183777 1.579411 -9.086060 153 | v -11.164124 1.530037 -9.024170 154 | v -11.194641 1.549240 -8.966553 155 | v -11.203491 1.506897 -8.952148 156 | v -12.079956 1.128800 -9.316895 157 | v -12.068359 1.197022 -9.253906 158 | v -12.075936 1.152769 -9.339558 159 | v -12.236539 1.169867 -8.724620 160 | v -12.121343 1.255847 -8.523687 161 | v -11.884536 1.166036 -9.794504 162 | v -11.882507 1.188057 -9.809643 163 | v -11.867233 1.222366 -9.789315 164 | v -10.458800 1.171652 -10.997978 165 | v -10.495373 1.176389 -11.145775 166 | v -10.575855 1.179624 -11.272779 167 | v -10.695175 1.182100 -11.373688 168 | v -10.991829 1.182905 -11.442918 169 | v -11.156769 1.181489 -11.401627 170 | v -11.247559 1.178896 -11.343933 171 | v -11.325470 1.176251 -11.273987 172 | v -10.489411 1.317454 -10.979095 173 | v -10.545195 1.493430 -10.948400 174 | v -10.544264 1.457291 -11.051163 175 | v -10.537641 1.373806 -11.114778 176 | v -8.128437 1.625890 -19.344604 177 | v -8.350254 1.732199 -19.471870 178 | v -8.336742 1.717176 -19.246916 179 | v -8.462677 1.720454 -19.259888 180 | v -8.540298 1.787694 -19.203796 181 | v -10.933159 1.476767 -11.361804 182 | v -11.064653 1.355480 -11.380160 183 | v -11.059204 1.514817 -11.330078 184 | v -11.222626 1.443543 -11.271118 185 | v -11.143860 1.541367 -11.283203 186 | v -11.314819 1.455964 -11.158234 187 | v -10.600744 1.606250 -10.917950 188 | v -10.596652 1.582421 -11.039162 189 | v -10.649534 1.610669 -11.118317 190 | v -10.621435 1.544114 -11.145279 191 | v -10.716824 1.558686 -11.241226 192 | v -10.732009 1.640427 -11.186852 193 | v -10.828682 1.607941 -11.272720 194 | v -10.949474 1.562817 -11.325616 195 | v -10.927994 1.727501 -11.202663 196 | v -11.082611 1.605835 -11.271622 197 | v -11.179115 1.635369 -11.186821 198 | v -11.229370 1.635504 -11.124939 199 | v -11.237495 1.574353 -11.183914 200 | v -11.294312 1.553664 -11.111206 201 | v -10.846458 1.182863 -11.434948 202 | v -11.162384 1.166178 -11.151321 203 | v -11.010929 1.168431 -11.235921 204 | v -10.861717 1.174505 -11.249600 205 | v -10.693426 1.172736 -11.144831 206 | v -10.655085 1.167792 -11.041412 207 | v -11.178596 1.152021 -11.113739 208 | v -11.103638 1.160352 -11.191574 209 | v -11.116394 1.157311 -11.139740 210 | v -11.029091 1.155509 -11.218374 211 | v -11.049286 1.159991 -11.180725 212 | v -10.975510 1.160729 -11.200235 213 | v -10.830549 1.155890 -11.202646 214 | v -10.895584 1.161283 -11.195169 215 | v -10.823257 1.160550 -11.166649 216 | v -10.738028 1.159870 -11.140158 217 | v -10.762657 1.158948 -11.117185 218 | v -10.690098 1.158250 -11.066933 219 | v -10.723007 1.156507 -11.056747 220 | v -10.673525 1.153821 -11.013033 221 | v -10.697653 1.153457 -10.951937 222 | v -10.669847 1.151066 -10.923450 223 | v -11.140747 1.171340 -11.111206 224 | v -11.084900 1.172960 -11.159821 225 | v -11.031987 1.173898 -11.185053 226 | v -10.955402 1.174641 -11.200177 227 | v -10.908016 1.161510 -11.233242 228 | v -10.863014 1.174528 -11.183506 229 | v -10.803875 1.162909 -11.196468 230 | v -10.792220 1.173639 -11.143023 231 | v -10.740768 1.172149 -11.084381 232 | v -10.714241 1.170710 -11.028870 233 | v -10.702599 1.169118 -10.974091 234 | v -10.796997 1.433823 -11.346615 235 | v -10.680206 1.379284 -11.300148 236 | v -10.539065 1.307505 -11.164893 237 | v -10.482513 1.255967 -11.025543 238 | v -10.472573 1.252748 -10.969524 239 | v -11.136787 1.725221 -11.127876 240 | v -11.005524 1.727526 -11.194153 241 | v -10.805511 1.727207 -11.154831 242 | v -10.729671 1.725176 -11.076241 243 | v -10.690386 1.722433 -10.974388 244 | v -10.791504 1.504441 -11.315500 245 | v -10.652087 1.444996 -11.252289 246 | v -8.191528 1.583436 -19.036743 247 | v -8.193726 1.591370 -18.991211 248 | v -8.192871 1.621629 -19.043945 249 | v -8.583984 1.749497 -19.141602 250 | v -8.479767 1.772703 -19.394470 251 | v -10.565884 1.409742 -11.280298 252 | v -10.955242 1.497859 -11.431988 253 | v -10.893122 1.567364 -11.535013 254 | v -10.251709 1.255871 -11.590820 255 | v -10.262130 1.337236 -11.671606 256 | v -10.277832 1.311150 -11.564453 257 | v -10.275513 1.278397 -11.518066 258 | v -10.676123 1.646790 -12.654903 259 | v -11.012695 1.577912 -11.821289 260 | v -11.086914 1.636887 -11.546875 261 | v -11.034180 1.597382 -11.784668 262 | v -10.964600 1.652390 -11.899902 263 | v -10.988819 1.628531 -11.905661 264 | v -8.216766 1.648438 -19.081299 265 | v -8.689657 1.765260 -18.620539 266 | v -8.979492 1.791589 -18.016602 267 | v -9.018555 1.758851 -17.986328 268 | v -8.203926 1.609780 -19.147949 269 | v -8.561819 1.517448 -17.463562 270 | v -8.895597 1.682353 -17.515629 271 | v -9.511099 1.729136 -16.277639 272 | v -9.282715 1.773621 -17.146484 273 | v -9.761852 1.690672 -15.734376 274 | v -9.591263 1.728323 -16.235666 275 | v -9.214844 1.746705 -17.417969 276 | v -9.313493 1.694647 -16.982700 277 | v -9.327106 1.714237 -17.050865 278 | v -9.363788 1.647988 -16.552929 279 | v -9.214134 1.558337 -16.291189 280 | v -8.830982 1.642635 -17.795168 281 | v -8.707834 1.509110 -17.215530 282 | v -8.646484 1.463380 -17.105469 283 | v -8.909021 1.415290 -16.153393 284 | v -8.842342 1.548970 -16.780071 285 | v -8.754687 1.516136 -16.826641 286 | v -9.329127 1.627271 -16.058285 287 | v -10.046375 1.576601 -13.830681 288 | v -10.190063 1.694810 -14.249512 289 | v -10.294434 1.651612 -14.060547 290 | v -10.101562 1.665406 -14.638672 291 | v -10.246094 1.632874 -14.166016 292 | v -10.162369 1.561229 -13.917553 293 | v -9.926744 1.385335 -13.275108 294 | v -9.303885 1.381819 -14.884438 295 | v -9.642578 1.254395 -13.181641 296 | v -9.177443 1.401389 -15.066717 297 | v -9.622070 1.272797 -13.242188 298 | v -10.722168 1.687684 -12.781006 299 | v -10.714111 1.650189 -12.929199 300 | v -10.709717 1.626877 -12.915039 301 | v -9.821411 1.232125 -12.582764 302 | v -9.833740 1.303818 -12.642578 303 | v -9.825612 1.254735 -12.558478 304 | v -9.665291 1.308583 -13.171366 305 | v -9.781216 1.405100 -13.367026 306 | v -10.869677 1.649058 -12.354177 307 | v -10.875448 1.623199 -12.374814 308 | v -10.017007 1.240188 -12.103568 309 | v -10.019207 1.261284 -12.087184 310 | v -10.034760 1.296585 -12.105500 311 | v -10.750598 1.717088 -10.792996 312 | v -10.775179 1.716128 -10.763126 313 | v -10.804107 1.715271 -10.737439 314 | v -10.872131 1.713961 -10.701157 315 | v -10.836721 1.714540 -10.716625 316 | v -10.909607 1.713517 -10.691410 317 | v -10.948147 1.713256 -10.687624 318 | v -10.986771 1.713169 -10.689949 319 | v -11.024567 1.713265 -10.698257 320 | v -11.060577 1.713516 -10.712372 321 | v -11.093979 1.713953 -10.731949 322 | v -11.123796 1.714546 -10.756479 323 | v -11.149559 1.715283 -10.785461 324 | v -11.170364 1.716145 -10.818054 325 | v -11.185898 1.717102 -10.853485 326 | v -11.195606 1.718144 -10.890915 327 | v -11.199399 1.719242 -10.929422 328 | v -11.197083 1.720365 -10.968048 329 | v -11.188782 1.721492 -11.005798 330 | v -11.174591 1.722592 -11.041870 331 | v -11.155068 1.723629 -11.075190 332 | v -11.130487 1.724589 -11.105061 333 | v -11.101560 1.725448 -11.130747 334 | v -11.033508 1.726773 -11.167038 335 | v -11.068945 1.726179 -11.151562 336 | v -10.996033 1.727201 -11.176788 337 | v -10.957527 1.727460 -11.180559 338 | v -10.918884 1.727550 -11.178234 339 | v -10.881073 1.727453 -11.169922 340 | v -10.845123 1.727200 -11.155838 341 | v -10.811676 1.726760 -11.136230 342 | v -10.781759 1.726160 -11.111601 343 | v -10.756180 1.725445 -11.082825 344 | v -10.735245 1.724575 -11.050049 345 | v -10.719795 1.723619 -11.014679 346 | v -10.710056 1.722575 -10.977295 347 | v -10.706267 1.721477 -10.938757 348 | v -10.708588 1.720351 -10.900116 349 | v -10.716885 1.719227 -10.862367 350 | v -10.731018 1.718136 -10.826355 351 | vt 0.485704 0.634109 352 | vt 0.504141 0.594834 353 | vt 0.500000 0.676623 354 | vt 0.749737 0.251236 355 | vt 0.714563 0.372665 356 | vt 0.651879 0.443448 357 | vt 0.560610 0.490348 358 | vt 0.571758 0.631029 359 | vt 0.573633 0.659101 360 | vt 0.516582 0.578812 361 | vt 0.512431 0.557553 362 | vt 0.488613 0.698934 363 | vt 0.503065 0.727630 364 | vt 0.585525 0.643925 365 | vt 0.617859 0.590782 366 | vt 0.597393 0.664033 367 | vt 0.517420 0.718774 368 | vt 0.513118 0.775441 369 | vt 0.693840 0.394483 370 | vt 0.746974 0.269106 371 | vt 0.746662 0.270727 372 | vt 0.727583 0.343842 373 | vt 0.560714 0.693128 374 | vt 0.552986 0.774800 375 | vt 0.548463 0.730873 376 | vt 0.576612 0.702680 377 | vt 0.584456 0.725893 378 | vt 0.718733 0.360163 379 | vt 0.746624 0.271312 380 | vt 0.500334 0.499815 381 | vt 0.438720 0.490156 382 | vt 0.687971 0.406616 383 | vt 0.287406 0.375570 384 | vt 0.259352 0.308299 385 | vt 0.253698 0.272905 386 | vt 0.296131 0.381336 387 | vt 0.404061 0.476713 388 | vt 0.479989 0.495356 389 | vt 0.529023 0.492062 390 | vt 0.353932 0.447582 391 | vt 0.640800 0.450332 392 | vt 0.609532 0.468891 393 | vt 0.556319 0.489644 394 | vt 0.558110 0.490796 395 | vt 0.632814 0.459455 396 | vt 0.289102 0.376985 397 | vt 0.262012 0.313152 398 | vt 0.289570 0.377749 399 | vt 0.733948 0.325929 400 | vt 0.690993 0.408207 401 | vt 0.443253 0.488426 402 | vt 0.575196 0.482745 403 | vt 0.417589 0.476243 404 | vt 0.339030 0.436253 405 | vt 0.339334 0.438534 406 | vt 0.441941 0.489227 407 | vt 0.360131 0.450728 408 | vt 0.288960 0.379702 409 | vt 0.260282 0.306009 410 | vt 0.253981 0.275134 411 | vt 0.597283 0.775483 412 | vt 0.578708 0.776379 413 | vt 0.596828 0.734388 414 | vt 0.607897 0.729839 415 | vt 0.250000 0.250000 416 | vt 0.652624 0.501004 417 | vt 0.619337 0.501003 418 | vt 0.548618 0.501001 419 | vt 0.556363 0.594201 420 | vt 0.531696 0.561268 421 | vt 0.504566 0.560515 422 | vt 0.502302 0.575571 423 | vt 0.516956 0.500986 424 | vt 0.503782 0.555583 425 | vt 0.475052 0.501022 426 | vt 0.493842 0.500961 427 | vt 0.497526 0.572430 428 | vt 0.564366 0.776342 429 | vt 0.494539 0.776354 430 | vt 0.645188 0.645763 431 | vt 0.667270 0.628290 432 | vt 0.635338 0.699248 433 | vt 0.555325 0.654506 434 | vt 0.542033 0.692024 435 | vt 0.542375 0.629654 436 | vt 0.530420 0.652909 437 | vt 0.512068 0.688409 438 | vt 0.513786 0.643302 439 | vt 0.622534 0.637892 440 | vt 0.635454 0.638742 441 | vt 0.616436 0.700302 442 | vt 0.608113 0.681849 443 | vt 0.707513 0.501067 444 | vt 0.724557 0.500985 445 | vt 0.682915 0.500996 446 | vt 0.599798 0.501009 447 | vt 0.509752 0.593750 448 | vt 0.605387 0.711743 449 | vt 0.604754 0.685051 450 | vt 0.599525 0.728571 451 | vt 0.563296 0.663388 452 | vt 0.534761 0.618063 453 | vt 0.523751 0.605376 454 | vt 0.510000 0.600000 455 | vt 0.607907 0.681393 456 | vt 0.591803 0.727445 457 | vt 0.572442 0.623345 458 | vt 0.546547 0.581272 459 | vt 0.503110 0.556962 460 | vt 0.507332 0.553965 461 | vt 0.500000 0.575571 462 | vt 0.502500 0.593893 463 | vt 0.600349 0.735054 464 | vt 0.567264 0.680312 465 | vt 0.567251 0.680729 466 | vt 0.616755 0.703184 467 | vt 0.604416 0.734683 468 | vt 0.611802 0.720524 469 | vt 0.614533 0.710962 470 | vt 0.603131 0.735077 471 | vt 0.588269 0.719292 472 | vt 0.591874 0.656366 473 | vt 0.614715 0.691599 474 | vt 0.604596 0.734450 475 | vt 0.611830 0.720424 476 | vt 0.563473 0.672295 477 | vt 0.524439 0.602216 478 | vt 0.527508 0.610510 479 | vt 0.613313 0.697205 480 | vt 0.615191 0.692312 481 | vt 0.501250 0.584732 482 | vt 0.502805 0.557250 483 | vt 0.576632 0.632004 484 | vt 0.594845 0.661317 485 | vt 0.511222 0.551143 486 | vt 0.612080 0.719549 487 | vt 0.601749 0.735103 488 | vt 0.589992 0.724669 489 | vt 0.615561 0.694886 490 | vt 0.527367 0.558014 491 | vt 0.531185 0.561525 492 | vt 0.605533 0.732536 493 | vt 0.601264 0.734458 494 | vt 0.614562 0.691344 495 | vt 0.505652 0.596459 496 | vt 0.526729 0.609406 497 | vt 0.563426 0.672431 498 | vt 0.608843 0.682701 499 | vt 0.568057 0.616362 500 | vt 0.579584 0.670411 501 | vt 0.561293 0.658785 502 | vt 0.524562 0.602351 503 | vt 0.517153 0.579191 504 | vt 0.486519 0.630271 505 | vt 0.504201 0.594834 506 | vt 0.500000 0.662291 507 | vt 0.597558 0.660607 508 | vt 0.618519 0.587761 509 | vt 0.654480 0.502578 510 | vt 0.608135 0.676538 511 | vt 0.529984 0.567829 512 | vt 0.598661 0.727177 513 | vt 0.591585 0.664797 514 | vt 0.523120 0.719380 515 | vt 0.528395 0.679891 516 | vt 0.543270 0.691152 517 | vt 0.535969 0.729381 518 | vt 0.585553 0.644266 519 | vt 0.518994 0.496908 520 | vt 0.726286 0.349676 521 | vt 0.702301 0.396561 522 | vt 0.592973 0.479515 523 | vt 0.574154 0.696778 524 | vt 0.550451 0.770324 525 | vt 0.553644 0.717167 526 | vt 0.584725 0.725078 527 | vt 0.608501 0.729851 528 | vt 0.631558 0.690453 529 | vt 0.635499 0.698891 530 | vt 0.708848 0.380334 531 | vt 0.731527 0.329049 532 | vt 0.631496 0.458098 533 | vt 0.659927 0.437144 534 | vt 0.616542 0.464747 535 | vt 0.720773 0.357775 536 | vt 0.501888 0.494653 537 | vt 0.402932 0.476244 538 | vt 0.271174 0.344755 539 | vt 0.298663 0.383846 540 | vt 0.253059 0.269314 541 | vt 0.250352 0.252221 542 | vt 0.576074 0.483890 543 | vt 0.646946 0.452254 544 | vt 0.594948 0.477123 545 | vt 0.687081 0.410002 546 | vt 0.536025 0.492824 547 | vt 0.464096 0.489704 548 | vt 0.518615 0.495467 549 | vt 0.288341 0.370374 550 | vt 0.372038 0.460299 551 | vt 0.442266 0.489305 552 | vt 0.317298 0.415123 553 | vt 0.265891 0.321944 554 | vt 0.258149 0.296807 555 | vt 0.747562 0.265395 556 | vt 0.729038 0.344658 557 | vt 0.744079 0.283162 558 | vt 0.534288 0.776383 559 | vt 0.519529 0.776298 560 | vt 0.688505 0.410695 561 | vt 0.484330 0.497518 562 | vt 0.440655 0.488919 563 | vt 0.405475 0.478964 564 | vt 0.371225 0.459802 565 | vt 0.314533 0.411890 566 | vt 0.296930 0.394897 567 | vt 0.339365 0.438565 568 | vt 0.271016 0.344485 569 | vt 0.253029 0.269125 570 | vt 0.629773 0.500974 571 | vt 0.572472 0.623426 572 | vt 0.580179 0.501014 573 | vt 0.605984 0.500986 574 | vt 0.598652 0.776404 575 | vt 0.582180 0.776383 576 | vt 0.554971 0.501019 577 | vt 0.556303 0.594520 578 | vt 0.506212 0.500961 579 | vt 0.511269 0.549264 580 | vt 0.501986 0.561329 581 | vt 0.531181 0.500979 582 | vt 0.483323 0.501099 583 | vt 0.562284 0.776336 584 | vt 0.598135 0.735470 585 | vt 0.649788 0.635291 586 | vt 0.667356 0.628097 587 | vt 0.511759 0.702918 588 | vt 0.612704 0.696427 589 | vt 0.605709 0.712198 590 | vt 0.545208 0.635358 591 | vt 0.560703 0.665550 592 | vt 0.513789 0.643433 593 | vt 0.510086 0.557522 594 | vt 0.488022 0.695061 595 | vt 0.497194 0.719383 596 | vt 0.509298 0.593730 597 | vt 0.523228 0.605015 598 | vt 0.535261 0.618584 599 | vt 0.590307 0.653866 600 | vt 0.575765 0.699881 601 | vt 0.507465 0.553869 602 | vt 0.614776 0.691684 603 | vt 0.590592 0.725977 604 | vt 0.616200 0.698572 605 | vt 0.604678 0.734181 606 | vt 0.615451 0.707748 607 | vt 0.604316 0.734514 608 | vt 0.615602 0.692929 609 | vt 0.604588 0.734452 610 | vt 0.584233 0.714558 611 | vt 0.591351 0.726575 612 | vt 0.607497 0.680779 613 | vt 0.618873 0.695771 614 | vt 0.601112 0.735171 615 | vt 0.607795 0.734550 616 | vt 0.614899 0.691863 617 | vt 0.616436 0.704299 618 | vt 0.616725 0.703288 619 | vt 0.601966 0.734935 620 | vt 0.505561 0.596385 621 | vt 1.000000 1.000000 622 | vt 0.500000 1.000000 623 | vt 0.191208 0.024801 624 | vt 1.000000 0.024538 625 | vt 0.500000 0.999755 626 | vt 0.000000 0.999892 627 | vt 0.000000 0.020529 628 | vt 0.755839 0.022522 629 | vt 0.500000 0.999901 630 | vt 0.000000 1.000473 631 | vt 0.000000 0.018464 632 | vt 0.699530 0.023342 633 | vt 0.000000 0.999983 634 | vt 0.000000 0.020831 635 | vt 0.691990 0.025792 636 | vt 0.754218 0.026948 637 | vt 0.000000 0.019023 638 | vt 0.744369 0.024296 639 | vt 0.500000 1.000138 640 | vt 1.000000 0.020539 641 | vt 0.302325 0.022311 642 | vt 0.824231 0.023803 643 | vt 0.500000 1.001321 644 | vt 0.000000 0.023442 645 | vt 1.000000 1.000335 646 | vt 0.500000 0.998725 647 | vt 0.213607 0.024146 648 | vt 1.000000 0.018505 649 | vt 0.787107 0.023790 650 | vt 0.903664 0.357122 651 | vt 0.000000 1.000555 652 | vt 0.000000 0.999761 653 | vt 0.743524 0.024244 654 | vt 0.000000 1.000764 655 | vt 0.000000 0.019738 656 | vt 0.745989 0.022873 657 | vt 0.259121 0.025089 658 | vt 1.000000 0.018929 659 | vt 1.000000 1.000057 660 | vt 0.425310 0.021896 661 | vt 0.500000 0.999651 662 | vt 0.000000 1.000057 663 | vt 0.500000 0.999288 664 | vt 0.664759 0.023354 665 | vt 0.740253 0.311539 666 | vt 0.250775 0.254891 667 | vt 0.580034 0.500986 668 | vt 0.534890 0.776369 669 | vt 0.529823 0.716111 670 | vt 0.491440 0.774646 671 | vt 0.750000 0.250000 672 | vt 0.369337 0.460442 673 | vt 0.311657 0.410843 674 | vt 0.745730 0.273535 675 | vt 0.504755 0.776343 676 | vt 0.691579 0.500993 677 | vt 0.717651 0.501026 678 | vt 0.631640 0.632519 679 | vt 0.000000 0.017869 680 | vt 0.000000 0.020300 681 | vt 0.352128 0.446780 682 | vt 0.658387 0.440813 683 | vt 0.439252 0.490378 684 | vt 0.674663 0.500964 685 | vt 1.000000 1.000541 686 | vt 1.000000 0.999892 687 | vt 0.500000 0.999160 688 | vt 0.295875 0.023718 689 | vt 1.000000 0.999174 690 | vt 0.500000 1.000713 691 | vt 0.304168 0.021543 692 | vt 1.000000 0.998910 693 | vt 0.000000 1.000382 694 | vt 0.511527 0.022757 695 | vt 1.000000 1.000382 696 | vt 0.569399 0.023711 697 | vt 0.500000 0.999357 698 | vt 1.000000 1.000555 699 | vt 0.000000 1.000335 700 | vt 0.000000 0.999085 701 | vt 1.000000 0.999085 702 | vt 1.000000 0.999761 703 | vt 1.000000 1.000764 704 | vt 0.000000 0.999174 705 | vt 0.000000 0.998910 706 | vn -0.930100 0.308900 -0.198800 707 | vn -0.891400 0.438000 -0.116900 708 | vn -0.920000 0.390700 0.031800 709 | vn 0.796600 -0.593000 0.117700 710 | vn 0.562000 -0.625900 0.540700 711 | vn 0.361200 -0.623200 0.693600 712 | vn -0.005900 -0.555000 0.831900 713 | vn 0.342700 -0.932800 0.111900 714 | vn -0.218700 0.072700 0.973100 715 | vn -0.907600 -0.412900 0.076200 716 | vn -0.225200 -0.971900 -0.067900 717 | vn -0.855400 0.486300 -0.178400 718 | vn -0.824100 0.559500 0.088100 719 | vn 0.215100 -0.536400 0.816100 720 | vn 0.229100 0.229000 0.946100 721 | vn 0.487600 -0.619500 0.615200 722 | vn -0.781000 0.515700 0.352200 723 | vn -0.452200 0.863700 0.222400 724 | vn 0.217500 -0.950200 0.223000 725 | vn 0.687000 -0.725800 0.036400 726 | vn 0.177500 -0.983500 -0.036200 727 | vn 0.850900 -0.340500 0.400000 728 | vn -0.273900 0.441300 0.854500 729 | vn -0.033100 0.844000 0.535300 730 | vn -0.352900 0.555300 0.753100 731 | vn -0.264000 0.938300 0.223600 732 | vn -0.029000 0.918800 0.393700 733 | vn -0.464800 -0.737900 -0.489300 734 | vn -0.595400 -0.731900 -0.331400 735 | vn -0.195900 -0.612100 0.766100 736 | vn -0.478600 -0.549800 0.684500 737 | vn 0.121300 -0.964300 0.235600 738 | vn -0.784100 -0.558400 0.270900 739 | vn -0.827200 -0.548200 -0.123400 740 | vn -0.128700 -0.985900 -0.106500 741 | vn -0.160800 -0.984100 0.075300 742 | vn 0.477400 -0.732800 -0.484900 743 | vn 0.243600 -0.742500 -0.624000 744 | vn -0.012100 -0.967600 0.252200 745 | vn -0.470100 -0.838200 0.276500 746 | vn -0.203200 -0.572200 -0.794600 747 | vn 0.257300 -0.905000 0.338800 748 | vn 0.093500 -0.710000 -0.698000 749 | vn 0.060500 -0.164000 -0.984600 750 | vn -0.191600 -0.129100 -0.972900 751 | vn 0.584600 -0.803400 -0.113500 752 | vn -0.413100 -0.909200 -0.052000 753 | vn -0.362000 -0.910400 0.200200 754 | vn -0.838400 0.040300 -0.543600 755 | vn -0.533100 0.192600 -0.823800 756 | vn 0.500500 0.143500 -0.853800 757 | vn -0.001400 -0.978100 0.208300 758 | vn -0.102700 -0.994000 0.037400 759 | vn 0.591600 -0.735000 -0.331300 760 | vn 0.809500 -0.215600 -0.546100 761 | vn -0.255000 -0.923700 0.286000 762 | vn -0.627800 -0.692100 0.356200 763 | vn 0.940300 -0.231700 -0.249400 764 | vn 0.647600 -0.760200 -0.051700 765 | vn 0.970700 0.089300 0.223200 766 | vn 0.452700 0.867500 0.206200 767 | vn 0.288900 0.869300 0.401100 768 | vn 0.206500 0.878500 0.430900 769 | vn 0.700400 0.574000 0.424300 770 | vn 0.448600 -0.856100 0.256500 771 | vn -0.689500 -0.587300 0.424000 772 | vn -0.076600 -0.741200 0.666900 773 | vn -0.317800 -0.690000 0.650300 774 | vn -0.525700 -0.838400 -0.144000 775 | vn -0.973700 -0.218400 -0.064700 776 | vn -0.996200 -0.030800 -0.081600 777 | vn -0.713800 -0.601700 -0.358400 778 | vn -0.962400 0.238900 -0.129000 779 | vn 0.128000 0.860900 0.492500 780 | vn -0.491100 0.868200 -0.070500 781 | vn 0.820700 0.320000 0.473400 782 | vn 0.930500 0.293100 0.219800 783 | vn 0.856100 0.456600 0.242300 784 | vn -0.565100 0.743400 0.357800 785 | vn -0.566500 0.434400 0.700200 786 | vn -0.693400 0.694600 0.191600 787 | vn -0.783100 0.349600 0.514400 788 | vn -0.864400 0.434900 0.252100 789 | vn -0.911600 0.315300 0.263600 790 | vn 0.506100 0.293200 0.811100 791 | vn 0.684800 0.288700 0.669100 792 | vn 0.835600 0.388400 0.388500 793 | vn 0.836800 -0.360900 0.411800 794 | vn 0.690300 -0.564700 0.452400 795 | vn -0.794200 0.598900 -0.103200 796 | vn 0.611600 0.309400 0.728100 797 | vn 0.476700 -0.863700 0.163500 798 | vn 0.286200 0.951800 0.110400 799 | vn -0.399700 0.916200 -0.029400 800 | vn -0.427500 0.899200 -0.093300 801 | vn -0.712100 0.670600 0.208000 802 | vn -0.527200 0.836300 -0.150900 803 | vn 0.549400 -0.817900 0.170800 804 | vn -0.439600 0.894800 -0.078100 805 | vn 0.028700 -0.656800 0.753500 806 | vn 0.374300 -0.923100 0.087600 807 | vn -0.503000 -0.850100 -0.155600 808 | vn -0.321400 -0.925100 -0.202200 809 | vn -0.895000 0.137100 -0.424400 810 | vn -0.751800 0.604800 -0.262800 811 | vn 0.089900 0.988500 0.121400 812 | vn -0.299100 0.818000 0.491300 813 | vn -0.364300 0.927700 -0.081400 814 | vn 0.927300 0.258400 0.270900 815 | vn 0.583700 0.773700 0.246300 816 | vn 0.939100 0.045800 0.340600 817 | vn 0.939900 0.105600 0.324800 818 | vn 0.202700 0.977500 0.058000 819 | vn 0.258600 0.945000 0.200400 820 | vn 0.344000 -0.935800 0.077900 821 | vn 0.637600 -0.749100 0.180000 822 | vn -0.007500 0.996700 0.080700 823 | vn 0.786400 0.571200 0.235200 824 | vn -0.370900 0.924600 -0.087100 825 | vn -0.529600 0.843400 -0.090800 826 | vn -0.397800 0.912500 -0.095800 827 | vn 0.853100 -0.263100 0.450500 828 | vn 0.612700 -0.766900 0.191000 829 | vn -0.911500 0.352800 -0.211500 830 | vn -0.651500 -0.731300 -0.201900 831 | vn 0.343200 -0.935600 0.082700 832 | vn 0.350700 -0.932700 0.084200 833 | vn 0.369600 -0.927500 0.056100 834 | vn -0.564700 0.813900 -0.136900 835 | vn 0.905400 0.281200 0.318000 836 | vn 0.076100 0.985200 0.153500 837 | vn 0.823500 0.496800 0.274100 838 | vn -0.379200 0.920600 -0.093000 839 | vn 0.908900 -0.290600 0.299100 840 | vn 0.778500 0.565300 0.272700 841 | vn 0.380500 -0.920600 0.087400 842 | vn 0.389200 -0.916400 0.093600 843 | vn 0.923400 0.053700 0.380100 844 | vn 0.719800 0.653700 0.233600 845 | vn 0.938100 -0.136500 0.318300 846 | vn 0.720700 0.629100 0.291300 847 | vn 0.227500 0.973600 0.019700 848 | vn -0.393600 -0.842000 -0.369000 849 | vn -0.966700 0.041200 -0.252600 850 | vn 0.626300 -0.746800 0.223600 851 | vn -0.692300 0.698700 -0.180300 852 | vn -0.655200 0.735900 -0.170800 853 | vn -0.890100 0.331700 -0.312500 854 | vn -0.682200 0.693600 -0.231200 855 | vn -0.890000 0.278300 -0.361100 856 | vn -0.743100 0.645100 -0.177700 857 | vn -0.227100 -0.909300 -0.348800 858 | vn -0.401500 0.911200 -0.091900 859 | vn -0.357200 0.930400 -0.082800 860 | vn 0.421300 -0.900400 0.108200 861 | vn 0.344300 -0.935300 0.081600 862 | vn 0.038400 0.175200 -0.983800 863 | vn 0.361100 0.928400 0.087600 864 | vn 0.489900 0.859700 0.144800 865 | vn 0.604500 -0.713200 -0.354700 866 | vn 0.914200 0.379200 0.143000 867 | vn 0.929100 0.369800 -0.004100 868 | vn -0.610400 -0.433100 -0.663200 869 | vn -0.254000 0.289600 -0.922800 870 | vn -0.306200 -0.546800 -0.779300 871 | vn -0.665100 -0.379000 -0.643400 872 | vn -0.322400 -0.931900 -0.166200 873 | vn -0.266500 0.962600 -0.048300 874 | vn -0.390900 -0.898400 -0.200200 875 | vn 0.707200 0.562500 -0.428300 876 | vn 0.768400 0.427100 -0.476600 877 | vn 0.549600 0.467300 -0.692600 878 | vn 0.543600 0.582700 -0.604200 879 | vn -0.236800 -0.420900 -0.875600 880 | vn 0.177000 -0.523400 -0.833500 881 | vn -0.612600 -0.595500 -0.519700 882 | vn -0.548500 -0.502600 -0.668200 883 | vn -0.152900 -0.537400 -0.829400 884 | vn 0.376900 0.842200 -0.385500 885 | vn 0.046600 0.872800 -0.485800 886 | vn 0.294000 0.548800 -0.782500 887 | vn 0.062400 0.953300 -0.295600 888 | vn -0.696600 0.640200 -0.323800 889 | vn -0.811200 0.450500 -0.372800 890 | vn 0.390800 -0.760200 0.519000 891 | vn -0.413700 -0.866200 -0.280200 892 | vn -0.152500 -0.913300 -0.377600 893 | vn 0.263200 -0.743400 0.614900 894 | vn 0.010200 -0.956600 -0.291300 895 | vn -0.278700 -0.944300 -0.174800 896 | vn 0.106700 -0.981800 -0.157100 897 | vn 0.094400 -0.992200 -0.081200 898 | vn 0.834900 -0.540800 -0.102700 899 | vn 0.229400 -0.961700 -0.150200 900 | vn -0.015000 0.004700 0.999900 901 | vn 0.266400 -0.174300 0.948000 902 | vn 0.113000 -0.625400 0.772100 903 | vn -0.569600 -0.330000 -0.752700 904 | vn -0.124700 -0.871000 -0.475100 905 | vn 0.161700 -0.871100 -0.463800 906 | vn -0.150700 -0.735900 0.660100 907 | vn 0.318800 -0.946300 -0.053600 908 | vn -0.701900 -0.459000 0.544700 909 | vn -0.323400 -0.789300 0.522000 910 | vn -0.541400 -0.823000 0.172000 911 | vn -0.568600 -0.816500 -0.100600 912 | vn -0.436600 -0.896700 -0.073100 913 | vn 0.778900 0.012000 0.627000 914 | vn 0.271300 0.886000 -0.376100 915 | vn 0.409800 0.883000 -0.228900 916 | vn 0.529100 -0.032400 0.847900 917 | vn -0.358000 -0.023700 0.933400 918 | vn 0.324600 -0.883500 -0.337700 919 | vn -0.632300 -0.006500 0.774700 920 | vn 0.114600 -0.987900 -0.104100 921 | vn 0.392600 -0.914300 -0.100000 922 | vn -0.964500 0.057200 0.257600 923 | vn -0.836700 -0.061800 0.544200 924 | vn -0.983600 -0.088100 0.157300 925 | vn -0.976200 0.050600 -0.210900 926 | vn -0.097800 -0.575900 -0.811700 927 | vn 0.477200 -0.544600 -0.689700 928 | vn -0.334100 0.883800 -0.327500 929 | vn 0.572200 -0.625500 -0.530400 930 | vn 0.083100 -0.697000 -0.712200 931 | vn 0.355000 -0.630000 -0.690700 932 | vn 0.695700 -0.668400 -0.263200 933 | vn 0.961900 0.249000 0.112600 934 | vn 0.792200 -0.547900 -0.268800 935 | vn -0.122900 0.881800 -0.455400 936 | vn -0.182900 0.925000 -0.333100 937 | vn -0.815700 0.323600 -0.479500 938 | vn 0.839400 0.500500 -0.212000 939 | vn -0.864600 -0.425600 -0.267100 940 | vn -0.655800 0.345500 -0.671200 941 | vn 0.697300 0.668600 -0.258400 942 | vn 0.465700 0.801700 -0.374700 943 | vn 0.907500 0.341500 -0.244400 944 | vn 0.301300 -0.947600 0.106200 945 | vn 0.922900 0.198300 0.330000 946 | vn 0.840000 0.539900 0.054100 947 | vn 0.779100 0.600200 0.181000 948 | vn 0.888600 0.151000 0.433200 949 | vn 0.770900 0.558500 0.306200 950 | vn 0.764500 0.641400 -0.064500 951 | vn 0.372100 0.919700 0.125200 952 | vn -0.323700 -0.932800 -0.158500 953 | vn 0.356900 0.924500 0.134000 954 | vn -0.367100 -0.919400 -0.140900 955 | vn 0.502000 -0.858600 0.103900 956 | vn 0.330100 -0.931600 0.151800 957 | vn 0.545300 0.811000 0.212000 958 | vn -0.340200 -0.930900 -0.133200 959 | vn -0.603100 -0.772900 -0.197400 960 | vn 0.376900 0.915900 0.138000 961 | vn 0.357600 0.924000 0.135200 962 | vn -0.953700 0.075600 -0.291000 963 | vn -0.806500 0.318200 -0.498300 964 | vn -0.593600 0.778900 -0.202500 965 | vn -0.922900 0.014100 -0.384800 966 | vn -0.254000 0.967000 0.017800 967 | vn -0.645900 -0.726700 -0.234100 968 | vn -0.110200 0.989000 -0.098400 969 | vn -0.783800 0.588500 -0.198300 970 | vn 0.353500 0.925400 0.137000 971 | vn 0.346300 0.929900 0.124000 972 | vn 0.390800 0.908400 0.148500 973 | vn 0.386600 0.909400 0.153400 974 | vn -0.348300 -0.927200 -0.137900 975 | vn -0.399600 -0.904100 -0.151200 976 | vn -0.695700 -0.678600 -0.235700 977 | vn 0.923000 0.307000 0.232100 978 | vn 0.689000 -0.705100 0.167700 979 | vn -0.331000 -0.933900 -0.135300 980 | vn 0.558200 0.809100 0.183900 981 | vn -0.898800 0.304400 -0.315300 982 | vn -0.159000 0.986100 0.047500 983 | vn -0.324300 0.929400 -0.176000 984 | vn -0.820300 0.515600 -0.247600 985 | vn -0.916200 -0.210900 -0.340600 986 | vn -0.923200 0.086900 -0.374400 987 | vn -0.385300 -0.911600 -0.142900 988 | vn -0.392300 -0.907600 -0.149600 989 | vn -0.262900 -0.953900 -0.144900 990 | vn -0.667200 -0.697500 -0.261500 991 | vn -0.930600 0.228100 -0.286200 992 | vn -0.921300 0.097600 -0.376500 993 | vn 0.388300 -0.865200 0.317200 994 | vn 0.967000 0.034500 0.252500 995 | vn -0.239500 0.970600 0.023300 996 | vn 0.678600 0.701200 0.218800 997 | vn 0.646700 0.732500 0.212700 998 | vn 0.887700 0.319400 0.331600 999 | vn -0.962800 0.000600 -0.270100 1000 | vn -0.770200 0.610200 -0.185600 1001 | vn 0.730500 0.648700 0.213300 1002 | vn 0.669600 0.692600 0.268100 1003 | vn 0.884700 0.275400 0.376000 1004 | vn 0.234100 -0.926200 0.295600 1005 | vn 0.394600 0.907400 0.144700 1006 | vn 0.352300 0.925900 0.136300 1007 | vn -0.336900 -0.931700 -0.135500 1008 | vn 0.000300 0.999500 0.032600 1009 | vn 0.000400 0.999500 0.032900 1010 | vn -0.004300 0.998700 0.051200 1011 | vn -0.005200 0.998400 0.056400 1012 | vn -0.009300 0.998000 0.062100 1013 | vn -0.010300 0.998100 0.060700 1014 | vn -0.283000 0.859200 0.426300 1015 | vn -0.010600 0.999400 0.034400 1016 | vn -0.011000 0.999300 0.034500 1017 | vn -0.000700 0.999400 0.035500 1018 | vn -0.000200 0.999200 0.039000 1019 | vn -0.014700 0.999400 0.029900 1020 | vn -0.014800 0.999400 0.030100 1021 | vn -0.001000 1.000000 0.003500 1022 | vn 0.001900 1.000000 -0.001700 1023 | vn -0.011000 0.999600 0.025800 1024 | vn 0.003100 1.000000 0.000500 1025 | vn 0.002500 1.000000 0.004500 1026 | vn -0.004100 0.999600 0.028800 1027 | vn -0.004100 0.999600 0.028700 1028 | vn -0.003600 0.999500 0.030200 1029 | vn -0.007100 0.999800 0.020600 1030 | vn 0.004300 0.999700 0.022100 1031 | vn 0.003800 0.999800 0.021800 1032 | vn 0.006300 0.999700 0.024100 1033 | vn 0.006500 0.999700 0.024500 1034 | vn 0.480200 0.875900 -0.046700 1035 | vn 0.003700 0.999600 0.029100 1036 | vn 0.004100 0.999600 0.028900 1037 | vn 0.003200 0.999600 0.027500 1038 | vn -0.002400 0.999600 0.029800 1039 | vn -0.002500 0.999600 0.029200 1040 | vn -0.651900 0.519500 0.552400 1041 | vn -0.613700 0.312400 -0.725100 1042 | vn -0.003800 0.999600 0.028900 1043 | vn -0.000900 0.999500 0.033100 1044 | vn -0.005400 0.999500 0.031700 1045 | vn -0.005800 0.999500 0.031500 1046 | vn -0.009100 0.999400 0.032000 1047 | vn -0.009400 0.999500 0.030500 1048 | vn -0.009700 0.999600 0.026800 1049 | vn -0.009300 0.999600 0.028300 1050 | vn -0.005100 0.999800 0.021500 1051 | vn -0.000200 0.999700 0.024000 1052 | usemtl lambert2SG 1053 | s 1 1054 | f 12/1/1 13/2/2 28/3/3 1055 | f 11/4/4 9/5/5 8/6/6 7/7/7 1056 | f 17/8/8 18/9/9 16/10/10 1057 | f 16/10/10 116/11/11 17/8/8 1058 | f 27/12/12 28/3/3 29/13/13 1059 | f 20/14/14 21/15/15 22/16/16 1060 | f 29/13/13 31/17/17 87/18/18 1061 | f 50/19/19 218/20/20 44/21/21 49/22/22 1062 | f 35/23/23 36/24/24 34/25/25 1063 | f 37/26/26 38/27/27 36/24/24 1064 | f 50/19/19 51/28/28 52/29/29 218/20/20 1065 | f 6/30/30 5/31/31 7/7/7 1066 | f 11/4/4 43/32/32 44/21/21 1067 | f 3/33/33 2/34/34 5/31/31 1068 | f 2/34/34 48/35/35 47/36/36 1069 | f 59/37/37 57/38/38 56/39/39 60/40/40 1070 | f 50/19/19 56/39/39 54/41/41 1071 | f 53/42/42 56/39/39 50/19/19 1072 | f 50/19/19 54/41/41 51/28/28 1073 | f 55/43/43 72/44/44 71/45/45 54/41/41 1074 | f 63/46/46 60/40/40 64/47/47 1075 | f 60/40/40 62/48/48 64/47/47 1076 | f 49/22/22 43/32/32 50/19/19 1077 | f 68/49/49 52/29/29 51/28/28 1078 | f 70/50/50 68/49/49 51/28/28 1079 | f 71/45/45 70/50/50 54/41/41 1080 | f 73/51/51 72/44/44 55/43/43 57/38/38 1081 | f 45/52/52 56/39/39 53/42/42 1082 | f 45/52/52 46/53/53 56/39/39 1083 | f 61/54/54 75/55/55 73/51/51 59/37/37 1084 | f 46/53/53 58/56/56 56/39/39 1085 | f 74/57/57 60/40/40 58/56/56 1086 | f 46/53/53 74/57/57 58/56/56 1087 | f 76/58/58 75/55/55 61/54/54 63/46/46 1088 | f 60/40/40 74/57/57 62/48/48 1089 | f 46/53/53 47/36/36 74/57/57 1090 | f 63/46/46 65/59/59 77/60/60 76/58/58 1091 | f 83/61/61 84/62/62 39/63/63 40/64/64 1092 | f 77/60/60 65/59/59 67/65/65 1093 | f 8/66/6 21/15/15 7/67/7 1094 | f 4/68/66 79/69/67 80/70/68 1095 | f 117/71/69 116/11/11 16/10/10 92/72/70 1096 | f 3/73/33 80/70/68 81/74/71 1097 | f 1/75/72 2/76/34 82/77/73 1098 | f 38/27/27 84/62/62 85/78/74 1099 | f 27/12/12 29/13/13 88/79/75 1100 | f 38/27/27 85/78/74 36/24/24 1101 | f 29/13/13 87/18/18 88/79/75 1102 | f 25/80/76 26/81/77 42/82/78 1103 | f 90/83/79 35/23/23 33/84/80 1104 | f 97/85/81 33/84/80 91/86/82 1105 | f 91/86/82 31/17/17 30/87/83 1106 | f 14/88/84 30/87/83 28/3/3 1107 | f 23/89/85 24/90/86 41/91/87 96/92/88 1108 | f 27/12/12 12/1/1 28/3/3 1109 | f 8/66/6 23/89/85 21/15/15 1110 | f 10/93/89 11/94/4 26/81/77 1111 | f 9/95/5 10/93/89 25/80/76 1112 | f 9/95/5 24/90/86 23/89/85 1113 | f 8/66/6 9/95/5 23/89/85 1114 | f 7/67/7 20/14/14 6/96/30 1115 | f 92/72/70 16/10/10 93/97/90 1116 | f 12/1/1 1/75/72 82/77/73 1117 | f 95/98/91 18/9/9 115/99/92 1118 | f 18/9/9 94/100/93 19/101/94 1119 | f 98/102/95 15/103/96 13/2/2 102/104/97 1120 | f 23/89/85 96/92/88 22/16/16 1121 | f 22/16/16 96/92/88 99/105/98 1122 | f 97/85/81 91/86/82 15/103/96 1123 | f 15/103/96 91/86/82 14/88/84 1124 | f 97/85/81 15/103/96 98/102/95 1125 | f 90/83/79 97/85/81 98/102/95 1126 | f 38/27/27 37/26/26 100/106/99 1127 | f 79/69/67 78/107/100 143/108/101 1128 | f 80/70/68 152/109/102 101/110/103 1129 | f 82/77/73 104/111/104 103/112/105 13/2/2 1130 | f 82/77/73 81/74/71 101/110/103 104/111/104 1131 | f 137/113/106 100/106/99 89/114/107 136/115/108 1132 | f 106/116/109 41/91/87 40/64/64 105/117/110 1133 | f 37/26/26 89/114/107 100/106/99 1134 | f 112/118/111 111/119/112 120/120/113 110/121/114 1135 | f 142/122/115 99/105/98 141/123/116 1136 | f 105/117/110 107/124/117 108/125/118 1137 | f 106/116/109 105/117/110 108/125/118 1138 | f 119/126/119 109/127/120 110/121/114 1139 | f 119/126/119 133/128/121 109/127/120 1140 | f 110/121/114 19/101/94 94/100/93 1141 | f 95/98/91 112/118/111 110/121/114 94/100/93 1142 | f 114/129/122 113/130/123 111/119/112 112/118/111 1143 | f 112/118/111 95/98/91 114/129/122 1144 | f 118/131/124 131/132/125 117/71/69 92/72/70 1145 | f 129/133/126 127/134/127 115/99/92 1146 | f 129/133/126 115/99/92 17/8/8 1147 | f 131/132/125 132/135/128 117/71/69 1148 | f 17/8/8 116/11/11 129/133/126 1149 | f 93/97/90 109/127/120 134/104/129 118/131/124 1150 | f 140/136/130 137/113/106 122/137/131 124/125/132 1151 | f 133/128/121 134/104/129 109/127/120 1152 | f 121/138/133 119/126/119 120/120/113 1153 | f 123/139/134 124/125/132 125/125/135 1154 | f 121/138/133 120/120/113 122/137/131 1155 | f 122/137/131 120/120/113 125/125/135 1156 | f 144/140/136 130/141/137 132/135/128 1157 | f 139/116/138 138/142/139 140/136/130 1158 | f 138/142/139 137/113/106 140/136/130 1159 | f 138/142/139 139/116/138 149/116/140 150/125/141 1160 | f 141/123/116 139/116/138 140/136/130 1161 | f 148/143/142 138/142/139 150/125/141 1162 | f 145/132/143 131/132/125 147/111/144 1163 | f 148/143/142 137/113/106 138/142/139 1164 | f 141/123/116 151/144/145 149/116/140 139/116/138 1165 | f 155/112/146 153/145/147 154/111/148 147/111/144 1166 | f 154/111/148 152/109/102 145/132/143 147/111/144 1167 | f 159/112/149 103/112/105 104/111/104 158/111/150 1168 | f 107/124/117 100/106/99 148/143/142 1169 | f 153/145/147 155/112/146 146/112/151 1170 | f 107/124/117 148/143/142 108/125/118 1171 | f 149/116/140 108/125/118 150/125/141 1172 | f 151/144/145 106/116/109 149/116/140 1173 | f 104/111/104 101/110/103 157/132/152 158/111/150 1174 | f 157/132/152 101/110/103 152/109/102 1175 | f 158/111/150 157/132/152 152/109/102 154/111/148 1176 | f 153/145/147 159/112/149 158/111/150 154/111/148 1177 | f 102/104/97 153/145/147 156/146/153 98/102/95 1178 | f 153/145/147 102/104/97 159/112/149 1179 | f 102/104/97 103/112/105 159/112/149 1180 | f 134/104/129 133/128/121 156/146/153 153/145/147 1181 | f 141/123/116 140/136/130 123/139/134 1182 | f 135/147/154 156/146/153 133/128/121 1183 | f 126/148/155 141/123/116 123/139/134 1184 | f 127/134/127 141/123/116 126/148/155 1185 | f 78/107/100 142/122/115 128/149/156 1186 | f 78/107/100 128/149/156 143/108/101 1187 | f 173/150/157 174/151/158 260/152/159 172/153/160 1188 | f 143/108/101 128/149/156 144/140/136 1189 | f 152/109/102 132/135/128 131/132/125 1190 | f 111/119/112 123/139/134 125/125/135 1191 | f 115/99/92 123/139/134 113/130/123 1192 | f 146/112/151 147/111/144 118/131/124 1193 | f 130/141/137 116/11/11 132/135/128 1194 | f 134/104/129 146/112/151 118/131/124 1195 | f 26/154/77 168/155/161 169/156/162 1196 | f 179/157/163 178/158/164 165/159/165 181/160/166 1197 | f 264/161/167 173/150/157 172/153/160 1198 | f 173/150/157 176/162/168 174/151/158 1199 | f 264/161/167 175/163/169 173/150/157 1200 | f 185/164/170 186/165/171 187/166/172 188/167/173 1201 | f 178/158/164 179/157/163 177/168/174 1202 | f 197/169/175 167/170/176 166/171/177 164/172/178 1203 | f 190/173/179 191/174/180 189/175/181 1204 | f 190/173/179 192/176/182 191/174/180 1205 | f 194/177/183 196/178/184 27/179/12 1206 | f 205/180/185 203/181/186 206/182/187 207/183/188 1207 | f 167/170/176 199/184/189 198/185/190 1208 | f 167/170/176 197/169/175 199/184/189 1209 | f 197/169/175 200/186/191 199/184/189 1210 | f 201/187/192 197/169/175 160/188/193 1211 | f 202/189/194 11/190/4 44/191/21 1212 | f 160/188/193 11/190/4 202/189/194 1213 | f 222/192/195 221/193/196 207/183/188 208/194/197 1214 | f 204/195/198 206/182/187 203/181/186 1215 | f 223/196/199 209/197/200 206/182/187 1216 | f 206/182/187 209/197/200 210/198/201 1217 | f 209/197/200 216/199/202 213/200/203 1218 | f 209/197/200 213/200/203 211/201/204 1219 | f 216/199/202 215/202/205 213/200/203 1220 | f 216/199/202 217/203/206 215/202/205 1221 | f 216/199/202 218/204/20 217/203/206 1222 | f 66/205/207 198/185/190 203/181/186 1223 | f 204/195/198 199/184/189 206/182/187 1224 | f 219/206/208 67/207/65 205/180/185 1225 | f 185/164/170 188/167/173 237/208/209 238/209/210 1226 | f 221/193/196 220/210/211 207/183/188 1227 | f 200/186/191 223/196/199 199/184/189 1228 | f 224/211/212 222/192/195 210/198/201 1229 | f 200/186/191 201/187/192 225/212/213 1230 | f 200/186/191 225/212/213 223/196/199 1231 | f 226/213/214 224/211/212 211/201/204 1232 | f 201/187/192 202/189/194 212/214/215 1233 | f 201/187/192 212/214/215 225/212/213 1234 | f 202/189/194 214/215/216 212/214/215 1235 | f 228/216/217 227/217/218 215/202/205 1236 | f 227/217/218 213/200/203 215/202/205 1237 | f 229/218/219 228/216/217 217/203/206 1238 | f 228/216/217 215/202/205 217/203/206 1239 | f 69/219/220 229/218/219 217/203/206 1240 | f 178/158/164 164/220/178 165/159/165 1241 | f 230/221/221 163/222/222 197/223/175 1242 | f 194/177/183 88/224/75 235/225/223 1243 | f 162/226/224 231/227/225 232/70/226 1244 | f 160/228/193 233/229/227 234/230/228 1245 | f 162/226/224 232/70/226 161/231/229 1246 | f 11/232/4 160/228/193 234/230/228 1247 | f 161/231/229 232/70/226 233/229/227 1248 | f 192/176/182 235/225/223 236/233/230 1249 | f 193/234/231 194/177/183 235/225/223 1250 | f 196/178/184 182/235/232 12/236/1 27/179/12 1251 | f 192/176/182 236/233/230 191/174/180 1252 | f 189/175/181 237/208/209 188/167/173 1253 | f 237/208/209 189/175/181 191/174/180 1254 | f 185/164/170 238/209/210 184/237/233 1255 | f 245/238/234 246/239/235 173/150/157 175/163/169 1256 | f 241/240/236 187/166/172 186/165/171 1257 | f 240/241/237 189/175/181 187/166/172 1258 | f 184/237/233 186/165/171 185/164/170 1259 | f 170/242/238 184/237/233 169/156/162 1260 | f 242/243/239 172/153/160 243/72/240 1261 | f 42/244/78 169/156/162 183/245/241 1262 | f 197/223/175 164/220/178 177/168/174 1263 | f 243/72/240 172/153/160 244/246/242 1264 | f 26/154/77 234/230/228 168/155/161 1265 | f 234/230/228 253/111/243 252/112/244 168/155/161 1266 | f 171/247/245 186/165/171 170/242/238 1267 | f 241/240/236 171/247/245 247/248/246 1268 | f 230/221/221 177/168/174 248/249/247 1269 | f 177/168/174 179/157/163 248/249/247 1270 | f 190/173/179 240/241/237 249/250/248 1271 | f 192/176/182 249/250/248 193/234/231 1272 | f 231/227/225 230/221/221 289/108/249 1273 | f 232/70/226 297/109/250 250/251/251 1274 | f 171/247/245 168/155/161 251/104/252 1275 | f 288/122/253 248/249/247 181/160/166 255/252/254 1276 | f 234/230/228 250/251/251 253/111/243 1277 | f 254/253/255 249/250/248 283/115/256 1278 | f 257/116/257 195/254/258 194/177/183 256/255/259 1279 | f 176/162/168 246/239/235 263/256/260 262/257/261 1280 | f 288/122/253 255/252/254 287/258/262 1281 | f 256/255/259 258/259/263 259/125/264 1282 | f 257/116/257 256/255/259 259/125/264 1283 | f 181/160/166 195/254/258 255/252/254 1284 | f 266/126/265 260/152/159 261/260/266 1285 | f 266/126/265 280/128/267 260/152/159 1286 | f 261/260/266 174/151/158 176/162/168 1287 | f 262/257/261 267/261/268 261/260/266 1288 | f 245/238/234 175/163/169 274/134/269 272/262/270 1289 | f 245/238/234 272/262/270 273/130/271 1290 | f 263/256/260 246/239/235 245/238/234 1291 | f 245/238/234 273/130/271 263/256/260 1292 | f 265/131/272 278/132/273 242/243/239 243/72/240 1293 | f 276/133/274 274/134/269 175/163/169 1294 | f 260/152/159 281/104/275 265/131/272 244/246/242 1295 | f 286/263/276 284/264/277 268/124/278 270/125/279 1296 | f 280/128/267 281/104/275 260/152/159 1297 | f 273/130/271 269/116/280 271/265/281 1298 | f 290/140/282 277/141/283 279/135/284 1299 | f 287/258/262 296/266/285 295/267/286 285/268/287 1300 | f 287/258/262 285/268/287 286/263/276 1301 | f 287/258/262 286/263/276 269/116/280 1302 | f 291/132/288 278/132/273 293/111/289 1303 | f 294/269/290 284/264/277 295/267/286 1304 | f 300/112/291 298/270/292 299/111/293 293/111/289 1305 | f 299/111/293 297/109/250 291/132/288 293/111/289 1306 | f 303/116/294 257/116/257 259/125/264 302/125/295 1307 | f 258/259/263 254/253/255 294/269/290 1308 | f 298/270/292 300/112/291 292/112/296 1309 | f 259/125/264 258/259/263 294/269/290 302/125/295 1310 | f 296/266/285 255/252/254 257/116/257 303/116/294 1311 | f 303/116/294 302/125/295 295/267/286 1312 | f 306/112/297 252/112/244 253/111/243 305/111/298 1313 | f 253/111/243 250/251/251 304/132/299 305/111/298 1314 | f 296/266/285 303/116/294 295/267/286 1315 | f 287/258/262 255/252/254 296/266/285 1316 | f 304/132/299 250/251/251 297/109/250 1317 | f 305/111/298 304/132/299 297/109/250 299/111/293 1318 | f 298/270/292 306/112/297 305/111/298 299/111/293 1319 | f 251/104/252 298/270/292 301/146/300 171/247/245 1320 | f 298/270/292 251/104/252 306/112/297 1321 | f 251/104/252 252/112/244 306/112/297 1322 | f 281/104/275 280/128/267 301/146/300 298/270/292 1323 | f 282/147/301 301/146/300 280/128/267 1324 | f 274/134/269 287/258/262 272/262/270 1325 | f 230/221/221 288/122/253 275/149/302 1326 | f 230/221/221 275/149/302 289/108/249 1327 | f 263/256/260 271/265/281 268/124/278 262/257/261 1328 | f 289/108/249 275/149/302 290/140/282 1329 | f 297/109/250 279/135/284 278/132/273 1330 | f 307/271/303 308/272/304 84/273/62 83/274/61 1331 | f 279/135/284 242/243/239 278/132/273 1332 | f 273/130/271 271/265/281 263/256/260 1333 | f 292/112/296 293/111/289 265/131/272 1334 | f 277/141/283 264/161/167 242/243/239 1335 | f 281/104/275 292/112/296 265/131/272 1336 | f 312/275/305 313/276/306 36/277/24 85/278/74 1337 | f 314/279/307 315/280/308 86/281/309 36/282/24 1338 | f 318/272/310 319/283/311 87/284/18 86/285/309 1339 | f 84/286/62 311/272/312 310/280/313 1340 | f 323/283/314 88/287/75 87/288/18 322/289/315 1341 | f 236/290/230 333/271/316 334/272/317 191/291/180 1342 | f 88/292/75 326/293/318 235/294/223 1343 | f 335/295/319 336/296/320 237/297/209 191/298/180 1344 | f 235/299/223 328/272/321 329/283/322 1345 | f 235/300/223 331/272/323 330/301/324 1346 | f 339/302/325 238/284/210 237/303/209 338/272/326 1347 | f 340/289/327 341/304/328 239/305/329 238/306/210 1348 | f 344/275/330 83/307/61 239/308/329 343/309/331 1349 | f 239/310/329 342/311/332 343/312/331 1350 | f 346/313/333 83/314/61 345/271/334 1351 | f 11/4/4 10/315/89 9/5/5 1352 | f 11/4/4 7/7/7 43/32/32 1353 | f 43/32/32 7/7/7 45/52/52 1354 | f 7/7/7 5/31/31 45/52/52 1355 | f 45/52/52 5/31/31 46/53/53 1356 | f 46/53/53 5/31/31 47/36/36 1357 | f 5/31/31 2/34/34 47/36/36 1358 | f 54/41/41 56/39/39 55/43/43 1359 | f 18/9/9 19/101/94 109/127/120 16/10/10 1360 | f 65/59/59 64/47/47 67/65/65 1361 | f 74/57/57 47/36/36 62/48/48 1362 | f 62/48/48 47/36/36 64/47/47 1363 | f 64/47/47 48/35/35 66/316/207 1364 | f 78/107/100 5/317/31 6/96/30 1365 | f 3/73/33 4/68/66 80/70/68 1366 | f 34/25/25 36/24/24 86/318/309 1367 | f 86/318/309 32/319/335 34/25/25 1368 | f 32/319/335 86/318/309 87/18/18 1369 | f 87/18/18 31/17/17 32/319/335 1370 | f 37/26/26 35/23/23 89/114/107 1371 | f 25/80/76 10/93/89 26/81/77 1372 | f 24/90/86 9/95/5 25/80/76 1373 | f 7/67/7 21/15/15 20/14/14 1374 | f 6/96/30 20/14/14 78/107/100 1375 | f 18/9/9 95/98/91 94/100/93 1376 | f 115/99/92 18/9/9 17/8/8 1377 | f 80/70/68 79/69/67 143/108/101 1378 | f 141/123/116 99/105/98 151/144/145 1379 | f 121/138/133 135/147/154 119/126/119 1380 | f 124/125/132 122/137/131 125/125/135 1381 | f 115/99/92 126/148/155 123/139/134 1382 | f 137/113/106 121/138/133 122/137/131 1383 | f 145/132/143 152/109/102 131/132/125 1384 | f 100/106/99 137/113/106 148/143/142 1385 | f 127/134/127 142/122/115 141/123/116 1386 | f 128/149/156 142/122/115 127/134/127 1387 | f 134/104/129 153/145/147 146/112/151 1388 | f 42/244/78 183/245/241 83/320/61 1389 | f 1/29/72 167/170/176 2/321/34 1390 | f 197/169/175 162/322/224 160/188/193 1391 | f 162/322/224 161/323/229 160/188/193 1392 | f 2/321/34 167/170/176 48/324/35 1393 | f 48/324/35 167/170/176 198/185/190 1394 | f 200/186/191 197/169/175 201/187/192 1395 | f 225/212/213 212/214/215 209/197/200 1396 | f 212/214/215 214/215/216 216/199/202 1397 | f 217/203/206 218/204/20 52/65/29 1398 | f 66/205/207 48/324/35 198/185/190 1399 | f 216/199/202 202/189/194 218/204/20 1400 | f 160/228/193 161/231/229 233/229/227 1401 | f 88/224/75 194/177/183 27/179/12 1402 | f 184/237/233 238/209/210 239/325/329 1403 | f 170/242/238 186/165/171 184/237/233 1404 | f 167/326/176 1/327/72 182/235/232 1405 | f 182/235/232 1/327/72 12/236/1 1406 | f 180/328/336 167/326/176 182/235/232 1407 | f 164/220/178 178/158/164 177/168/174 1408 | f 197/223/175 177/168/174 230/221/221 1409 | f 171/247/245 241/240/236 186/165/171 1410 | f 192/176/182 190/173/179 249/250/248 1411 | f 232/70/226 231/227/225 289/108/249 1412 | f 267/261/268 282/147/301 266/126/265 1413 | f 284/264/277 267/261/268 268/124/278 1414 | f 291/132/288 297/109/250 278/132/273 1415 | f 274/134/269 288/122/253 287/258/262 1416 | f 275/149/302 288/122/253 274/134/269 1417 | f 281/104/275 298/270/292 292/112/296 1418 | f 268/124/278 267/261/268 262/257/261 1419 | f 85/329/74 84/286/62 310/280/313 1420 | f 235/294/223 326/293/318 327/283/337 1421 | f 236/330/230 235/300/223 330/301/324 1422 | f 83/307/61 344/275/330 345/283/334 1423 | f 83/314/61 346/313/333 307/283/303 1424 | f 12/1/1 82/77/73 13/2/2 1425 | f 13/2/2 14/88/84 28/3/3 1426 | f 22/16/16 21/15/15 23/89/85 1427 | f 28/3/3 30/87/83 29/13/13 1428 | f 35/23/23 37/26/26 36/24/24 1429 | f 83/61/61 40/64/64 42/82/78 1430 | f 3/33/33 5/31/31 4/331/66 1431 | f 57/38/38 55/43/43 56/39/39 1432 | f 56/39/39 58/56/56 60/40/40 1433 | f 61/54/54 59/37/37 60/40/40 1434 | f 68/49/49 69/321/220 52/29/29 1435 | f 44/21/21 43/32/32 49/22/22 1436 | f 54/41/41 70/50/50 51/28/28 1437 | f 43/32/32 45/52/52 53/42/42 1438 | f 47/36/36 48/35/35 64/47/47 1439 | f 79/69/67 5/317/31 78/107/100 1440 | f 82/77/73 2/76/34 81/74/71 1441 | f 38/27/27 39/63/63 84/62/62 1442 | f 40/64/64 25/80/76 42/82/78 1443 | f 91/86/82 33/84/80 32/319/335 1444 | f 14/88/84 91/86/82 30/87/83 1445 | f 41/91/87 24/90/86 25/80/76 1446 | f 99/105/98 96/92/88 41/91/87 1447 | f 39/63/63 38/27/27 100/106/99 1448 | f 156/146/153 90/83/79 98/102/95 1449 | f 152/109/102 80/70/68 143/108/101 1450 | f 136/115/108 90/83/79 156/146/153 1451 | f 142/122/115 78/107/100 20/14/14 1452 | f 142/122/115 20/14/14 99/105/98 1453 | f 40/64/64 39/63/63 105/117/110 1454 | f 39/63/63 100/106/99 107/124/117 1455 | f 110/121/114 109/127/120 19/101/94 1456 | f 130/141/137 128/149/156 116/11/11 1457 | f 132/135/128 116/11/11 117/71/69 1458 | f 127/134/127 129/133/126 128/149/156 1459 | f 152/109/102 143/108/101 144/140/136 1460 | f 146/112/151 155/112/146 147/111/144 1461 | f 135/147/154 136/115/108 156/146/153 1462 | f 121/138/133 136/115/108 135/147/154 1463 | f 137/113/106 136/115/108 121/138/133 1464 | f 123/139/134 140/136/130 124/125/132 1465 | f 125/125/135 120/120/113 111/119/112 1466 | f 168/155/161 170/242/238 169/156/162 1467 | f 164/172/178 166/171/177 165/332/165 1468 | f 162/322/224 197/169/175 163/333/222 1469 | f 202/189/194 201/187/192 160/188/193 1470 | f 64/321/47 203/181/186 67/207/65 1471 | f 67/207/65 203/181/186 205/180/185 1472 | f 207/183/188 206/182/187 208/194/197 1473 | f 208/194/197 206/182/187 210/198/201 1474 | f 210/198/201 209/197/200 211/201/204 1475 | f 209/197/200 212/214/215 216/199/202 1476 | f 198/185/190 199/184/189 204/195/198 1477 | f 207/183/188 220/210/211 205/180/185 1478 | f 206/182/187 199/184/189 223/196/199 1479 | f 210/198/201 222/192/195 208/194/197 1480 | f 223/196/199 225/212/213 209/197/200 1481 | f 211/201/204 224/211/212 210/198/201 1482 | f 227/217/218 226/213/214 213/200/203 1483 | f 213/200/203 226/213/214 211/201/204 1484 | f 216/199/202 214/215/216 202/189/194 1485 | f 202/189/194 44/191/21 218/204/20 1486 | f 52/65/29 69/219/220 217/203/206 1487 | f 180/328/336 166/334/177 167/326/176 1488 | f 231/227/225 163/222/222 230/221/221 1489 | f 192/176/182 193/234/231 235/225/223 1490 | f 183/245/241 184/237/233 239/325/329 1491 | f 182/235/232 196/178/184 195/254/258 1492 | f 189/175/181 240/241/237 190/173/179 1493 | f 241/240/236 240/241/237 187/166/172 1494 | f 169/156/162 184/237/233 183/245/241 1495 | f 42/244/78 26/154/77 169/156/162 1496 | f 180/328/336 182/235/232 195/254/258 1497 | f 240/241/237 241/240/236 247/248/246 1498 | f 301/146/300 240/241/237 247/248/246 1499 | f 297/109/250 232/70/226 289/108/249 1500 | f 283/115/256 240/241/237 301/146/300 1501 | f 288/122/253 230/221/221 248/249/247 1502 | f 194/177/183 193/234/231 256/255/259 1503 | f 193/234/231 254/253/255 258/259/263 1504 | f 261/260/266 260/152/159 174/151/158 1505 | f 262/257/261 261/260/266 176/162/168 1506 | f 277/141/283 275/149/302 264/161/167 1507 | f 274/134/269 276/133/274 275/149/302 1508 | f 284/264/277 254/253/255 267/261/268 1509 | f 284/264/277 294/269/290 254/253/255 1510 | f 295/267/286 284/264/277 285/268/287 1511 | f 297/109/250 289/108/249 290/140/282 1512 | f 292/112/296 300/112/291 293/111/289 1513 | f 282/147/301 283/115/256 301/146/300 1514 | f 267/261/268 283/115/256 282/147/301 1515 | f 254/253/255 283/115/256 267/261/268 1516 | f 273/130/271 287/258/262 269/116/280 1517 | f 270/125/279 268/124/278 271/265/281 1518 | f 242/243/239 279/135/284 277/141/283 1519 | f 309/283/338 84/273/62 308/272/304 1520 | f 311/272/312 84/286/62 309/271/338 1521 | f 312/275/305 85/278/74 310/335/313 1522 | f 313/336/306 314/279/307 36/282/24 1523 | f 315/335/308 316/337/339 86/338/309 1524 | f 317/339/340 318/272/310 86/285/309 1525 | f 319/271/311 320/340/341 87/341/18 1526 | f 321/342/342 322/289/315 87/288/18 1527 | f 325/343/343 88/344/75 324/311/344 1528 | f 325/345/343 326/293/318 88/292/75 1529 | f 328/272/321 235/299/223 327/271/337 1530 | f 333/283/316 236/346/230 332/347/345 1531 | f 332/347/345 236/346/230 330/348/324 1532 | f 335/349/319 191/291/180 334/272/317 1533 | f 337/350/346 237/297/209 336/296/320 1534 | f 337/351/346 338/272/326 237/303/209 1535 | f 340/289/327 238/306/210 339/352/325 1536 | f 341/353/328 342/311/332 239/310/329 1537 | f 29/13/13 30/87/83 31/17/17 1538 | f 34/25/25 32/319/335 33/84/80 1539 | f 33/84/80 35/23/23 34/25/25 1540 | f 63/46/46 61/54/54 60/40/40 1541 | f 65/59/59 63/46/46 64/47/47 1542 | f 50/19/19 43/32/32 53/42/42 1543 | f 59/37/37 73/51/51 57/38/38 1544 | f 4/68/66 5/317/31 79/69/67 1545 | f 2/76/34 3/73/33 81/74/71 1546 | f 41/91/87 25/80/76 40/64/64 1547 | f 90/83/79 89/114/107 35/23/23 1548 | f 97/85/81 90/83/79 33/84/80 1549 | f 31/17/17 91/86/82 32/319/335 1550 | f 93/97/90 16/10/10 109/127/120 1551 | f 114/129/122 95/98/91 115/99/92 1552 | f 20/14/14 22/16/16 99/105/98 1553 | f 89/114/107 90/83/79 136/115/108 1554 | f 81/74/71 80/70/68 101/110/103 1555 | f 102/104/97 13/2/2 103/112/105 1556 | f 105/117/110 39/63/63 107/124/117 1557 | f 106/116/109 99/105/98 41/91/87 1558 | f 151/144/145 99/105/98 106/116/109 1559 | f 120/120/113 119/126/119 110/121/114 1560 | f 114/129/122 115/99/92 113/130/123 1561 | f 116/11/11 128/149/156 129/133/126 1562 | f 93/97/90 118/131/124 92/72/70 1563 | f 115/99/92 127/134/127 126/148/155 1564 | f 144/140/136 128/149/156 130/141/137 1565 | f 149/116/140 106/116/109 108/125/118 1566 | f 108/125/118 148/143/142 150/125/141 1567 | f 132/135/128 152/109/102 144/140/136 1568 | f 113/130/123 123/139/134 111/119/112 1569 | f 118/131/124 147/111/144 131/132/125 1570 | f 119/126/119 135/147/154 133/128/121 1571 | f 168/155/161 171/247/245 170/242/238 1572 | f 165/159/165 180/328/336 181/160/166 1573 | f 188/167/173 187/166/172 189/175/181 1574 | f 195/254/258 196/178/184 194/177/183 1575 | f 64/321/47 66/205/207 203/181/186 1576 | f 67/207/65 219/206/208 77/20/60 1577 | f 203/181/186 198/185/190 204/195/198 1578 | f 220/210/211 219/206/208 205/180/185 1579 | f 13/2/2 15/103/96 14/88/84 1580 | f 180/328/336 165/159/165 166/334/177 1581 | f 162/226/224 163/222/222 231/227/225 1582 | f 83/320/61 183/245/241 239/325/329 1583 | f 242/243/239 264/161/167 172/153/160 1584 | f 244/246/242 172/153/160 260/152/159 1585 | f 26/154/77 11/232/4 234/230/228 1586 | f 173/150/157 246/239/235 176/162/168 1587 | f 181/160/166 180/328/336 195/254/258 1588 | f 248/249/247 179/157/163 181/160/166 1589 | f 301/146/300 247/248/246 171/247/245 1590 | f 249/250/248 240/241/237 283/115/256 1591 | f 234/230/228 233/229/227 250/251/251 1592 | f 233/229/227 232/70/226 250/251/251 1593 | f 251/104/252 168/155/161 252/112/244 1594 | f 193/234/231 249/250/248 254/253/255 1595 | f 256/255/259 193/234/231 258/259/263 1596 | f 255/252/254 195/254/258 257/116/257 1597 | f 267/261/268 266/126/265 261/260/266 1598 | f 264/161/167 275/149/302 276/133/274 1599 | f 276/133/274 175/163/169 264/161/167 1600 | f 244/246/242 265/131/272 243/72/240 1601 | f 271/265/281 269/116/280 270/125/279 1602 | f 290/140/282 275/149/302 277/141/283 1603 | f 285/268/287 284/264/277 286/263/276 1604 | f 269/116/280 286/263/276 270/125/279 1605 | f 295/267/286 302/125/295 294/269/290 1606 | f 273/130/271 272/262/270 287/258/262 1607 | f 279/135/284 297/109/250 290/140/282 1608 | f 265/131/272 293/111/289 278/132/273 1609 | f 266/126/265 282/147/301 280/128/267 1610 | f 316/337/339 317/354/340 86/338/309 1611 | f 321/355/342 87/341/18 320/340/341 1612 | f 323/271/314 324/311/344 88/344/75 1613 | f 331/272/323 235/300/223 329/271/322 1614 | -------------------------------------------------------------------------------- /src/models/drone_propeller2.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl lambert2SG 5 | Ns 96.078431 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 0.000000 1.000000 1.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 0.000000 12 | illum 6 13 | -------------------------------------------------------------------------------- /src/models/drone_propeller3.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl lambert2SG 5 | Ns 96.078431 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 0.000000 1.000000 1.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 0.000000 12 | illum 6 13 | -------------------------------------------------------------------------------- /src/models/drone_propeller4.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 1 3 | 4 | newmtl lambert2SG 5 | Ns 96.078431 6 | Ka 0.000000 0.000000 0.000000 7 | Kd 0.000000 1.000000 1.000000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 0.000000 12 | illum 6 13 | -------------------------------------------------------------------------------- /src/models/drone_simple.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'None' 2 | # Material Count: 12 3 | 4 | newmtl Material 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.640000 0.640000 0.640000 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Material.001 15 | Ns 94.117647 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.640000 0.640000 0.640000 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | 24 | newmtl blinn1SG 25 | Ns 94.117647 26 | Ka 0.000000 0.000000 0.000000 27 | Kd 1.000000 0.000000 0.000000 28 | Ks 0.500000 0.500000 0.500000 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.000000 31 | d 0.000000 32 | illum 6 33 | 34 | newmtl blinn2SG 35 | Ns 94.117647 36 | Ka 0.000000 0.000000 0.000000 37 | Kd 1.000000 1.000000 0.000000 38 | Ks 0.500000 0.500000 0.500000 39 | Ke 0.000000 0.000000 0.000000 40 | Ni 1.000000 41 | d 0.000000 42 | illum 6 43 | 44 | newmtl blinn3SG 45 | Ns 94.117647 46 | Ka 0.000000 0.000000 0.000000 47 | Kd 0.000000 1.000000 0.000000 48 | Ks 0.500000 0.500000 0.500000 49 | Ke 0.000000 0.000000 0.000000 50 | Ni 1.000000 51 | d 0.000000 52 | illum 6 53 | 54 | newmtl initialShadingGroup 55 | Ns 94.117647 56 | Ka 0.000000 0.000000 0.000000 57 | Kd 0.500000 0.500000 0.500000 58 | Ks 0.500000 0.500000 0.500000 59 | Ke 0.000000 0.000000 0.000000 60 | Ni 1.000000 61 | d 0.000000 62 | illum 6 63 | 64 | newmtl lambert2SG 65 | Ns 94.117647 66 | Ka 0.000000 0.000000 0.000000 67 | Kd 0.000000 1.000000 1.000000 68 | Ks 0.500000 0.500000 0.500000 69 | Ke 0.000000 0.000000 0.000000 70 | Ni 1.000000 71 | d 0.000000 72 | illum 6 73 | 74 | newmtl lambert3SG 75 | Ns 94.117647 76 | Ka 0.000000 0.000000 0.000000 77 | Kd 0.760000 0.330000 0.000000 78 | Ks 0.500000 0.500000 0.500000 79 | Ke 0.000000 0.000000 0.000000 80 | Ni 1.000000 81 | d 0.000000 82 | illum 6 83 | 84 | newmtl lambert4SG 85 | Ns 94.117647 86 | Ka 0.000000 0.000000 0.000000 87 | Kd 0.000000 0.000000 1.000000 88 | Ks 0.500000 0.500000 0.500000 89 | Ke 0.000000 0.000000 0.000000 90 | Ni 1.000000 91 | d 0.000000 92 | illum 6 93 | 94 | newmtl lambert5SG 95 | Ns 94.117647 96 | Ka 0.000000 0.000000 0.000000 97 | Kd 1.000000 0.000000 1.000000 98 | Ks 0.500000 0.500000 0.500000 99 | Ke 0.000000 0.000000 0.000000 100 | Ni 1.000000 101 | d 0.000000 102 | illum 6 103 | 104 | newmtl lambert6SG 105 | Ns 94.117647 106 | Ka 0.000000 0.000000 0.000000 107 | Kd 0.100000 0.410000 0.210000 108 | Ks 0.500000 0.500000 0.500000 109 | Ke 0.000000 0.000000 0.000000 110 | Ni 1.000000 111 | d 0.000000 112 | illum 6 113 | 114 | newmtl lambert7SG 115 | Ns 94.117647 116 | Ka 0.000000 0.000000 0.000000 117 | Kd 0.000000 0.000000 0.000000 118 | Ks 0.500000 0.500000 0.500000 119 | Ke 0.000000 0.000000 0.000000 120 | Ni 1.000000 121 | d 1.000000 122 | illum 2 123 | -------------------------------------------------------------------------------- /src/objloader.py: -------------------------------------------------------------------------------- 1 | #[MIT license:] 2 | # 3 | # Copyright (c) 2011 Dave Pape 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | import math, types, string 24 | import os 25 | 26 | from OpenGL.GL import * 27 | from OpenGL.GLUT import * 28 | from OpenGL.GLU import * 29 | 30 | class WFObject: 31 | 32 | class wfface: 33 | def __init__(self): 34 | self.points = [] 35 | def draw(self): 36 | glBegin(GL_POLYGON) 37 | for p in self.points: 38 | if 'vn' in p: 39 | glNormal3fv((GLfloat * 3)(*p['vn'])) 40 | if 'vt' in p: 41 | glTexCoord2fv((GLfloat * 2)(*p['vt'])) 42 | glVertex3fv((GLfloat * 3)(*p['v'])) 43 | glEnd() 44 | 45 | 46 | class wfline: 47 | def __init__(self): 48 | self.points = [] 49 | def draw(self): 50 | glBegin(GL_LINE_STRIP) 51 | for p in self.points: 52 | if 'vn' in p: 53 | glNormal3fv((GLfloat * 3)(*p['vn'])) 54 | if 'vt' in p: 55 | glTexCoord2fv((GLfloat * 2)(*p['vt'])) 56 | glVertex3fv((GLfloat * 3)(*p['v'])) 57 | glEnd() 58 | 59 | 60 | class wfpoints: 61 | def __init__(self): 62 | self.points = [] 63 | def draw(self): 64 | glBegin(GL_POINTS) 65 | for p in self.points: 66 | if p.has_key('vn'): 67 | glNormal3fv((GLfloat * 3)(*p['vn'])) 68 | if p.has_key('vt'): 69 | glTexCoord2fv((GLfloat * 2)(*p['vt'])) 70 | glVertex3fv((GLfloat * 3)(*p['v'])) 71 | glEnd() 72 | 73 | 74 | class wfmaterial: 75 | def __init__(self): 76 | self.illum = 2 77 | self.Kd = [1, 1, 1, 1] 78 | self.Ka = [0, 0, 0, 0] 79 | self.Ks = [0, 0, 0, 0] 80 | self.Ns = 0 81 | self.texture = None 82 | def draw(self): 83 | glColor4fv((GLfloat * 4)(*self.Kd)) 84 | glMaterialfv(GL_FRONT, GL_DIFFUSE, (GLfloat * 4)(*self.Kd)) 85 | glMaterialfv(GL_FRONT, GL_AMBIENT, (GLfloat * 4)(*self.Ka)) 86 | if self.illum > 1: 87 | glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat * 4)(*self.Ks)) 88 | glMaterialf(GL_FRONT, GL_SHININESS, self.Ns) 89 | else: 90 | glMaterialfv(GL_FRONT, GL_SPECULAR, (GLfloat * 4)(*[0,0,0,0])) 91 | if self.texture: 92 | glEnable(GL_TEXTURE_2D) 93 | glBindTexture(GL_TEXTURE_2D, self.texture.id) 94 | else: 95 | glDisable(GL_TEXTURE_2D) 96 | 97 | def __init__(self, filename=None, debugmode=True): 98 | self.v = [] 99 | self.vn = [] 100 | self.vt = [] 101 | self.mtl = [] 102 | self.geometry = [] 103 | self.materials = {} 104 | self.displayListInitted = False 105 | self.hasNormals = False 106 | self.debugmode = debugmode 107 | self.r = [] 108 | self.maxR = 0 109 | self.path = None 110 | if filename: 111 | self.loadFile(filename) 112 | 113 | def loadFile(self, filename): 114 | self.fileName = filename 115 | self.path = os.path.dirname(filename) 116 | self.lineNum = 0 117 | for line in open(filename, 'r').readlines(): 118 | self.lineNum += 1 119 | values = line.split() 120 | if len(values) < 1: 121 | continue 122 | elif values[0] == 'v': 123 | self.parseVertex(values) 124 | elif values[0] == 'vn': 125 | self.parseNormal(values) 126 | elif values[0] == 'vt': 127 | self.parseTexCoord(values) 128 | elif (values[0] == 'f') or (values[0] == 'fo'): 129 | self.parseFace(values) 130 | elif values[0] == 'l': 131 | self.parseLine(values) 132 | elif values[0] == 'p': 133 | self.parsePoints(values) 134 | elif values[0] == 'mtllib': 135 | self.parseMtllib(values) 136 | elif values[0] == 'usemtl': 137 | self.parseUsemtl(values) 138 | 139 | self.findRadius() 140 | 141 | def parseVertex(self, val): 142 | if len(val) < 4: 143 | if self.debugmode: 144 | print('warning: incomplete vertex info:', self.fileName, 'line', self.lineNum) 145 | self.v.append([0, 0, 0]) 146 | else: 147 | self.v.append([float(val[1]), float(val[2]), float(val[3])]) 148 | 149 | def findRadius(self): 150 | maxX,maxY,maxZ = -1,-1,-1 151 | 152 | for vi in self.v: 153 | if (vi[0]>maxX): 154 | maxX = vi[0] 155 | 156 | if (vi[1]>maxY): 157 | maxY = vi[1] 158 | 159 | if (vi[2]>maxZ): 160 | maxZ = vi[2] 161 | 162 | self.r = [ maxX, maxY, maxZ ] 163 | self.maxR = max( maxX, maxY, maxZ ) 164 | 165 | 166 | def parseNormal(self, val): 167 | if len(val) < 4: 168 | if self.debugmode: 169 | print('warning: incomplete normal info:', self.fileName, 'line', self.lineNum) 170 | self.vn.append([0, 0, 0]) 171 | else: 172 | self.vn.append([float(val[1]), float(val[2]), float(val[3])]) 173 | self.hasNormals = True 174 | 175 | def parseTexCoord(self, val): 176 | if len(val) < 3: 177 | if self.debugmode: 178 | print('warning: incomplete texcoord info:', self.fileName, 'line', self.lineNum) 179 | self.vt.append([0, 0]) 180 | else: 181 | self.vt.append([float(val[1]), float(val[2])]) 182 | 183 | def parseFace(self, val): 184 | if len(val) < 4: 185 | return 186 | face = WFObject.wfface() 187 | face.points = self.parsePointList(val) 188 | self.geometry.append(face) 189 | 190 | def parseLine(self, val): 191 | if len(val) < 3: 192 | return 193 | line = WFObject.wfline() 194 | line.points = self.parsePointList(val) 195 | self.geometry.append(line) 196 | 197 | def parsePoints(self, val): 198 | if len(val) < 2: 199 | return 200 | pt = WFObject.wfpoints() 201 | pt.points = self.parsePointList(val) 202 | self.geometry.append(pt) 203 | 204 | def parsePointList(self, val): 205 | points = [] 206 | for v in val[1:]: 207 | p = { } 208 | data = v.split('/') 209 | if len(data) > 0: 210 | index = int(data[0]) 211 | if index > 0: 212 | p['v'] = self.v[index-1] 213 | elif index < 0: 214 | p['v'] = self.v[len(self.v)+index] 215 | if len(data) > 1: 216 | index = int('0'+data[1]) 217 | if index > 0: 218 | p['vt'] = self.vt[index-1] 219 | elif index < 0: 220 | p['vt'] = self.vt[len(self.vt)+index] 221 | if len(data) > 2: 222 | index = int('0'+data[2]) 223 | if index > 0: 224 | p['vn'] = self.vn[index-1] 225 | elif index < 0: 226 | p['vn'] = self.vn[len(self.vn)+index] 227 | points.append(p) 228 | return points 229 | 230 | def parseMtllib(self, val): 231 | if len(val) < 2: 232 | return 233 | curmtl = WFObject.wfmaterial() 234 | for line in open(os.path.join(self.path,val[1])).readlines(): 235 | values = line.split() 236 | if len(values) < 1: 237 | continue 238 | if values[0] == 'newmtl': 239 | curmtl = WFObject.wfmaterial() 240 | if len(values) > 1: 241 | self.materials[values[1]] = curmtl 242 | elif values[0] == 'illum': 243 | if len(values) > 1: 244 | curmtl.illum = int(values[1]) 245 | elif values[0] == 'Ns': 246 | if len(values) > 1: 247 | curmtl.Ns = float(values[1]) 248 | elif values[0] == 'Kd': 249 | if len(values) > 3: 250 | curmtl.Kd = [float(values[1]), float(values[2]), float(values[3]), 1] 251 | elif values[0] == 'Ka': 252 | if len(values) > 3: 253 | curmtl.Ka = [float(values[1]), float(values[2]), float(values[3]), 1] 254 | elif values[0] == 'Ks': 255 | if len(values) > 3: 256 | curmtl.Ks = [float(values[1]), float(values[2]), float(values[3]), 1] 257 | elif values[0] == 'map_Kd': 258 | if len(values) > 1: 259 | curmtl.texture = pyglet.image.load(values[1]).get_mipmapped_texture() 260 | 261 | def parseUsemtl(self, val): 262 | if len(val) < 2: 263 | return 264 | if val[1] in self.materials: 265 | self.geometry.append(self.materials[val[1]]) 266 | elif self.debugmode: 267 | print('warning: no material "' + val[1] + '" found:', self.fileName, 'line', self.lineNum) 268 | 269 | def draw(self): 270 | if not self.displayListInitted: 271 | self.displayList = glGenLists(1) 272 | glNewList(self.displayList, GL_COMPILE) 273 | 274 | for g in self.geometry: 275 | g.draw() 276 | glEndList() 277 | self.displayListInitted = True 278 | glCallList(self.displayList) -------------------------------------------------------------------------------- /src/widgets.py: -------------------------------------------------------------------------------- 1 | 2 | from __future__ import division 3 | import time 4 | from collections import OrderedDict 5 | from copy import deepcopy 6 | import pkg_resources 7 | from OpenGL.raw.GL.VERSION.GL_1_1 import GL_SHININESS 8 | from pyqtgraph.Qt import QtCore,QtGui,QtOpenGL 9 | import pyqtgraph as pg 10 | from objloader import WFObject 11 | import numpy as np 12 | import time 13 | import pdb 14 | from matplotlib.backends.qt_compat import QtWidgets 15 | 16 | try: 17 | from OpenGL.GL import * 18 | from OpenGL.GLUT import * 19 | from OpenGL.GLU import * 20 | except ImportError: 21 | app = QtGui.QApplication(sys.argv) 22 | QtGui.QMessageBox.critical(None,"OpenGL Error", 23 | "PyOpenGL must be installed to run this program.") 24 | 25 | pyqtSignal = QtCore.pyqtSignal 26 | 27 | resource_package = __name__ 28 | 29 | def get_source_name(file_path_name): 30 | return pkg_resources.resource_filename(resource_package,file_path_name) 31 | 32 | def move_model(x,y,z): 33 | def process_draw(some_draw_func): 34 | def new_draw_func(): 35 | glPushMatrix() 36 | glTranslatef(x,y,z) 37 | some_draw_func() 38 | glPopMatrix() 39 | return new_draw_func 40 | return process_draw 41 | 42 | def rotate_model(roll,pitch, yaw): 43 | """3-1-2 rotation transform""" 44 | def process_draw(some_draw_func): 45 | def new_draw_func(): 46 | glPushMatrix() 47 | glRotatef(pitch,0,1,0) 48 | glRotatef(roll,1,0,0) 49 | glRotatef(yaw,0,0,1) 50 | some_draw_func() 51 | glPopMatrix() 52 | return new_draw_func 53 | return process_draw 54 | 55 | class Marker(QtGui.QComboBox): 56 | sigMarkerChanged = pyqtSignal(object) 57 | def __init__(self, marker=None, *args, **kwargs): 58 | super().__init__(*args, **kwargs) 59 | self.marker = marker 60 | self._markerdict = OrderedDict([('None',None), ('☐','s'), ('▽','t'), ('○','o'), ('+','+')]) 61 | for key in self._markerdict.keys(): 62 | self.addItem(key) 63 | self.setCurrentIndex(list(self._markerdict.values()).index(self.marker)) 64 | self.currentIndexChanged.connect(self.callback_markerChanged) 65 | 66 | def callback_markerChanged(self): 67 | self.marker = list(self._markerdict.values())[self.currentIndex()] 68 | self.sigMarkerChanged.emit(self) 69 | 70 | def set_marker(self, marker=None): 71 | if marker in self._markerdict.values(): 72 | self.marker = marker 73 | self.setCurrentIndex(list(self._markerdict.values()).index(marker)) 74 | else: 75 | raise TypeError('marker not in the MarkerList') 76 | 77 | class PropertyLabel(QtGui.QLabel): 78 | sigPropertyChanged = pyqtSignal(bool) 79 | def __init__(self, item_id, mainwindow, *args, **kwargs): 80 | self.id = item_id 81 | self.mainwindow = mainwindow 82 | self.markdict = OrderedDict([(None,'None'), ('s','☐'), ('t','▽'), ('o','○'), ('+','+')]) 83 | super().__init__(*args, **kwargs) 84 | 85 | def mouseDoubleClickEvent(self, event, *args, **kwargs): 86 | print('label double clicked') 87 | self.win = CurveModifyWin(self.id, self.mainwindow) 88 | self.win.sigCurveChanged.connect(self.callback_sigchanged) 89 | self.win.show() 90 | QtGui.QApplication.processEvents() 91 | time.sleep(0.2) 92 | 93 | def update_tab_text(self): 94 | curve = self.mainwindow.data_plotting[self.id][2] 95 | marker = curve.opts['symbol'] 96 | color = curve.opts['pen'] 97 | color_text = '#{0[0]:02x}{0[1]:02x}{0[2]:02x}'.format(color) 98 | self.setText("{1} {2}".format(color_text,'▇▇',self.markdict[marker])) 99 | 100 | def callback_sigchanged(self): 101 | self.sigPropertyChanged.emit(True) 102 | 103 | class ThreadQDialog(QtCore.QThread): 104 | def __init__(self, loading_widget, parent=None, *args, **kwargs): 105 | super(ThreadQDialog, self).__init__(parent, *args, **kwargs) 106 | self.dialog = QtGui.QMessageBox() 107 | self.dialog.setWindowTitle('Info:Loading') 108 | self.dialog.setModal(True) 109 | self.dialog.hide() 110 | self.loading_widget = loading_widget 111 | self.loading_widget.loadFinished.connect(self.callback_close) 112 | 113 | def run(self): 114 | self.dialog.setText('Loading...') 115 | self.dialog.setStyleSheet('QLabel{min-width: 100px;}') 116 | self.dialog.show() 117 | 118 | def callback_close(self, isFinished): 119 | if isFinished: 120 | self.dialog.close() 121 | return 122 | 123 | class ColorPushButton(pg.ColorButton): 124 | def __init__(self, id, *args, **kwargs): 125 | self.id = id 126 | super(ColorPushButton, self).__init__(*args, **kwargs) 127 | 128 | class Checkbox(QtGui.QCheckBox): 129 | sigStateChanged = pyqtSignal(object) 130 | def __init__(self, id, *args, **kwargs): 131 | self.id = id 132 | super(Checkbox, self).__init__(*args, **kwargs) 133 | self.stateChanged.connect(self.callback_stateChanged) 134 | 135 | def callback_stateChanged(self): 136 | self.sigStateChanged.emit(self) 137 | 138 | class LineEdit(QtGui.QLineEdit): 139 | sigTextChanged = pyqtSignal(bool) 140 | def __init__(self, *args, **kwargs): 141 | super().__init__(*args, **kwargs) 142 | 143 | def keyPressEvent(self, event, *args, **kwargs): 144 | if event.key() == QtCore.Qt.Key_Enter: 145 | self.sigTextChanged.emit(True) 146 | else: 147 | QtGui.QLineEdit.keyPressEvent(self, event, *args, **kwargs) 148 | 149 | 150 | class TabBar(QtWidgets.QTabBar): 151 | def __init__(self, colors, parent=None): 152 | super(TabBar, self).__init__(parent) 153 | self.mColors = colors 154 | 155 | def paintEvent(self, event): 156 | painter = QtWidgets.QStylePainter(self) 157 | opt = QtWidgets.QStyleOptionTab() 158 | 159 | for i in range(self.count()): 160 | self.initStyleOption(opt, i) 161 | if opt.text in self.mColors: 162 | opt.palette.setColor( 163 | QtGui.QPalette.Button, self.mColors[opt.text] 164 | ) 165 | painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt) 166 | painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt) 167 | 168 | 169 | class TabWidget(QtWidgets.QTabWidget): 170 | def __init__(self, parent=None): 171 | super(TabWidget, self).__init__(parent) 172 | d = { 173 | "custom": QtGui.QColor("#e7e7e7"), 174 | "other": QtGui.QColor("#f0f0f0"), 175 | "POS Sales": QtGui.QColor("#90EE90"), 176 | "Cash Sales": QtGui.QColor("pink"), 177 | "invoice": QtGui.QColor("#800080"), 178 | } 179 | self.setTabBar(TabBar(d)) 180 | 181 | class CurveModifyWin(QtGui.QMainWindow): 182 | sigCurveChanged = pyqtSignal(bool) 183 | def __init__(self, item_id, mainwindow, *args, **kargs): 184 | self.id = item_id 185 | self.mainwindow = mainwindow 186 | super().__init__(*args, **kargs) 187 | self.resize(QtCore.QSize(300, 200)) 188 | self.properties_table = QtGui.QTableWidget() 189 | # self.setCentralWidget(self.properties_table) 190 | self.properties_table.setSortingEnabled(False) 191 | self.properties_table.horizontalHeader().setStretchLastSection(True) 192 | self.properties_table.resizeColumnsToContents() 193 | self.properties_table.setColumnCount(2) 194 | self.properties_table.setColumnWidth(0, 120) 195 | self.properties_table.setColumnWidth(1, 50) 196 | self.properties_table.setHorizontalHeaderLabels(['Property', 'value']) 197 | # first row --- color 198 | self.properties_table.insertRow(0) 199 | self.properties_table.setCellWidget(0, 0, QtGui.QLabel('Color')) 200 | self.curve = mainwindow.data_plotting[self.id][2] 201 | self.btn = ColorPushButton(self.id, self.properties_table, self.curve.opts['pen']) 202 | self.properties_table.setCellWidget(0, 1, self.btn) 203 | # second row --- symbol 204 | self.properties_table.insertRow(1) 205 | self.properties_table.setCellWidget(1, 0, QtGui.QLabel('Marker')) 206 | self.mkr = Marker(self.curve.opts['symbol']) 207 | self.properties_table.setCellWidget(1, 1, self.mkr) 208 | # third row --- symbol size 209 | self.properties_table.insertRow(2) 210 | self.properties_table.setCellWidget(2, 0, QtGui.QLabel('Marker Size')) 211 | print('symbolSize:', str(self.curve.opts['symbolSize'])) 212 | self.ln = LineEdit(str(self.curve.opts['symbolSize'])) 213 | self.properties_table.setCellWidget(2, 1, self.ln) 214 | w = QtGui.QWidget() 215 | self.vlayout = QtGui.QVBoxLayout() 216 | self.hlayout = QtGui.QHBoxLayout() 217 | self.setCentralWidget(w) 218 | self.centralWidget().setLayout(self.vlayout) 219 | self.vlayout.addWidget(self.properties_table) 220 | self.vlayout.addLayout(self.hlayout) 221 | self.cancel_btn = QtGui.QPushButton('Cancel') 222 | self.ok_btn = QtGui.QPushButton('OK') 223 | self.cancel_btn.clicked.connect(self.callback_cancel_clicked) 224 | self.ok_btn.clicked.connect(self.callback_properties_changed) 225 | self.hlayout.addWidget(self.cancel_btn) 226 | self.hlayout.addWidget(self.ok_btn) 227 | 228 | def closeEvent(self, *args, **kwargs): 229 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 230 | 231 | def callback_cancel_clicked(self): 232 | self.close() 233 | 234 | def callback_properties_changed(self, *args, **kwargs): 235 | self.curve.opts['symbol'] = self.mkr.marker 236 | self.curve.opts['pen'] = self.btn.color() 237 | try: 238 | self.curve.opts['symbolSize'] = int(self.ln.text()) 239 | print('set size finished to ', self.curve.opts['symbolSize']) 240 | except: 241 | d = QtGui.QDialog('Input Error') 242 | b1 = QtGui.QPushButton("ok",d) 243 | d.setWindowModality(QtCore.Qt.ApplicationModal) 244 | d.exec_() 245 | # update graph and items in plot list pane 246 | self.sigCurveChanged.emit(True) 247 | 248 | 249 | class InfoWin(QtGui.QMainWindow): 250 | closed = pyqtSignal(bool) 251 | 252 | def __init__(self, info_data, *args, **kwargs): 253 | super().__init__(*args, **kwargs) 254 | self.resize(QtCore.QSize(500,500)) 255 | self.info_table = QtGui.QTableWidget() 256 | self.setCentralWidget(self.info_table) 257 | self.info_table.setSortingEnabled(False) 258 | self.info_table.horizontalHeader().setStretchLastSection(True) 259 | self.info_table.resizeColumnsToContents() 260 | self.info_table.setColumnCount(2) 261 | self.info_table.setColumnWidth(0, 120) 262 | self.info_table.setColumnWidth(1, 50) 263 | self.info_table.setHorizontalHeaderLabels(['Name', 'value']) 264 | index = 0 265 | for name, value in info_data.items(): 266 | self.info_table.insertRow(index) 267 | self.info_table.setCellWidget(index, 0, QtGui.QLabel(name)) 268 | self.info_table.setCellWidget(index, 1, QtGui.QLabel(str(value))) 269 | index += 1 270 | # self.info_table.setCellWidget(index, 0, QtGui.QLabel(des)) 271 | 272 | def closeEvent(self, *args, **kwargs): 273 | self.closed.emit(True) 274 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 275 | 276 | class ParamsWin(QtGui.QMainWindow): 277 | closed = pyqtSignal(bool) 278 | def __init__(self, params_data, changed_params_data, *args, **kwargs): 279 | self.params_data = params_data 280 | self.params_data_show = list(self.params_data.keys()) 281 | self.changed_params_data = changed_params_data 282 | super().__init__(*args, **kwargs) 283 | self.resize(QtCore.QSize(500,500)) 284 | self.params_table = QtGui.QTableWidget() 285 | self.choose_item_lineEdit = QtGui.QLineEdit(self) 286 | self.choose_item_lineEdit.setPlaceholderText('filter by data name') 287 | self.choose_item_lineEdit.textChanged.connect(self.callback_filter) 288 | self.btn_changed_filter = QtGui.QPushButton('Changed') 289 | self.btn_changed_filter.clicked.connect(self.btn_changed_filter_clicked) 290 | w = QtGui.QWidget() 291 | self.vlayout = QtGui.QVBoxLayout(w) 292 | self.hlayout = QtGui.QHBoxLayout(self) 293 | self.setCentralWidget(w) 294 | self.centralWidget().setLayout(self.vlayout) 295 | self.vlayout.addWidget(self.params_table) 296 | self.vlayout.addLayout(self.hlayout) 297 | self.hlayout.addWidget(self.btn_changed_filter) 298 | self.hlayout.addWidget(self.choose_item_lineEdit) 299 | self.params_table.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked | 300 | QtGui.QAbstractItemView.SelectedClicked) 301 | self.params_table.setSortingEnabled(False) 302 | self.params_table.horizontalHeader().setStretchLastSection(True) 303 | self.params_table.resizeColumnsToContents() 304 | self.params_table.setColumnCount(2) 305 | self.params_table.setColumnWidth(0, 120) 306 | self.params_table.setColumnWidth(1, 50) 307 | self.params_table.setHorizontalHeaderLabels(['Name', 'value']) 308 | self.show_all_params = True 309 | self.filtertext = '' 310 | self.update_table() 311 | 312 | def closeEvent(self, *args, **kwargs): 313 | self.closed.emit(True) 314 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 315 | 316 | def filter(self): 317 | if self.show_all_params: 318 | self.params_data_show = list(self.params_data.keys()) 319 | else: 320 | self.params_data_show = deepcopy(self.changed_params_data) 321 | names_to_be_removed = [] 322 | for name in self.params_data_show: 323 | if self.filtertext not in name: 324 | names_to_be_removed.append(name) 325 | for name in names_to_be_removed: 326 | self.params_data_show.remove(name) 327 | 328 | def callback_filter(self, filtertext): 329 | self.filtertext = str(filtertext) 330 | self.filter() 331 | self.update_table() 332 | 333 | def btn_changed_filter_clicked(self): 334 | self.show_all_params = not self.show_all_params 335 | if self.show_all_params: 336 | text = 'Changed' 337 | else: 338 | text = 'All' 339 | self.btn_changed_filter.setText(text) 340 | self.filter() 341 | self.update_table() 342 | 343 | def update_table(self): 344 | self.params_table.setRowCount(0) 345 | index = 0 346 | for name, value in self.params_data.items(): 347 | if name in self.params_data_show: 348 | self.params_table.insertRow(index) 349 | if name in self.changed_params_data: 350 | name_str = "%s"%(name) 351 | value_str = "%s"%(str(value)) 352 | else: 353 | name_str = name 354 | value_str = str(value) 355 | name_lbl = QtGui.QLabel(name_str) 356 | value_lbl = QtGui.QLabel(value_str) 357 | self.params_table.setCellWidget(index, 0, name_lbl) 358 | self.params_table.setCellWidget(index, 1, value_lbl) 359 | index += 1 360 | 361 | class AnalysisGraphWin(QtGui.QMainWindow): 362 | sigChecked = pyqtSignal(tuple) 363 | sigUnchecked = pyqtSignal(str) 364 | closed = pyqtSignal(bool) 365 | def __init__(self, mainwindow, *args, **kwargs): 366 | self.mainwindow = mainwindow 367 | # gui 368 | self.graph_predefined_list = ['XY_Estimation', 369 | 'Altitude Estimate', 370 | 'Roll Angle', 371 | 'Pitch Angle', 372 | 'Yaw Angle', 373 | 'Roll Angle Rate', 374 | 'Pitch Angle Rate', 375 | 'Yaw Angle Rate', 376 | 'Local Position X', 377 | 'Local Position Y', 378 | 'Local Position Z', 379 | 'Velocity', 380 | 'Manual Control Input', 381 | 'Actuator Controls 0', 382 | 'Actuation Outputs(Main)', 383 | 'Actuation Outputs(Aux)', 384 | 'Magnetic field strength', 385 | 'Distance Sensor', 386 | 'GPS Uncertainty', 387 | 'GPS noise and jamming', 388 | 'CPU & RAM', 389 | 'Power'] 390 | super().__init__(*args,**kwargs) 391 | self.setFixedSize(QtCore.QSize(300, 660)) 392 | self.graph_table = QtGui.QTableWidget(self) 393 | self.graph_table.setColumnCount(2) 394 | self.graph_table.setColumnWidth(0, 200) 395 | self.graph_table.setColumnWidth(1, 40) 396 | for index, item in enumerate(self.graph_predefined_list): 397 | self.graph_table.insertRow(index) 398 | lbl = QtGui.QLabel(item) 399 | lbl.mouseDoubleClickEvent = self.callback_double_clicked(item) 400 | self.graph_table.setCellWidget(index, 0, lbl) 401 | chk = Checkbox(item) 402 | if item in self.mainwindow.analysis_graph_list: 403 | chk.setChecked(True) 404 | chk.sigStateChanged.connect(self.callback_check_state_changed) 405 | self.graph_table.setCellWidget(index, 1, chk) 406 | self.clear_btn = QtGui.QPushButton('Clear all') 407 | self.clear_btn.clicked.connect(self.callback_clear) 408 | # 409 | w = QtGui.QWidget() 410 | self.vlayout = QtGui.QVBoxLayout(w) 411 | self.setCentralWidget(w) 412 | self.centralWidget().setLayout(self.vlayout) 413 | self.vlayout.addWidget(self.graph_table) 414 | self.vlayout.addWidget(self.clear_btn) 415 | 416 | def callback_double_clicked(self, lbl_text): 417 | def func(*args, **kwargs): 418 | if lbl_text in self.mainwindow.analysis_graph_list: 419 | ind = list(self.mainwindow.analysis_graph_list.keys()).index(lbl_text) 420 | self.mainwindow.default_tab.setCurrentIndex(ind + 3) 421 | return func 422 | 423 | def callback_item_clicked(self): 424 | pass 425 | 426 | def callback_clear(self): 427 | for index in range(self.graph_table.columnCount()): 428 | self.graph_table.item(index, 1).setChecked(False) 429 | 430 | def callback_check_state_changed(self, chk): 431 | graph_name = chk.id 432 | if not chk.isChecked(): 433 | self.sigUnchecked.emit(graph_name) 434 | else: 435 | if graph_name == 'XY_Estimation': 436 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_local_position') 437 | x = self.mainwindow.log_data_list[data_index].data['x'] 438 | y = self.mainwindow.log_data_list[data_index].data['y'] 439 | data = ['2d',(x,y, 'XY_Estimation')] 440 | elif graph_name == 'Altitude Estimate': 441 | data = ['t'] 442 | # gps 443 | try: 444 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_gps_position') 445 | gps_alt = self.mainwindow.log_data_list[data_index].data['alt'] * 0.001 446 | gps_t = self.mainwindow.log_data_list[data_index].data['timestamp'] 447 | data.append((gps_t, gps_alt, 'GPS Altitude')) 448 | except: 449 | print('No vehicle_gps_position') 450 | 451 | # barometer 452 | try: 453 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_air_data') 454 | bra_alt = self.mainwindow.log_data_list[data_index].data['baro_alt_meter'] 455 | bra_t = self.mainwindow.log_data_list[data_index].data['timestamp'] 456 | data.append((bra_t, bra_alt, 'Barometer Altitude')) 457 | except: 458 | print('No vehicle_air_data') 459 | # Fused 460 | try: 461 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_global_position') 462 | fused_alt = self.mainwindow.log_data_list[data_index].data['alt'] 463 | fused_t = self.mainwindow.log_data_list[data_index].data['timestamp'] 464 | data.append((fused_t, fused_alt, 'Fused Altitude')) 465 | except: 466 | print('No vehicle_global_position') 467 | 468 | # setpoint 469 | try: 470 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_setpoint_triplet') 471 | current_alt = self.mainwindow.log_data_list[data_index].data['current.alt'] 472 | current_t = self.mainwindow.log_data_list[data_index].data['timestamp'] 473 | data.append((current_t, current_alt, 'Altitude Setpoint')) 474 | except: 475 | print('No vehicle_setpoint_triplet') 476 | 477 | # actuator_controls_0 478 | try: 479 | data_index = list(list(self.mainwindow.data_dict.keys())).index('actuator_controls_0') 480 | thrust_v = self.mainwindow.log_data_list[data_index].data['control[3]'] * 100 481 | thrust_t = self.mainwindow.log_data_list[data_index].data['timestamp'] 482 | data.append((thrust_t, thrust_v, 'Thust [0, 100]')) 483 | except: 484 | print('No actuator_controls_0') 485 | 486 | elif graph_name in ['Roll Angle', 487 | 'Pitch Angle', 488 | 'Yaw Angle']: 489 | data_index_dict = {'Roll Angle':0, 490 | 'Pitch Angle':1, 491 | 'Yaw Angle':2} 492 | data_index = data_index_dict[graph_name] 493 | angle = [angles[data_index] for angles in self.mainwindow.attitude_history] 494 | angle_t = self.mainwindow.time_stamp_attitude 495 | angle_setpoint = [angles[data_index] for angles in self.mainwindow.attitude_setpoint_history] 496 | angle_setpoint_t = self.mainwindow.time_stamp_attitude_setpoint 497 | data = ['t', (angle_t, angle, graph_name + ' Estimated'), 498 | (angle_setpoint_t, angle_setpoint, graph_name + ' Setpoint')] 499 | 500 | elif graph_name in ['Roll Angle Rate', 501 | 'Pitch Angle Rate', 502 | 'Yaw Angle Rate',]: 503 | data_index_dict = {'Roll Angle Rate':['rollspeed', 'roll'], 504 | 'Pitch Angle Rate':['pitchspeed', 'pitch'], 505 | 'Yaw Angle Rate':['yawspeed', 'yaw']} 506 | data_name = data_index_dict[graph_name] 507 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_attitude') 508 | angle_rate = np.rad2deg(self.mainwindow.log_data_list[data_index].data[data_name[0]]) 509 | t_angle_rate = self.mainwindow.log_data_list[data_index].data['timestamp'] 510 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_rates_setpoint') 511 | angle_rate_setpoint = np.rad2deg(self.mainwindow.log_data_list[data_index].data[data_name[1]]) 512 | t_angle_rate_setpoint = self.mainwindow.log_data_list[data_index].data['timestamp'] 513 | data = ['t', (t_angle_rate, angle_rate, graph_name + ' Estimated'), 514 | (t_angle_rate_setpoint, angle_rate_setpoint, graph_name + ' Setpoint')] 515 | 516 | elif graph_name in ['Local Position X', 517 | 'Local Position Y', 518 | 'Local Position Z']: 519 | data_index_dict = {'Local Position X':'x', 520 | 'Local Position Y':'y', 521 | 'Local Position Z':'z'} 522 | data_name = data_index_dict[graph_name] 523 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_local_position') 524 | x = self.mainwindow.log_data_list[data_index].data[data_name] 525 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 526 | data = ['t', (t, x, graph_name + ' Estimated')] 527 | try: 528 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_local_position_setpoint') 529 | x_setpoint = self.mainwindow.log_data_list[data_index].data[data_name] 530 | t_x_setpoint = self.mainwindow.log_data_list[data_index].data['timestamp'] 531 | data.append((t_x_setpoint, x_setpoint, graph_name + ' Setpoint')) 532 | except: 533 | pass 534 | 535 | elif graph_name == 'Velocity': 536 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_local_position') 537 | vx = self.mainwindow.log_data_list[data_index].data['vx'] 538 | vy = self.mainwindow.log_data_list[data_index].data['vy'] 539 | vz = self.mainwindow.log_data_list[data_index].data['vz'] 540 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 541 | data = ['t', 542 | (t, vx, 'VX'), 543 | (t, vy, 'VY'), 544 | (t, vz, 'VZ')] 545 | try: 546 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_local_position_setpoint') 547 | vx_setpoint = self.mainwindow.log_data_list[data_index].data['vx'] 548 | vy_setpoint = self.mainwindow.log_data_list[data_index].data['vy'] 549 | vz_setpoint = self.mainwindow.log_data_list[data_index].data['vz'] 550 | t_setpoint = self.mainwindow.log_data_list[data_index].data['timestamp'] 551 | data.extend([(t_setpoint, vx_setpoint, 'VX setpoint'), 552 | (t_setpoint, vy_setpoint, 'VY setpoint'), 553 | (t_setpoint, vz_setpoint, 'VZ setpoint')]) 554 | except: 555 | pass 556 | 557 | elif graph_name == 'Manual Control Input': 558 | # not compatible to other format 559 | data_index = list(list(self.mainwindow.data_dict.keys())).index('manual_control_setpoint') 560 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 561 | data = ['t'] 562 | data_name_dict = {'y':'Y / Roll', 'x':'X / Pitch', 'r':'Yaw', 563 | 'z': 'Throttle [0, 1]', 'mode_slot': 'Flight Mode', 564 | 'aux1': 'Aux1', 'aux2':'Aux2', 565 | 'kill_switch':'Kill Switch'} 566 | for name, label in data_name_dict.items(): 567 | if name == 'mode_slot': 568 | data.append((t, self.mainwindow.log_data_list[data_index].data[name]/6, label)) 569 | elif name == 'kill_switch': 570 | data.append((t, (self.mainwindow.log_data_list[data_index].data[name] == 1).astype(np.uint32), label)) 571 | else: 572 | data.append((t, self.mainwindow.log_data_list[data_index].data[name], label)) 573 | elif graph_name == 'Actuator Controls 0': 574 | data_index = list(list(self.mainwindow.data_dict.keys())).index('actuator_controls_0') 575 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 576 | data = ['t'] 577 | data_name_list = ['Roll', 'Pitch', 'Yaw', 'Thrust'] 578 | for i in range(4): 579 | name = 'control[%d]' % (i) 580 | data.append((t, self.mainwindow.log_data_list[data_index].data[name]), 581 | data_name_list[i]) 582 | 583 | elif graph_name == 'Actuation Outputs(Main)': 584 | data_index = list(list(self.mainwindow.data_dict.keys())).index('actuator_outputs') 585 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 586 | data = ['t'] 587 | for i in range(8): 588 | name = 'control[%d]' % (i) 589 | data.append((t, self.mainwindow.log_data_list[name], name)) 590 | 591 | elif graph_name == 'Actuation Outputs(Aux)': 592 | data_index = list(list(self.mainwindow.data_dict.keys())).index('actuator_outputs_1') 593 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 594 | num_outputs = np.max(self.mainwindow.log_data_list[data_index].data['noutputs']) 595 | data = ['t'] 596 | for i in range(num_outputs): 597 | name = 'control[%d]' % (i) 598 | data.append((t, self.mainwindow.log_data_list[name], name)) 599 | elif graph_name == 'Magnetic field strength': 600 | data_index = list(list(self.mainwindow.data_dict.keys())).index('sensor_combined') 601 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 602 | data = ['t'] 603 | for i in range(3): 604 | name = 'magnetometer_ga[%d]'%(i) 605 | data.append((t, self.mainwindow.log_data_list[name], name)) 606 | 607 | elif graph_name == 'Distance Sensor': 608 | data_index = list(list(self.mainwindow.data_dict.keys())).index('sensor_combined') 609 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 610 | current_distance = self.mainwindow.log_data_list[data_index].data['current_distance'] 611 | covariance = self.mainwindow.log_data_list[data_index].data['covariance'] 612 | data = ['t', (t, current_distance, 'Distance'), (t, covariance, 'Covariance')] 613 | elif graph_name == 'GPS Uncertainty': 614 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_gps_position') 615 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 616 | data = ['t'] 617 | data_dict = {'eph':'Horizontal position accuracy [m]', 618 | 'epv':'Vertical position accuracy [m]', 619 | 'satellites_used': 'Num Satellites used', 620 | 'fix_type':'GPS fix'} 621 | for index, label in data_dict.items(): 622 | data.append((t, self.mainwindow.log_data_list[data_index].data['eph'], label)) 623 | elif graph_name == 'GPS noise and jamming': 624 | data_index = list(list(self.mainwindow.data_dict.keys())).index('vehicle_gps_position') 625 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 626 | noise_per_ms = self.mainwindow.log_data_list[data_index].data['noise_per_ms'] 627 | jamming_indicator = self.mainwindow.log_data_list[data_index].data['jamming_indicator'] 628 | data = ['t', (t, noise_per_ms, 'Noise per ms'), (t, jamming_indicator, 'Jamming Indicator')] 629 | elif graph_name == 'CPU & RAM': 630 | data_index = list(list(self.mainwindow.data_dict.keys())).index('cpuload') 631 | x1 = self.mainwindow.log_data_list[data_index].data['load'] 632 | x2 = self.mainwindow.log_data_list[data_index].data['ram_usage'] 633 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 634 | data = ['t', (t, x1, 'CPU'), (t, x2, 'Ram usage')] 635 | elif graph_name == 'Power': 636 | data_index = list(list(self.mainwindow.data_dict.keys())).index('battery_status') 637 | t = self.mainwindow.log_data_list[data_index].data['timestamp'] 638 | v = self.mainwindow.log_data_list[data_index].data['voltage_v'] 639 | data = ['t', (t, v, 'Votage(V)')] 640 | 641 | self.sigChecked.emit((graph_name, data)) 642 | 643 | def closeEvent(self, *args, **kwargs): 644 | self.closed.emit(True) 645 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 646 | 647 | class HelpWin(QtGui.QMainWindow): 648 | closed = pyqtSignal(bool) 649 | 650 | def __init__(self, *args, **kwargs): 651 | super().__init__(*args, **kwargs) 652 | self.setFixedSize(QtCore.QSize(600, 400)) 653 | w = QtGui.QWidget() 654 | self.setCentralWidget(w) 655 | vlayout = QtGui.QVBoxLayout() 656 | w.setLayout(vlayout) 657 | self.htmlView = QtWidgets.QTextBrowser(self) 658 | font = QtGui.QFont() 659 | font.setFamily('Arial') 660 | self.htmlView.setReadOnly(True) 661 | self.htmlView.setFont(font) 662 | self.htmlView.setOpenExternalLinks(True) 663 | self.htmlView.setObjectName('Help information') 664 | html_help_path = get_source_name('docs/help.html') 665 | ret = self.htmlView.setSource(QtCore.QUrl(html_help_path)) 666 | print('load result:', ret) 667 | # self.htmlView.append(ret) 668 | vlayout.addWidget(self.htmlView) 669 | 670 | def closeEvent(self, *args, **kwargs): 671 | self.closed.emit(True) 672 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 673 | 674 | class QuadrotorWin(QtGui.QMainWindow): 675 | closed = pyqtSignal(bool) 676 | 677 | def __init__(self,*args,**kwargs): 678 | super(QuadrotorWin,self).__init__(*args,**kwargs) 679 | self.toolBar = self.addToolBar('showSetting') 680 | self.trace_show = QtGui.QAction(QtGui.QIcon(get_source_name('icons/trace.gif')), 681 | 'show trace', 682 | self) 683 | self.trace_show.triggered.connect(self.callback_show_trace) 684 | self.trace_showed = False 685 | self.vector_show = QtGui.QAction(QtGui.QIcon(get_source_name('icons/rotor_vector.gif')), 686 | 'show rotation speed vector',self) 687 | self.vector_show.triggered.connect(self.callback_show_vector) 688 | self.vector_showed = False 689 | self.toolBar.addAction(self.trace_show) 690 | self.toolBar.addAction(self.vector_show) 691 | self.quadrotor_win_main_widget = QtGui.QWidget(self) 692 | self.quadrotor_win_main_layout = QtGui.QHBoxLayout() 693 | self.quadrotor_win_main_widget.setLayout(self.quadrotor_win_main_layout) 694 | self.quadrotor_widget = QuadrotorWidget(self.quadrotor_win_main_widget) 695 | self.quadrotor_win_main_layout.addWidget(self.quadrotor_widget) 696 | self.setCentralWidget(self.quadrotor_win_main_widget) 697 | self.setWindowTitle("pyFlightAnalysis Trace plot") 698 | 699 | def closeEvent(self, *args, **kwargs): 700 | self.closed.emit(True) 701 | return QtGui.QMainWindow.closeEvent(self, *args, **kwargs) 702 | 703 | def callback_show_trace(self): 704 | if self.trace_showed: 705 | self.trace_show.setIcon(QtGui.QIcon(get_source_name('icons/trace.gif'))) 706 | self.quadrotor_widget.trace_visible = False 707 | else: 708 | self.trace_show.setIcon(QtGui.QIcon(get_source_name("icons/trace_pressed.gif"))) 709 | self.quadrotor_widget.trace_visible = True 710 | self.trace_showed = not self.trace_showed 711 | 712 | def callback_show_vector(self): 713 | if self.vector_showed: 714 | self.vector_show.setIcon(QtGui.QIcon(get_source_name("icons/rotor_vector.gif"))) 715 | self.quadrotor_widget.vector_visible = False 716 | else: 717 | self.vector_show.setIcon(QtGui.QIcon(get_source_name("icons/rotor_vector_pressed.gif"))) 718 | self.quadrotor_widget.vector_visible = True 719 | self.vector_showed = not self.vector_showed 720 | 721 | def callback_update_quadrotor_pos(self,state): 722 | self.quadrotor_widget.update_state(state) 723 | 724 | def callback_quadrotor_state_reset(self): 725 | self.quadrotor_widget.reset() 726 | 727 | class QuadrotorWidget(QtOpenGL.QGLWidget): 728 | """ 729 | Quadrotor 3D viewer 730 | """ 731 | loadFinished = pyqtSignal(bool) 732 | 733 | def __init__(self,*args,**kwargs): 734 | super(QuadrotorWidget,self).__init__(*args,**kwargs) 735 | 736 | self.object = 0 737 | 738 | self.lastPos = QtCore.QPoint() 739 | 740 | self.trolltechGreen = QtGui.QColor.fromCmykF(0.40,0.0,1.0,0.0) 741 | self.trolltechPurple = QtGui.QColor.fromCmykF(0.39,0.39,0.0,0.0) 742 | 743 | # window parameters 744 | self.window = 0 745 | ## window size in pixels. width and height 746 | self.window_size = (800,800) 747 | self.window_size_minimum = (400,400) 748 | ## perspective 749 | self.fovy = 60 750 | self.near = 0.01 751 | self.far = 2000 752 | 753 | # axes parameters 754 | self.tip = 0 755 | self.ORG = [0,0,0] 756 | self.AXES = [[2,0,0],[0,2,0],[0,0,2]] 757 | self.AXES_NORM = [[-1,0,0],[0,-1,0],[0,0,-1]] 758 | self.mat_diffuse = (1.0,0.0,0.0,0.0) 759 | 760 | # material and light parameters 761 | self.mat_specular = (0.2,0.2,0.2,0.2) 762 | self.mat_shininess = 0.2 763 | self.light_position = (10.0,10.0,10.0,0.0) 764 | self.white_light = (1.0,1.0,1.0,1.0) 765 | self.lmodel_ambient = (0.1,0.1,0.1,1.0) 766 | 767 | # load model 768 | self.drone_base = WFObject() 769 | self.drone_base.loadFile(get_source_name("models/drone_base.obj")) 770 | self.drone_propeller1 = WFObject() 771 | self.drone_propeller1.loadFile(get_source_name("models/drone_propeller1.obj")) 772 | self.drone_propeller2 = WFObject() 773 | self.drone_propeller2.loadFile(get_source_name("models/drone_propeller2.obj")) 774 | self.drone_propeller3 = WFObject() 775 | self.drone_propeller3.loadFile(get_source_name("models/drone_propeller3.obj")) 776 | self.drone_propeller4 = WFObject() 777 | self.drone_propeller4.loadFile(get_source_name("models/drone_propeller4.obj")) 778 | 779 | # Base Plane Size 780 | self.floor_size = (-100,100) 781 | self.floor_grid_num = 60 782 | self.floor_color = (0.3,0.3,0.3) 783 | 784 | # rotation vector 785 | self.vector_radius = 0.4 786 | self.vector_radius_color = [0.8,0.0,0.0] 787 | 788 | # quadrotor state set 789 | self.drone_position = [0,0,0] 790 | ## 312 yaw-roll-pitch 791 | self.drone_angles = [0,0,0] 792 | ## drone_motor_speed 793 | self.drone_motors_speed = [100.0,200.0,250.0,10.0] 794 | 795 | # info screen 796 | self.info_screen = None 797 | self.info_screen_size = (150,200) 798 | ## gap between the left border/top border and info screen 799 | self.info_screen_gap = (10,10) 800 | ## view size 801 | self.info_screen_L = 10 802 | ## char size 803 | self.char_height = 4 804 | self.char_gap = 2 805 | 806 | # interaction parameters 807 | self.mouse_state = {'button':None,'position':[0,0]} 808 | self.scene_movement = [0,0] 809 | self.movement_ratio = 0.1 810 | self.rotation_ratio = 0.1 811 | self.scale_ratio = 0.01 812 | self.camera_azimuth = 45 813 | self.camera_attitude = 45 814 | ## distance between eye and origin 815 | self.eye_R = 180 816 | self.camera_view_center = [0,0,0] 817 | self.follow = True 818 | self.trace_visible = False 819 | self.vector_visible = False 820 | self.drone_position_history = [] 821 | ## drone trace color 822 | self.drone_trace_color = [1,0,0] 823 | 824 | # GUI 825 | glutInit() 826 | 827 | self.animationTimer = QtCore.QTimer() 828 | self.animationTimer.setSingleShot(False) 829 | self.animationTimer.timeout.connect(self.animate) 830 | self.animationTimer.start(25) 831 | 832 | 833 | def minimumSizeHint(self): 834 | return QtCore.QSize(*self.window_size_minimum) 835 | 836 | def sizeHint(self): 837 | return QtCore.QSize(*self.window_size) 838 | 839 | def initializeGL(self): 840 | glClearColor(0.0,0.0,0.0,0.0) 841 | glShadeModel(GL_SMOOTH) 842 | w,h = self.window_size 843 | glMatrixMode(GL_PROJECTION) 844 | glLoadIdentity() 845 | gluPerspective(60,w/h,self.near,self.far) 846 | 847 | # Isolate the light position 848 | glMatrixMode(GL_MODELVIEW) 849 | glLoadIdentity() 850 | glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,self.mat_diffuse) 851 | glMaterialfv(GL_FRONT,GL_SPECULAR,self.mat_specular) 852 | glMaterialfv(GL_FRONT,GL_SHININESS,self.mat_shininess) 853 | 854 | glLightfv(GL_LIGHT0,GL_POSITION,self.light_position) 855 | glLightfv(GL_LIGHT0,GL_DIFFUSE,self.white_light) 856 | glLightModelfv(GL_LIGHT_MODEL_AMBIENT,self.lmodel_ambient) 857 | glEnable(GL_LIGHTING) 858 | glEnable(GL_LIGHT0) 859 | glEnable(GL_DEPTH_TEST) 860 | # keep unit of normal vector 861 | glEnable(GL_NORMALIZE) 862 | print('End initialization.') 863 | 864 | def setRotation(self,dxdy): 865 | dx,dy = dxdy 866 | # rotate the view port 867 | self.camera_azimuth -= dx * self.rotation_ratio 868 | self.camera_attitude += dy * self.rotation_ratio 869 | self.update() 870 | 871 | def setScale(self,scale_size): 872 | _, y = scale_size.x(), scale_size.y() 873 | if self.eye_R > 5: 874 | self.eye_R += self.scale_ratio * y 875 | self.update() 876 | 877 | def setMovement(self,dxdy): 878 | dx,dy = dxdy 879 | self.scene_movement[0] += self.movement_ratio*dx 880 | self.scene_movement[1] += self.movement_ratio*dy 881 | self.update() 882 | 883 | def paintGL(self): 884 | #glutSetWindow(self.main_window) 885 | glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) 886 | # # debug code 887 | # num = glGetIntegerv (GL_MODELVIEW_STACK_DEPTH) 888 | # if num == 31: 889 | # sys.exit() 890 | # print('The number of ModelVIEW:',num) 891 | 892 | glPushMatrix() 893 | # scene 894 | ## move 895 | glTranslatef(self.scene_movement[0],-self.scene_movement[1],0.0) 896 | ## rotation 897 | eyex,eyey,eyez = self.calculate_eyepoint() 898 | if self.follow: 899 | centerx,centery,centerz = self.drone_position 900 | else: 901 | centerx,centery,centerz = self.camera_view_center 902 | 903 | if self.camera_attitude > 90: 904 | # solve the situation where the up vector is upward 905 | glRotatef(180,0.0,0.0,1.0) 906 | gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,0.0,0.0,1.0) 907 | else: 908 | gluLookAt(eyex,eyey,eyez,centerx,centery,centerz,0.0,0.0,1.0) 909 | 910 | glLightfv(GL_LIGHT0,GL_POSITION,self.light_position) 911 | self.draw_model(self.drone_position,self.drone_angles,self.draw_drone) 912 | self.draw_axes() 913 | if self.trace_visible: 914 | self.draw_trace() 915 | self.draw_floor() 916 | glPopMatrix() 917 | #glutSwapBuffers() 918 | 919 | def resizeGL(self,w,h): 920 | glViewport(0,0,w,h) 921 | glMatrixMode(GL_PROJECTION) 922 | glLoadIdentity() 923 | gluPerspective(65.0,w/h,self.near,self.far) 924 | glMatrixMode(GL_MODELVIEW) 925 | glLoadIdentity() 926 | glTranslatef(0.0,0.0,-5.0) 927 | 928 | def mousePressEvent(self, event): 929 | self.lastPos = event.pos() 930 | 931 | def mouseMoveEvent(self, event): 932 | dx = event.x() - self.lastPos.x() 933 | dy = event.y() - self.lastPos.y() 934 | 935 | if event.buttons() & QtCore.Qt.LeftButton: 936 | # move the scene 937 | self.setMovement((dx,dy)) 938 | elif event.buttons() & QtCore.Qt.MiddleButton: 939 | self.setRotation((dx,dy)) 940 | 941 | self.lastPos = event.pos() 942 | 943 | def wheelEvent(self, event): 944 | # scale the scene 945 | self.setScale(event.angleDelta()) 946 | 947 | def normalizeAngle(self, angle): 948 | while angle < 0: 949 | angle += 360 * 16 950 | while angle > 360 * 16: 951 | angle -= 360 * 16 952 | return angle 953 | 954 | @staticmethod 955 | def calc_angle(t,speed): 956 | return t * speed % 1.0 / 1.0 * 360 957 | 958 | def draw_vector(self,speed): 959 | glPushMatrix() 960 | glColor3f(*self.vector_radius_color) 961 | glRotatef(-90,1.0,0.0,0.0) 962 | glutSolidCylinder(self.vector_radius,speed/100.0,8,10) 963 | glTranslatef(0.0,0.0,speed/100.0) 964 | glutSolidCone(2*self.vector_radius,3*self.vector_radius,8,10) 965 | glPopMatrix() 966 | 967 | def draw_propeller(self,t,pos,propeller_num,propeller_obj): 968 | glPushMatrix() 969 | angle = self.calc_angle(t,self.drone_motors_speed[propeller_num]) 970 | if self.vector_visible: 971 | glPushMatrix() 972 | glTranslatef(pos[0],pos[1]+2.2,pos[2]) 973 | self.draw_vector(self.drone_motors_speed[propeller_num]) 974 | glPopMatrix() 975 | 976 | neg_pos = [-item for item in pos] 977 | glTranslatef(*neg_pos) 978 | glRotatef(angle,0.0,-1.0,0.0) 979 | glTranslatef(*pos) 980 | propeller_obj.draw() 981 | 982 | glPopMatrix() 983 | 984 | def draw_model(self,displacement,angles,draw_func): 985 | move_model(*displacement)(rotate_model(*angles)(draw_func))() 986 | 987 | def draw_drone(self): 988 | # print('In draw model') 989 | glPushMatrix() 990 | # model plot 991 | glTranslatef(0.0,0.0,9.5) 992 | # 312 993 | glRotatef(90,1.0,0.0,0.0) 994 | 995 | self.drone_base.draw() 996 | 997 | t = time.time() 998 | self.draw_propeller(t, [11.0,0.0,11.0], 0, self.drone_propeller1) 999 | self.draw_propeller(t, [11.0,0.0,-11.0], 1, self.drone_propeller2) 1000 | self.draw_propeller(t, [-11.0,0.0,-11.0], 2, self.drone_propeller3) 1001 | self.draw_propeller(t, [-11.0,0.0,11.0],3, self.drone_propeller4) 1002 | 1003 | glPopMatrix() 1004 | 1005 | def draw_trace(self): 1006 | """Draw the drone history trace""" 1007 | glPushMatrix() 1008 | glDisable(GL_LIGHTING) 1009 | glColor3f(*self.drone_trace_color) 1010 | glBegin(GL_LINE_STRIP) 1011 | for pos in self.drone_position_history: 1012 | glVertex3f(*pos) 1013 | glEnd() 1014 | glEnable(GL_LIGHTING) 1015 | glPopMatrix() 1016 | 1017 | def draw_axes(self): 1018 | """ Draw axes 1019 | """ 1020 | glPushMatrix() 1021 | glTranslatef(0,0,0) 1022 | glRotatef(self.tip,1,0,0) 1023 | #glScalef(0.25,0.25,0.25) 1024 | glLineWidth(2.0) 1025 | glDisable(GL_LIGHTING) 1026 | # X axis 1027 | glColor3f(1,0,0) 1028 | glBegin(GL_LINE_STRIP) 1029 | glVertex3fv(self.ORG) 1030 | glVertex3fv(self.AXES[0]) 1031 | glEnd() 1032 | glRasterPos3f(np.linalg.norm(self.AXES[0]),0.0,0.0) 1033 | glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,ord('x')) 1034 | 1035 | # Y axis 1036 | glColor3f(0,1,0) 1037 | glBegin(GL_LINE_STRIP) 1038 | glVertex3fv(self.ORG) 1039 | glVertex3fv(self.AXES[1]) 1040 | glEnd() 1041 | glRasterPos3f(0.0,np.linalg.norm(self.AXES[1]),0.0) 1042 | glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,ord('y')) 1043 | 1044 | # Z axis 1045 | glColor3f(0,0,1) 1046 | glBegin(GL_LINE_STRIP) 1047 | glVertex3fv(self.ORG) 1048 | glVertex3fv(self.AXES[2]) 1049 | glEnd() 1050 | glRasterPos3f(0.0,0.0,np.linalg.norm(self.AXES[2])) 1051 | glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,ord('z')) 1052 | 1053 | glEnable(GL_LIGHTING) 1054 | glPopMatrix() 1055 | 1056 | 1057 | def draw_floor(self): 1058 | """Draw the floor""" 1059 | glPushMatrix() 1060 | #glLoadIdentity() 1061 | glLineWidth(2.0) 1062 | glDisable(GL_LIGHTING) 1063 | glColor3fv(self.floor_color) 1064 | dl = (self.floor_size[1] - self.floor_size[0])/self.floor_grid_num 1065 | glBegin(GL_LINES) 1066 | for i in range(self.floor_grid_num+1): 1067 | glVertex3f(self.floor_size[0] + dl * i,self.floor_size[0],0.0) 1068 | glVertex3f(self.floor_size[0] + dl * i,self.floor_size[1],0.0) 1069 | glVertex3f(self.floor_size[0],self.floor_size[0] + dl * i,0.0) 1070 | glVertex3f(self.floor_size[1],self.floor_size[0] + dl * i,0.0) 1071 | glEnd() 1072 | glPopMatrix() 1073 | glEnable(GL_LIGHTING) 1074 | 1075 | def calculate_eyepoint(self): 1076 | """Calculate gluLookAt eye point from azimuth and attitude""" 1077 | L_xy = self.eye_R * np.cos(self.camera_attitude * np.pi/180) 1078 | x = L_xy * np.cos(self.camera_azimuth * np.pi / 180) 1079 | y = L_xy * np.sin(self.camera_azimuth * np.pi / 180) 1080 | z = self.eye_R * np.sin(self.camera_attitude * np.pi /180) 1081 | return (x,y,z) 1082 | 1083 | def animate(self): 1084 | self.update() 1085 | 1086 | def update(self, *args, **kwargs): 1087 | self.loadFinished.emit(True) 1088 | return QtOpenGL.QGLWidget.update(self, *args, **kwargs) 1089 | 1090 | def update_state(self,state): 1091 | """Update motor animation state from state""" 1092 | try: 1093 | pos,attitude,output = state 1094 | self.drone_position_history.append(self.drone_position) 1095 | self.drone_position = [pos[0], pos[1], -pos[2]] 1096 | self.drone_angles = attitude 1097 | self.drone_motors_speed = output 1098 | self.update() 1099 | except Exception as ex: 1100 | print(ex) 1101 | 1102 | def reset(self): 1103 | """Go back to origin.""" 1104 | self.position_history = [] 1105 | self.drone_position = [0,0,0] 1106 | self.drone_motors_speed = [0,0,0,0] 1107 | --------------------------------------------------------------------------------