cfclient 2017.4__py3-none-any.whl → 2025.12.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cfclient/__init__.py +16 -11
- cfclient/configs/config.json +4 -3
- cfclient/configs/input/Generic_OS_X.json +1 -0
- cfclient/configs/input/Joystick.json +1 -0
- cfclient/configs/input/PS3_Mode_1.json +1 -0
- cfclient/configs/input/PS3_Mode_2.json +1 -0
- cfclient/configs/input/PS3_Mode_3.json +1 -0
- cfclient/configs/input/PS4_Mode_1.json +1 -0
- cfclient/configs/input/PS4_Mode_2.json +1 -0
- cfclient/configs/input/PS4_shoulder_btns_yaw.json +1 -0
- cfclient/configs/input/xbox360_mode1.json +1 -0
- cfclient/configs/log/PID_tuning/Attitude.json +46 -0
- cfclient/configs/log/PID_tuning/Attitude_rate.json +46 -0
- cfclient/configs/log/PID_tuning/Position.json +46 -0
- cfclient/configs/log/PID_tuning/Velocity.json +46 -0
- cfclient/configs/log/PID_tuning_components/Pitch.json +22 -0
- cfclient/configs/log/PID_tuning_components/Pitch_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Position_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll.json +22 -0
- cfclient/configs/log/PID_tuning_components/Roll_rate.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_x.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_y.json +22 -0
- cfclient/configs/log/PID_tuning_components/Velocity_z.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw.json +22 -0
- cfclient/configs/log/PID_tuning_components/Yaw_rate.json +22 -0
- cfclient/gui.py +44 -9
- cfclient/headless.py +3 -12
- cfclient/resources/log_param_doc.json +1 -0
- cfclient/ui/connectivity_manager.py +198 -0
- cfclient/ui/dialogs/about.py +53 -36
- cfclient/ui/dialogs/about.ui +23 -3
- cfclient/ui/dialogs/anchor_position_dialog.py +252 -0
- cfclient/ui/dialogs/anchor_position_dialog.ui +138 -0
- cfclient/ui/dialogs/basestation_mode_dialog.py +185 -0
- cfclient/ui/dialogs/basestation_mode_dialog.ui +186 -0
- cfclient/ui/dialogs/bootloader.py +448 -85
- cfclient/ui/dialogs/bootloader.ui +387 -134
- cfclient/ui/dialogs/cf2config.py +4 -4
- cfclient/ui/dialogs/cf2config.ui +3 -4
- cfclient/ui/dialogs/inputconfigdialogue.py +24 -19
- cfclient/ui/dialogs/inputconfigdialogue.ui +53 -30
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.py +220 -0
- cfclient/ui/dialogs/lighthouse_bs_geometry_dialog.ui +110 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.py +93 -0
- cfclient/ui/dialogs/lighthouse_system_type_dialog.ui +121 -0
- cfclient/ui/dialogs/logconfigdialogue.py +401 -101
- cfclient/ui/dialogs/logconfigdialogue.ui +117 -72
- cfclient/ui/icons/bl.webp +0 -0
- cfclient/ui/icons/bolt.webp +0 -0
- cfclient/ui/icons/cf21.webp +0 -0
- cfclient/ui/icons/checkmark_black.png +0 -0
- cfclient/ui/icons/checkmark_white.png +0 -0
- cfclient/ui/icons/create.png +0 -0
- cfclient/ui/icons/delete.png +0 -0
- cfclient/ui/icons/flapper.webp +0 -0
- cfclient/ui/icons/tag.webp +0 -0
- cfclient/ui/main.py +328 -258
- cfclient/ui/main.ui +184 -80
- cfclient/ui/pluginhelper.py +7 -1
- cfclient/ui/pose_logger.py +116 -0
- cfclient/ui/tab_toolbox.py +208 -0
- cfclient/ui/tabs/ColorLEDTab.py +752 -0
- cfclient/ui/tabs/ConsoleTab.py +48 -13
- cfclient/ui/{toolboxes → tabs}/CrtpSharkToolbox.py +19 -34
- cfclient/ui/tabs/ExampleTab.py +9 -16
- cfclient/ui/tabs/FlightTab.py +437 -325
- cfclient/ui/tabs/GpsTab.py +14 -20
- cfclient/ui/tabs/LEDRingTab.py +277 -0
- cfclient/ui/tabs/LogBlockDebugTab.py +20 -27
- cfclient/ui/tabs/LogBlockTab.py +35 -35
- cfclient/ui/tabs/LogClientTab.py +85 -0
- cfclient/ui/tabs/LogTab.py +50 -27
- cfclient/ui/tabs/ParamTab.py +443 -57
- cfclient/ui/tabs/PlotTab.py +23 -25
- cfclient/ui/tabs/TuningTab.py +292 -0
- cfclient/ui/tabs/__init__.py +12 -2
- cfclient/ui/tabs/colorLEDTab.ui +624 -0
- cfclient/ui/tabs/consoleTab.ui +46 -0
- cfclient/ui/tabs/flightActionContainer.ui +103 -0
- cfclient/ui/tabs/flightTab.ui +724 -237
- cfclient/ui/tabs/{ledTab.ui → ledRingTab.ui} +63 -46
- cfclient/ui/tabs/lighthouse_tab.py +714 -0
- cfclient/ui/tabs/lighthouse_tab.ui +430 -0
- cfclient/ui/tabs/locopositioning_tab.py +606 -389
- cfclient/ui/tabs/locopositioning_tab.ui +370 -253
- cfclient/ui/tabs/logClientTab.ui +52 -0
- cfclient/ui/tabs/logTab.ui +1 -1
- cfclient/ui/tabs/paramTab.ui +204 -3
- cfclient/ui/tabs/tuningTab.ui +773 -0
- cfclient/ui/widgets/ai.py +37 -39
- cfclient/ui/widgets/hexspinbox.py +16 -10
- cfclient/ui/widgets/plotter.ui +39 -47
- cfclient/ui/widgets/plotwidget.py +57 -22
- cfclient/ui/widgets/super_slider.py +112 -0
- cfclient/ui/wizards/__init__.py +0 -0
- cfclient/ui/wizards/bslh_1.png +0 -0
- cfclient/ui/wizards/bslh_2.png +0 -0
- cfclient/ui/wizards/bslh_3.png +0 -0
- cfclient/ui/wizards/bslh_4.png +0 -0
- cfclient/ui/wizards/bslh_5.png +0 -0
- cfclient/ui/wizards/lighthouse_geo_bs_estimation_wizard.py +465 -0
- cfclient/utils/config_manager.py +5 -4
- cfclient/utils/input/__init__.py +77 -19
- cfclient/utils/input/inputinterfaces/wiimote.py +2 -2
- cfclient/utils/input/inputreaderinterface.py +17 -7
- cfclient/utils/input/inputreaders/__init__.py +17 -0
- cfclient/utils/logconfigreader.py +245 -25
- cfclient/utils/logdatawriter.py +3 -1
- cfclient/utils/periodictimer.py +1 -1
- cfclient/utils/ui.py +336 -0
- cfclient/utils/zmq_led_driver.py +5 -0
- cfclient/utils/zmq_param.py +6 -0
- cfclient/version.py +34 -1
- cfclient-2025.12.1.dist-info/METADATA +70 -0
- cfclient-2025.12.1.dist-info/RECORD +152 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/WHEEL +1 -1
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/entry_points.txt +0 -1
- cfclient-2025.12.1.dist-info/licenses/LICENSE.txt +350 -0
- {cfclient-2017.4.dist-info → cfclient-2025.12.1.dist-info}/top_level.txt +1 -0
- cfconfig/Makefile +51 -0
- cfconfig/configblock.py +111 -0
- cfloader/__init__.py +41 -55
- cfzmq/__init__.py +22 -14
- cfclient/ui/dialogs/cf1config.py +0 -265
- cfclient/ui/dialogs/cf1config.ui +0 -260
- cfclient/ui/tab.py +0 -96
- cfclient/ui/tabs/LEDTab.py +0 -169
- cfclient/ui/toolboxes/ConsoleToolbox.py +0 -69
- cfclient/ui/toolboxes/DebugDriverToolbox.py +0 -107
- cfclient/ui/toolboxes/__init__.py +0 -45
- cfclient/ui/toolboxes/consoleToolbox.ui +0 -62
- cfclient/ui/toolboxes/debugDriverToolbox.ui +0 -86
- cfclient-2017.4.dist-info/DESCRIPTION.rst +0 -3
- cfclient-2017.4.dist-info/METADATA +0 -22
- cfclient-2017.4.dist-info/RECORD +0 -104
- cfclient-2017.4.dist-info/metadata.json +0 -1
- /cfclient/{icon-256.png → ui/icons/icon-256.png} +0 -0
- /cfclient/ui/{toolboxes → tabs}/crtpSharkToolbox.ui +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
#
|
|
4
|
+
# || ____ _ __
|
|
5
|
+
# +------+ / __ )(_) /_______________ _____ ___
|
|
6
|
+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
|
|
7
|
+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
|
+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
|
+
#
|
|
10
|
+
# Copyright (C) 2021-2023 Bitcraze AB
|
|
11
|
+
#
|
|
12
|
+
# Crazyflie Nano Quadcopter Client
|
|
13
|
+
#
|
|
14
|
+
# This program is free software; you can redistribute it and/or
|
|
15
|
+
# modify it under the terms of the GNU General Public License
|
|
16
|
+
# as published by the Free Software Foundation; either version 2
|
|
17
|
+
# of the License, or (at your option) any later version.
|
|
18
|
+
#
|
|
19
|
+
# This program is distributed in the hope that it will be useful,
|
|
20
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
21
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
22
|
+
# GNU General Public License for more details.
|
|
23
|
+
|
|
24
|
+
# You should have received a copy of the GNU General Public License
|
|
25
|
+
# along with this program; if not, write to the Free Software
|
|
26
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
27
|
+
# 02110-1301, USA.
|
|
28
|
+
from collections import namedtuple
|
|
29
|
+
from PyQt6.QtCore import pyqtSignal, QObject
|
|
30
|
+
|
|
31
|
+
__author__ = 'Bitcraze AB'
|
|
32
|
+
__all__ = ['ConnectivityManager']
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ConnectivityManager(QObject):
|
|
36
|
+
UiElementsContainer = namedtuple('UiElementContainer', [
|
|
37
|
+
'interface_combo',
|
|
38
|
+
'address_spinner',
|
|
39
|
+
'connect_button',
|
|
40
|
+
'scan_button'])
|
|
41
|
+
|
|
42
|
+
class UIState:
|
|
43
|
+
DISCONNECTED = 0
|
|
44
|
+
CONNECTING = 1
|
|
45
|
+
CONNECTED = 2
|
|
46
|
+
SCANNING = 3
|
|
47
|
+
|
|
48
|
+
INTERFACE_PROMPT_TEXT = 'Select an interface'
|
|
49
|
+
|
|
50
|
+
connect_button_clicked = pyqtSignal()
|
|
51
|
+
scan_button_clicked = pyqtSignal(object)
|
|
52
|
+
connection_state_changed = pyqtSignal(object)
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
QObject.__init__(self)
|
|
56
|
+
self._ui_elements = []
|
|
57
|
+
self._state = self.UIState.DISCONNECTED
|
|
58
|
+
self._is_enabled = True
|
|
59
|
+
|
|
60
|
+
def register_ui_elements(self, ui_elements):
|
|
61
|
+
self._ui_elements.append(ui_elements)
|
|
62
|
+
|
|
63
|
+
ui_elements.connect_button.clicked.connect(self._connect_button_click_handler)
|
|
64
|
+
ui_elements.scan_button.clicked.connect(self._scan_button_click_handler)
|
|
65
|
+
|
|
66
|
+
ui_elements.address_spinner.valueChanged.connect(self._address_changed_handler)
|
|
67
|
+
ui_elements.address_spinner.editingFinished.connect(self._address_edited_handler)
|
|
68
|
+
|
|
69
|
+
ui_elements.interface_combo.currentIndexChanged.connect(self._interface_combo_current_index_changed_handler)
|
|
70
|
+
|
|
71
|
+
def set_state(self, state):
|
|
72
|
+
if self._state != state:
|
|
73
|
+
self._state = state
|
|
74
|
+
self._update_ui()
|
|
75
|
+
|
|
76
|
+
if self._state == self.UIState.DISCONNECTED:
|
|
77
|
+
self.connection_state_changed.emit(self.UIState.DISCONNECTED)
|
|
78
|
+
elif self._state == self.UIState.CONNECTED:
|
|
79
|
+
self.connection_state_changed.emit(self.UIState.CONNECTED)
|
|
80
|
+
elif self._state == self.UIState.CONNECTING:
|
|
81
|
+
self.connection_state_changed.emit(self.UIState.CONNECTING)
|
|
82
|
+
elif self._state == self.UIState.SCANNING:
|
|
83
|
+
self.connection_state_changed.emit(self.UIState.SCANNING)
|
|
84
|
+
|
|
85
|
+
def set_enable(self, enable):
|
|
86
|
+
if self._is_enabled != enable:
|
|
87
|
+
self._is_enabled = enable
|
|
88
|
+
self._update_ui()
|
|
89
|
+
|
|
90
|
+
def set_address(self, address):
|
|
91
|
+
for ui_elements in self._ui_elements:
|
|
92
|
+
ui_elements.address_spinner.setValue(address)
|
|
93
|
+
|
|
94
|
+
def get_address(self):
|
|
95
|
+
if len(self._ui_elements) > 0:
|
|
96
|
+
return self._ui_elements[0].address_spinner.value()
|
|
97
|
+
else:
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
def set_interfaces(self, interface_items, index):
|
|
101
|
+
new_index = 0
|
|
102
|
+
if index is not None:
|
|
103
|
+
new_index = index + 1
|
|
104
|
+
|
|
105
|
+
for ui_elements in self._ui_elements:
|
|
106
|
+
combo = ui_elements.interface_combo
|
|
107
|
+
|
|
108
|
+
combo.clear()
|
|
109
|
+
combo.addItem(self.INTERFACE_PROMPT_TEXT)
|
|
110
|
+
combo.addItems(interface_items)
|
|
111
|
+
combo.setCurrentIndex(new_index)
|
|
112
|
+
|
|
113
|
+
def get_interface(self):
|
|
114
|
+
if len(self._ui_elements) > 0:
|
|
115
|
+
interface = self._ui_elements[0].interface_combo.currentText()
|
|
116
|
+
if interface == self.INTERFACE_PROMPT_TEXT:
|
|
117
|
+
self._selected_interface = None
|
|
118
|
+
else:
|
|
119
|
+
return interface
|
|
120
|
+
else:
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
def _connect_button_click_handler(self):
|
|
124
|
+
self.connect_button_clicked.emit()
|
|
125
|
+
|
|
126
|
+
def _scan_button_click_handler(self):
|
|
127
|
+
self.scan_button_clicked.emit(self.get_address())
|
|
128
|
+
|
|
129
|
+
def _address_changed_handler(self, value):
|
|
130
|
+
for ui_elements in self._ui_elements:
|
|
131
|
+
if value != ui_elements.address_spinner.value():
|
|
132
|
+
ui_elements.address_spinner.setValue(value)
|
|
133
|
+
|
|
134
|
+
def _address_edited_handler(self):
|
|
135
|
+
# Find out if one of the addresses has changed and what the new value is
|
|
136
|
+
value = 0
|
|
137
|
+
is_changed = False
|
|
138
|
+
for ui_elements in self._ui_elements:
|
|
139
|
+
if ui_elements.address_spinner.is_text_different_from_value():
|
|
140
|
+
value = ui_elements.address_spinner.value()
|
|
141
|
+
is_changed = True
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
# Set the new value
|
|
145
|
+
if is_changed:
|
|
146
|
+
for ui_elements in self._ui_elements:
|
|
147
|
+
if value != ui_elements.address_spinner.value():
|
|
148
|
+
ui_elements.address_spinner.setValue(value)
|
|
149
|
+
|
|
150
|
+
def _interface_combo_current_index_changed_handler(self, interface):
|
|
151
|
+
interface_s = str(interface)
|
|
152
|
+
can_connect = interface != self.INTERFACE_PROMPT_TEXT
|
|
153
|
+
for ui_elements in self._ui_elements:
|
|
154
|
+
combo = ui_elements.interface_combo
|
|
155
|
+
if combo.currentText != interface_s:
|
|
156
|
+
combo.setCurrentText(interface_s)
|
|
157
|
+
ui_elements.connect_button.setEnabled(can_connect)
|
|
158
|
+
|
|
159
|
+
def _update_ui(self):
|
|
160
|
+
if self._is_enabled:
|
|
161
|
+
if self._state == self.UIState.DISCONNECTED:
|
|
162
|
+
can_connect = self.get_interface() is not None
|
|
163
|
+
for ui_elements in self._ui_elements:
|
|
164
|
+
ui_elements.connect_button.setText("Connect")
|
|
165
|
+
ui_elements.connect_button.setToolTip("Connect to the Crazyflie on the selected interface (Ctrl+I)")
|
|
166
|
+
ui_elements.connect_button.setEnabled(can_connect)
|
|
167
|
+
ui_elements.scan_button.setText("Scan")
|
|
168
|
+
ui_elements.scan_button.setEnabled(True)
|
|
169
|
+
ui_elements.address_spinner.setEnabled(True)
|
|
170
|
+
ui_elements.interface_combo.setEnabled(True)
|
|
171
|
+
elif self._state == self.UIState.CONNECTED:
|
|
172
|
+
for ui_elements in self._ui_elements:
|
|
173
|
+
ui_elements.connect_button.setText("Disconnect")
|
|
174
|
+
ui_elements.connect_button.setToolTip("Disconnect from the Crazyflie (Ctrl+I)")
|
|
175
|
+
ui_elements.scan_button.setEnabled(False)
|
|
176
|
+
ui_elements.address_spinner.setEnabled(False)
|
|
177
|
+
ui_elements.interface_combo.setEnabled(False)
|
|
178
|
+
elif self._state == self.UIState.CONNECTING:
|
|
179
|
+
for ui_elements in self._ui_elements:
|
|
180
|
+
ui_elements.connect_button.setText("Cancel")
|
|
181
|
+
ui_elements.connect_button.setToolTip("Cancel connecting to the Crazyflie")
|
|
182
|
+
ui_elements.scan_button.setEnabled(False)
|
|
183
|
+
ui_elements.address_spinner.setEnabled(False)
|
|
184
|
+
ui_elements.interface_combo.setEnabled(False)
|
|
185
|
+
elif self._state == self.UIState.SCANNING:
|
|
186
|
+
for ui_elements in self._ui_elements:
|
|
187
|
+
ui_elements.connect_button.setText("Connect")
|
|
188
|
+
ui_elements.connect_button.setEnabled(False)
|
|
189
|
+
ui_elements.scan_button.setText("Scanning...")
|
|
190
|
+
ui_elements.scan_button.setEnabled(False)
|
|
191
|
+
ui_elements.address_spinner.setEnabled(False)
|
|
192
|
+
ui_elements.interface_combo.setEnabled(False)
|
|
193
|
+
else:
|
|
194
|
+
for ui_elements in self._ui_elements:
|
|
195
|
+
ui_elements.connect_button.setEnabled(False)
|
|
196
|
+
ui_elements.scan_button.setEnabled(False)
|
|
197
|
+
ui_elements.address_spinner.setEnabled(False)
|
|
198
|
+
ui_elements.interface_combo.setEnabled(False)
|
cfclient/ui/dialogs/about.py
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
8
8
|
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
9
9
|
#
|
|
10
|
-
# Copyright (C) 2011-
|
|
10
|
+
# Copyright (C) 2011-2023 Bitcraze AB
|
|
11
11
|
#
|
|
12
12
|
# Crazyflie Nano Quadcopter Client
|
|
13
13
|
#
|
|
@@ -31,11 +31,12 @@ import sys
|
|
|
31
31
|
|
|
32
32
|
import cfclient
|
|
33
33
|
import cflib.crtp
|
|
34
|
-
from
|
|
35
|
-
from
|
|
36
|
-
from
|
|
37
|
-
from
|
|
38
|
-
from
|
|
34
|
+
from PyQt6.QtCore import QT_VERSION_STR
|
|
35
|
+
from PyQt6.QtCore import PYQT_VERSION_STR
|
|
36
|
+
from PyQt6 import QtWidgets
|
|
37
|
+
from PyQt6 import uic
|
|
38
|
+
from PyQt6.QtCore import pyqtSignal
|
|
39
|
+
from cflib.crazyflie.mem import MemoryElement
|
|
39
40
|
|
|
40
41
|
__author__ = 'Bitcraze AB'
|
|
41
42
|
__all__ = ['AboutDialog']
|
|
@@ -64,8 +65,13 @@ PyQt: {pyqt_version}<br>
|
|
|
64
65
|
<b>Crazyflie</b><br>
|
|
65
66
|
Connected: {uri}<br>
|
|
66
67
|
Firmware: {firmware}<br>
|
|
68
|
+
<br>
|
|
69
|
+
<b>Decks found</b><br>
|
|
70
|
+
{decks}
|
|
71
|
+
<br>
|
|
67
72
|
<b>Sensors found</b><br>
|
|
68
73
|
{imu_sensors}
|
|
74
|
+
<br>
|
|
69
75
|
<b>Sensors tests</b><br>
|
|
70
76
|
{imu_sensor_tests}
|
|
71
77
|
"""
|
|
@@ -76,24 +82,12 @@ DEVICE_FORMAT = "{}: ({}) {}<br>"
|
|
|
76
82
|
IMU_SENSORS_FORMAT = "{}: {}<br>"
|
|
77
83
|
SENSOR_TESTS_FORMAT = "{}: {}<br>"
|
|
78
84
|
FIRMWARE_FORMAT = "{:x}{:x} ({})"
|
|
79
|
-
|
|
80
|
-
CREDITS_FORMAT = """
|
|
81
|
-
<b>Contributions</b><br>
|
|
82
|
-
{contribs}
|
|
83
|
-
<br><br>
|
|
84
|
-
<b>Used libraries</b><br>
|
|
85
|
-
<a href="http://qt-project.org/">QT</a><br>
|
|
86
|
-
<a href="http://www.riverbankcomputing.co.uk/software/pyqt/intro">PyQT</a><br>
|
|
87
|
-
<a href="http://pysdl2.readthedocs.org">PySDL2</a><br>
|
|
88
|
-
<a href="http://www.pyqtgraph.org/">PyQtGraph</a><br>
|
|
89
|
-
<a href="http://marble.kde.org/">KDE Marble</a><br>
|
|
90
|
-
<a href="http://sourceforge.net/projects/pyusb/">PyUSB</a><br>
|
|
91
|
-
<a href="http://www.python.org/">Python</a><br>
|
|
92
|
-
"""
|
|
85
|
+
DECK_FORMAT = "{}: rev={}, adr={}<br>"
|
|
93
86
|
|
|
94
87
|
|
|
95
88
|
class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
96
89
|
_disconnected_signal = pyqtSignal(str)
|
|
90
|
+
_cb_deck_data_updated_signal = pyqtSignal(object)
|
|
97
91
|
|
|
98
92
|
"""Crazyflie client About box for debugging and information"""
|
|
99
93
|
|
|
@@ -107,10 +101,13 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
107
101
|
self._interface_text = ""
|
|
108
102
|
self._imu_sensors_text = ""
|
|
109
103
|
self._imu_sensor_test_text = ""
|
|
104
|
+
self._decks_text = ""
|
|
110
105
|
self._uri = None
|
|
111
106
|
self._fw_rev0 = None
|
|
112
107
|
self._fw_rev1 = None
|
|
113
108
|
self._fw_modified = None
|
|
109
|
+
self._firmware = None
|
|
110
|
+
|
|
114
111
|
self._helper = helper
|
|
115
112
|
|
|
116
113
|
helper.cf.param.add_update_callback(
|
|
@@ -124,18 +121,7 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
124
121
|
self._disconnected_signal.connect(self._disconnected)
|
|
125
122
|
helper.cf.disconnected.add_callback(self._disconnected_signal.emit)
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
credits = ""
|
|
129
|
-
try:
|
|
130
|
-
with open("CREDITS.txt", encoding='utf-8', mode='r') as f:
|
|
131
|
-
for line in f:
|
|
132
|
-
credits += "{}<br>".format(line)
|
|
133
|
-
except IOError:
|
|
134
|
-
credits = ""
|
|
135
|
-
|
|
136
|
-
self._credits.setHtml(
|
|
137
|
-
CREDITS_FORMAT.format(contribs=credits)
|
|
138
|
-
)
|
|
124
|
+
self._cb_deck_data_updated_signal.connect(self._deck_data_updated)
|
|
139
125
|
|
|
140
126
|
def showEvent(self, event):
|
|
141
127
|
"""Event when the about box is shown"""
|
|
@@ -144,7 +130,6 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
144
130
|
for key in list(interface_status.keys()):
|
|
145
131
|
self._interface_text += INTERFACE_FORMAT.format(
|
|
146
132
|
key, interface_status[key])
|
|
147
|
-
firmware = None
|
|
148
133
|
|
|
149
134
|
self._device_text = ""
|
|
150
135
|
devs = self._helper.inputDeviceReader.available_devices()
|
|
@@ -163,10 +148,16 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
163
148
|
self._input_readers_text = "None<br>"
|
|
164
149
|
|
|
165
150
|
if self._uri:
|
|
166
|
-
|
|
151
|
+
self._firmware = FIRMWARE_FORMAT.format(
|
|
167
152
|
self._fw_rev0,
|
|
168
153
|
self._fw_rev1,
|
|
169
154
|
"MODIFIED" if self._fw_modified else "CLEAN")
|
|
155
|
+
|
|
156
|
+
self._request_deck_data_update()
|
|
157
|
+
|
|
158
|
+
self._update_debug_info_view()
|
|
159
|
+
|
|
160
|
+
def _update_debug_info_view(self):
|
|
170
161
|
self._debug_out.setHtml(
|
|
171
162
|
DEBUG_INFO_FORMAT.format(
|
|
172
163
|
version=cfclient.VERSION,
|
|
@@ -180,9 +171,10 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
180
171
|
input_devices=self._device_text,
|
|
181
172
|
input_readers=self._input_readers_text,
|
|
182
173
|
uri=self._uri,
|
|
183
|
-
firmware=
|
|
174
|
+
firmware=self._firmware,
|
|
184
175
|
imu_sensors=self._imu_sensors_text,
|
|
185
|
-
imu_sensor_tests=self._imu_sensor_test_text
|
|
176
|
+
imu_sensor_tests=self._imu_sensor_test_text,
|
|
177
|
+
decks=self._decks_text))
|
|
186
178
|
|
|
187
179
|
def _connected(self, uri):
|
|
188
180
|
"""Callback when Crazyflie is connected"""
|
|
@@ -216,7 +208,32 @@ class AboutDialog(QtWidgets.QWidget, about_widget_class):
|
|
|
216
208
|
self._interface_text = ""
|
|
217
209
|
self._imu_sensors_text = ""
|
|
218
210
|
self._imu_sensor_test_text = ""
|
|
211
|
+
self._decks_text = ""
|
|
219
212
|
self._uri = None
|
|
220
213
|
self._fw_rev1 = None
|
|
221
214
|
self._fw_rev0 = None
|
|
222
215
|
self._fw_modified = None
|
|
216
|
+
self._firmware = None
|
|
217
|
+
|
|
218
|
+
def _request_deck_data_update(self):
|
|
219
|
+
self._decks_text = ""
|
|
220
|
+
# Query both 1-Wire and DeckCtrl memories for deck information
|
|
221
|
+
mems = self._helper.cf.mem.get_mems(MemoryElement.TYPE_1W)
|
|
222
|
+
mems += self._helper.cf.mem.get_mems(MemoryElement.TYPE_DECKCTRL)
|
|
223
|
+
for mem in mems:
|
|
224
|
+
mem.update(self._cb_deck_data_updated_signal.emit)
|
|
225
|
+
|
|
226
|
+
def _deck_data_updated(self, deck_data):
|
|
227
|
+
name = 'N/A'
|
|
228
|
+
if "Board name" in deck_data.elements:
|
|
229
|
+
name = deck_data.elements["Board name"]
|
|
230
|
+
|
|
231
|
+
rev = 'N/A'
|
|
232
|
+
if "Board revision" in deck_data.elements:
|
|
233
|
+
rev = deck_data.elements["Board revision"]
|
|
234
|
+
|
|
235
|
+
# OWElement has addr attribute, DeckCtrlElement does not
|
|
236
|
+
addr = getattr(deck_data, 'addr', 'N/A')
|
|
237
|
+
self._decks_text += DECK_FORMAT.format(name, rev, addr)
|
|
238
|
+
|
|
239
|
+
self._update_debug_info_view()
|
cfclient/ui/dialogs/about.ui
CHANGED
|
@@ -39,14 +39,20 @@
|
|
|
39
39
|
<item row="1" column="0">
|
|
40
40
|
<widget class="QLabel" name="label_2">
|
|
41
41
|
<property name="text">
|
|
42
|
-
<string>Copyright (c) 2011-
|
|
42
|
+
<string>Copyright (c) 2011-2023, Bitcraze AB</string>
|
|
43
43
|
</property>
|
|
44
44
|
</widget>
|
|
45
45
|
</item>
|
|
46
46
|
<item row="2" column="0">
|
|
47
47
|
<widget class="QLabel" name="label">
|
|
48
48
|
<property name="text">
|
|
49
|
-
<string><html><head/><body><p><span style=" font-size:12pt;">The Crazyflie client is a multi-platform client<br/>for controlling, bootloading and logging the Crazyflie.<br/>For more info visit our homepage.</span></p><p><a href="http://www.bitcraze.io"><span style=" text-decoration: underline; color:#0000ff;">Bitcraze Homepage</span></a><span style=" font-size:12pt;"><br/></span><a href="
|
|
49
|
+
<string><html><head/><body><p><span style=" font-size:12pt;">The Crazyflie client is a multi-platform client<br/>for controlling, bootloading and logging the Crazyflie.<br/>For more info visit our homepage.</span></p><p><a href="http://www.bitcraze.io"><span style=" text-decoration: underline; color:#0000ff;">Bitcraze Homepage</span></a><span style=" font-size:12pt;"><br/></span><a href="https://www.bitcraze.io/documentation/repository/crazyflie-clients-python/master/userguides/userguide_client/"><span style=" text-decoration: underline; color:#0000ff;">Client documentation</span></a><span style=" font-size:12pt;"><br/></span><a href="http://discussions.bitcraze.io"><span style=" text-decoration: underline; color:#0000ff;">Bitcraze Discussions</span></a></p><p><br/></p></body></html></string>
|
|
50
|
+
</property>
|
|
51
|
+
<property name="openExternalLinks">
|
|
52
|
+
<bool>true</bool>
|
|
53
|
+
</property>
|
|
54
|
+
<property name="textInteractionFlags">
|
|
55
|
+
<set>Qt::TextBrowserInteraction</set>
|
|
50
56
|
</property>
|
|
51
57
|
</widget>
|
|
52
58
|
</item>
|
|
@@ -84,7 +90,21 @@
|
|
|
84
90
|
<item>
|
|
85
91
|
<layout class="QGridLayout" name="gridLayout_3">
|
|
86
92
|
<item row="0" column="0">
|
|
87
|
-
<widget class="
|
|
93
|
+
<widget class="QTextBrowser" name="_credits">
|
|
94
|
+
<property name="html">
|
|
95
|
+
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
|
96
|
+
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
|
97
|
+
p, li { white-space: pre-wrap; }
|
|
98
|
+
</style></head><body style=" font-family:'Cantarell'; font-size:11pt; font-weight:400; font-style:normal;">
|
|
99
|
+
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Contributions</span><br />We are very grateful for all the contributions we have received for this project. Below is a link showing all of the users that have contributed to the Crazyflie client. <br /><br />Thank you! <br /><br /><a href="https://github.com/bitcraze/crazyflie-clients-python/graphs/contributors"><span style=" text-decoration: underline; color:#1b6acb;">Contributor information on GitHub</span></a> <br /><br /></p></body></html></string>
|
|
100
|
+
</property>
|
|
101
|
+
<property name="textInteractionFlags">
|
|
102
|
+
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
|
103
|
+
</property>
|
|
104
|
+
<property name="openExternalLinks">
|
|
105
|
+
<bool>true</bool>
|
|
106
|
+
</property>
|
|
107
|
+
</widget>
|
|
88
108
|
</item>
|
|
89
109
|
</layout>
|
|
90
110
|
</item>
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# || ____ _ __
|
|
4
|
+
# +------+ / __ )(_) /_______________ _____ ___
|
|
5
|
+
# | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \
|
|
6
|
+
# +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/
|
|
7
|
+
# || || /_____/_/\__/\___/_/ \__,_/ /___/\___/
|
|
8
|
+
#
|
|
9
|
+
# Copyright (C) 2018-2023 Bitcraze AB
|
|
10
|
+
#
|
|
11
|
+
# This program is free software; you can redistribute it and/or
|
|
12
|
+
# modify it under the terms of the GNU General Public License
|
|
13
|
+
# as published by the Free Software Foundation; either version 2
|
|
14
|
+
# of the License, or (at your option) any later version.
|
|
15
|
+
#
|
|
16
|
+
# This program is distributed in the hope that it will be useful,
|
|
17
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
18
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
19
|
+
# GNU General Public License for more details.
|
|
20
|
+
# You should have received a copy of the GNU General Public License
|
|
21
|
+
# along with this program; if not, write to the Free Software
|
|
22
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
23
|
+
# MA 02110-1301, USA.
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
Dialog box used to configure anchor positions. Used from the LPS tab.
|
|
27
|
+
"""
|
|
28
|
+
import logging
|
|
29
|
+
|
|
30
|
+
import cfclient
|
|
31
|
+
from cfclient.utils.logconfigreader import FILE_REGEX_YAML
|
|
32
|
+
from PyQt6 import QtWidgets
|
|
33
|
+
from PyQt6 import uic
|
|
34
|
+
from PyQt6.QtCore import QAbstractTableModel, QVariant, Qt
|
|
35
|
+
from PyQt6.QtGui import QBrush, QColor
|
|
36
|
+
from PyQt6.QtWidgets import QInputDialog, QFileDialog
|
|
37
|
+
import yaml
|
|
38
|
+
import os
|
|
39
|
+
|
|
40
|
+
__author__ = 'Bitcraze AB'
|
|
41
|
+
__all__ = ['AnchorPositionDialog']
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger(__name__)
|
|
44
|
+
|
|
45
|
+
(anchor_postiong_widget_class, connect_widget_base_class) = (
|
|
46
|
+
uic.loadUiType(
|
|
47
|
+
cfclient.module_path + '/ui/dialogs/anchor_position_dialog.ui')
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AnchorPositionConfigTableModel(QAbstractTableModel):
|
|
52
|
+
def __init__(self, headers, parent=None, *args):
|
|
53
|
+
QAbstractTableModel.__init__(self, parent)
|
|
54
|
+
self._anchor_positions = []
|
|
55
|
+
self._headers = headers
|
|
56
|
+
self._latest_known_anchor_positions = {}
|
|
57
|
+
|
|
58
|
+
self._green_brush = QBrush(QColor(200, 255, 200))
|
|
59
|
+
self._red_brush = QBrush(QColor(255, 200, 200))
|
|
60
|
+
|
|
61
|
+
def rowCount(self, parent=None, *args, **kwargs):
|
|
62
|
+
return len(self._anchor_positions)
|
|
63
|
+
|
|
64
|
+
def columnCount(self, parent=None, *args, **kwargs):
|
|
65
|
+
return len(self._headers)
|
|
66
|
+
|
|
67
|
+
def data(self, index, role=None):
|
|
68
|
+
value = self._anchor_positions[index.row()][index.column()]
|
|
69
|
+
if index.isValid():
|
|
70
|
+
if index.column() == 0:
|
|
71
|
+
if role == Qt.ItemDataRole.CheckStateRole:
|
|
72
|
+
return QVariant(value)
|
|
73
|
+
elif index.column() == 1:
|
|
74
|
+
if role == Qt.ItemDataRole.DisplayRole:
|
|
75
|
+
return QVariant(value)
|
|
76
|
+
else:
|
|
77
|
+
if role == Qt.ItemDataRole.DisplayRole:
|
|
78
|
+
return QVariant('%.2f' % (value))
|
|
79
|
+
elif role == Qt.ItemDataRole.EditRole:
|
|
80
|
+
return QVariant(value)
|
|
81
|
+
elif role == Qt.ItemDataRole.BackgroundRole:
|
|
82
|
+
return self._get_background(index.row(), index.column())
|
|
83
|
+
|
|
84
|
+
return QVariant()
|
|
85
|
+
|
|
86
|
+
def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
|
|
87
|
+
if not index.isValid():
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
self._anchor_positions[index.row()][index.column()] = value
|
|
91
|
+
return True
|
|
92
|
+
|
|
93
|
+
def headerData(self, col, orientation, role=None):
|
|
94
|
+
if orientation == Qt.Orientation.Horizontal and role == Qt.ItemDataRole.DisplayRole:
|
|
95
|
+
return QVariant(self._headers[col])
|
|
96
|
+
return QVariant()
|
|
97
|
+
|
|
98
|
+
def flags(self, index):
|
|
99
|
+
if not index.isValid():
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
if index.column() == 0:
|
|
103
|
+
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable
|
|
104
|
+
elif index.column() == 1:
|
|
105
|
+
return Qt.ItemFlag.ItemIsEnabled
|
|
106
|
+
else:
|
|
107
|
+
return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsEditable
|
|
108
|
+
|
|
109
|
+
def add_anchor(self, anchor_id, x=0.0, y=0.0, z=0.0):
|
|
110
|
+
if not self._id_exist(anchor_id):
|
|
111
|
+
self.layoutAboutToBeChanged.emit()
|
|
112
|
+
self._anchor_positions.append([0, anchor_id, x, y, z])
|
|
113
|
+
self._anchor_positions.sort(key=lambda row: row[1])
|
|
114
|
+
self.layoutChanged.emit()
|
|
115
|
+
|
|
116
|
+
def replace_anchors_from_latest_known_positions(self):
|
|
117
|
+
self.replace_anchor_positions(self._latest_known_anchor_positions)
|
|
118
|
+
|
|
119
|
+
def replace_anchor_positions(self, anchor_positions):
|
|
120
|
+
self.layoutAboutToBeChanged.emit()
|
|
121
|
+
self._anchor_positions = []
|
|
122
|
+
for id, position in anchor_positions.items():
|
|
123
|
+
self.add_anchor(id, x=position[0], y=position[1], z=position[2])
|
|
124
|
+
self.layoutChanged.emit()
|
|
125
|
+
|
|
126
|
+
def get_anchor_postions(self):
|
|
127
|
+
result = {}
|
|
128
|
+
for row in self._anchor_positions:
|
|
129
|
+
result[row[1]] = (row[2], row[3], row[4])
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
def anchor_postions_updated(self, anchor_positions):
|
|
133
|
+
self.layoutAboutToBeChanged.emit()
|
|
134
|
+
self._latest_known_anchor_positions = anchor_positions
|
|
135
|
+
self.layoutChanged.emit()
|
|
136
|
+
|
|
137
|
+
def remove_selected_anchors(self):
|
|
138
|
+
self.layoutAboutToBeChanged.emit()
|
|
139
|
+
self._anchor_positions = list(filter(
|
|
140
|
+
lambda row: row[0] == 0, self._anchor_positions))
|
|
141
|
+
self.layoutChanged.emit()
|
|
142
|
+
|
|
143
|
+
def _id_exist(self, anchor_id):
|
|
144
|
+
for anchor in self._anchor_positions:
|
|
145
|
+
if anchor[1] == anchor_id:
|
|
146
|
+
return True
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
def _get_background(self, row, col):
|
|
150
|
+
id = self._anchor_positions[row][1]
|
|
151
|
+
if id in self._latest_known_anchor_positions:
|
|
152
|
+
current_value = self._anchor_positions[row][col]
|
|
153
|
+
latest_value = self._latest_known_anchor_positions[id][col - 2]
|
|
154
|
+
|
|
155
|
+
if abs(current_value - latest_value) < 0.005:
|
|
156
|
+
return self._green_brush
|
|
157
|
+
else:
|
|
158
|
+
return self._red_brush
|
|
159
|
+
|
|
160
|
+
return QVariant()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class AnchorPositionDialog(QtWidgets.QWidget, anchor_postiong_widget_class):
|
|
164
|
+
|
|
165
|
+
def __init__(self, lps_tab, helper):
|
|
166
|
+
super(AnchorPositionDialog, self).__init__()
|
|
167
|
+
self.setupUi(self)
|
|
168
|
+
|
|
169
|
+
self._lps_tab = lps_tab
|
|
170
|
+
self._helper = helper
|
|
171
|
+
|
|
172
|
+
self._headers = ['', 'id', 'x', 'y', 'z']
|
|
173
|
+
self._data_model = AnchorPositionConfigTableModel(self._headers, self)
|
|
174
|
+
self._table_view.setModel(self._data_model)
|
|
175
|
+
|
|
176
|
+
self._table_view.verticalHeader().setVisible(False)
|
|
177
|
+
|
|
178
|
+
header = self._table_view.horizontalHeader()
|
|
179
|
+
header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
|
180
|
+
header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
|
181
|
+
header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
|
182
|
+
header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
|
183
|
+
header.setSectionResizeMode(4, QtWidgets.QHeaderView.ResizeMode.Stretch)
|
|
184
|
+
|
|
185
|
+
self._add_anchor_button.clicked.connect(
|
|
186
|
+
self._add_anchor_button_clicked)
|
|
187
|
+
self._remove_anchors_button.clicked.connect(
|
|
188
|
+
self._data_model.remove_selected_anchors)
|
|
189
|
+
self._get_from_anchors_button.clicked.connect(
|
|
190
|
+
self._get_from_anchors_button_clicked)
|
|
191
|
+
self._write_to_anchors_button.clicked.connect(
|
|
192
|
+
self._write_to_anchors_button_clicked)
|
|
193
|
+
self._close_button.clicked.connect(self.close)
|
|
194
|
+
self._load_button.clicked.connect(
|
|
195
|
+
self._load_button_clicked)
|
|
196
|
+
self._save_button.clicked.connect(
|
|
197
|
+
self._save_button_clicked)
|
|
198
|
+
|
|
199
|
+
def _add_anchor_button_clicked(self):
|
|
200
|
+
anchor_id, ok = QInputDialog.getInt(
|
|
201
|
+
self, "New anchor", "Enter id", min=0, max=255)
|
|
202
|
+
if ok:
|
|
203
|
+
self._data_model.add_anchor(anchor_id)
|
|
204
|
+
|
|
205
|
+
def _get_from_anchors_button_clicked(self):
|
|
206
|
+
self._data_model.replace_anchors_from_latest_known_positions()
|
|
207
|
+
|
|
208
|
+
def _write_to_anchors_button_clicked(self):
|
|
209
|
+
anchor_positions = self._data_model.get_anchor_postions()
|
|
210
|
+
self._lps_tab.write_positions_to_anchors(anchor_positions)
|
|
211
|
+
|
|
212
|
+
def anchor_postions_updated(self, anchor_positions):
|
|
213
|
+
self._data_model.anchor_postions_updated(anchor_positions)
|
|
214
|
+
|
|
215
|
+
def _load_button_clicked(self):
|
|
216
|
+
names = QFileDialog.getOpenFileName(self, 'Open file', self._helper.current_folder, FILE_REGEX_YAML)
|
|
217
|
+
|
|
218
|
+
if names[0] == '':
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
self._helper.current_folder = os.path.dirname(names[0])
|
|
222
|
+
|
|
223
|
+
f = open(names[0], 'r')
|
|
224
|
+
with f:
|
|
225
|
+
data = yaml.safe_load(f)
|
|
226
|
+
|
|
227
|
+
anchor_positions = {}
|
|
228
|
+
for id, pos in data.items():
|
|
229
|
+
anchor_positions[id] = (pos['x'], pos['y'], pos['z'])
|
|
230
|
+
self._data_model.replace_anchor_positions(anchor_positions)
|
|
231
|
+
|
|
232
|
+
def _save_button_clicked(self):
|
|
233
|
+
anchor_positions = self._data_model.get_anchor_postions()
|
|
234
|
+
data = {}
|
|
235
|
+
for id, pos in anchor_positions.items():
|
|
236
|
+
data[id] = {'x': pos[0], 'y': pos[1], 'z': pos[2]}
|
|
237
|
+
|
|
238
|
+
names = QFileDialog.getSaveFileName(self, 'Save file', self._helper.current_folder, FILE_REGEX_YAML)
|
|
239
|
+
|
|
240
|
+
if names[0] == '':
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
self._helper.current_folder = os.path.dirname(names[0])
|
|
244
|
+
|
|
245
|
+
if not names[0].endswith(".yaml") and names[0].find(".") < 0:
|
|
246
|
+
filename = names[0] + ".yaml"
|
|
247
|
+
else:
|
|
248
|
+
filename = names[0]
|
|
249
|
+
|
|
250
|
+
f = open(filename, 'w')
|
|
251
|
+
with f:
|
|
252
|
+
yaml.dump(data, f)
|