1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2025-02-20 10:54:14 +01:00

OP-138 Mixer: fixed wing works, and Quad-Plus and Quad-X configurations are supported. Not tested for obvious reasons, since I am working in the train.

You can check how this works for you and report bugs. Layout works fine with screens as small as 800x600.




git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@1791 ebee16cc-31ac-478f-84a7-5cbb03baadba
This commit is contained in:
edouard 2010-09-28 18:27:34 +00:00 committed by edouard
parent 6f1f990d85
commit bd6754cd6b
6 changed files with 1046 additions and 600 deletions

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>720</width>
<height>480</height>
<height>508</height>
</rect>
</property>
<property name="windowTitle">
@ -69,7 +69,7 @@
<item>
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="fixedWing">
<property name="enabled">
@ -247,50 +247,84 @@
<string>Elevon Mix</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QLabel" name="elevonLabel1">
<property name="text">
<string>Roll / Pitch</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QSlider" name="elevonSlider1">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_14">
<item>
<widget class="QLabel" name="elevonLabel1">
<property name="minimumSize">
<size>
<width>65</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Rudder %</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="elevonSlider1">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>50</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QSlider" name="elevonSlider2">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QLabel" name="elevonLabel2">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Pitch %</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="elevonSlider2">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_25">
<property name="text">
<string>50</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="elevonLabel2">
<property name="text">
<string>Pitch 0%</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
@ -444,15 +478,8 @@
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Feed Forward</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="horizontalSlider">
<item row="2" column="1">
<widget class="QSlider" name="feedForwardSlider">
<property name="maximum">
<number>100</number>
</property>
@ -467,25 +494,57 @@
</property>
</widget>
</item>
<item row="1" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_21">
<property name="text">
<string>Accel Time Constant</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_4"/>
</item>
<item row="2" column="0">
<item row="5" column="0">
<widget class="QLabel" name="label_22">
<property name="text">
<string>Decel Time Constant</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_5"/>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QLabel" name="label_20">
<property name="text">
<string>FeedForward </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="feedForwardValue">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>000</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="accelTime">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="decelTime">
<property name="maximum">
<double>1000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
@ -528,7 +587,7 @@
</widget>
</item>
<item>
<widget class="MixerCurveWidget" name="widget" native="true">
<widget class="MixerCurveWidget" name="multiThrottleCurve" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
@ -640,7 +699,7 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox">
<widget class="QComboBox" name="multiMotor1">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -656,7 +715,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_2">
<widget class="QComboBox" name="multiMotor2">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -672,7 +731,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_3">
<widget class="QComboBox" name="multiMotor3">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -688,7 +747,7 @@
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_4">
<widget class="QComboBox" name="multiMotor4">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -714,7 +773,7 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox_5">
<widget class="QComboBox" name="multiMotor5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -736,7 +795,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_6">
<widget class="QComboBox" name="multiMotor6">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -752,7 +811,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="comboBox_7">
<widget class="QComboBox" name="multiMotor7">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -768,7 +827,7 @@
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="comboBox_8">
<widget class="QComboBox" name="multiMotor8">
<property name="font">
<font>
<pointsize>8</pointsize>
@ -916,5 +975,53 @@
</hint>
</hints>
</connection>
<connection>
<sender>elevonSlider2</sender>
<signal>valueChanged(int)</signal>
<receiver>label_25</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>127</x>
<y>105</y>
</hint>
<hint type="destinationlabel">
<x>127</x>
<y>105</y>
</hint>
</hints>
</connection>
<connection>
<sender>elevonSlider1</sender>
<signal>valueChanged(int)</signal>
<receiver>label_18</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>127</x>
<y>105</y>
</hint>
<hint type="destinationlabel">
<x>127</x>
<y>105</y>
</hint>
</hints>
</connection>
<connection>
<sender>feedForwardSlider</sender>
<signal>valueChanged(int)</signal>
<receiver>feedForwardValue</receiver>
<slot>setNum(int)</slot>
<hints>
<hint type="sourcelabel">
<x>214</x>
<y>157</y>
</hint>
<hint type="destinationlabel">
<x>144</x>
<y>161</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -40,24 +40,11 @@ ConfigAirframeWidget::ConfigAirframeWidget(QWidget *parent) : ConfigTaskWidget(p
m_aircraft = new Ui_AircraftWidget();
m_aircraft->setupUi(this);
// Now connect the widget to the ManualControlCommand / Channel UAVObject
/*
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
UAVObject *obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("SystemSettings")));
QString fieldName = QString("AirframeType");
UAVObjectField *field = obj->getField(fieldName);
m_aircraft->aircraftType->addItems(field->getOptions());
*/
mixerTypes << "Mixer0Type" << "Mixer1Type" << "Mixer2Type" << "Mixer3Type"
<< "Mixer4Type" << "Mixer5Type" << "Mixer6Type" << "Mixer7Type";
mixerVectors << "Mixer0Vector" << "Mixer1Vector" << "Mixer2Vector" << "Mixer3Vector"
<< "Mixer4Vector" << "Mixer5Vector" << "Mixer6Vector" << "Mixer7Vector";
QStringList airframeTypes;
airframeTypes << "Fixed Wing" << "Multirotor" << "Helicopter" << "Custom";
m_aircraft->aircraftType->addItems(airframeTypes);
@ -71,6 +58,8 @@ ConfigAirframeWidget::ConfigAirframeWidget(QWidget *parent) : ConfigTaskWidget(p
multiRotorTypes << "Quad +" << "Quad X" << "Hexacopter" << "Octocopter";
m_aircraft->multirotorFrameType->addItems(multiRotorTypes);
QStringList channels;
channels << "None" << "Channel0" << "Channel1" << "Channel2" <<
"Channel3" << "Channel4" << "Channel5" << "Channel6" << "Channel7";
@ -81,16 +70,37 @@ ConfigAirframeWidget::ConfigAirframeWidget(QWidget *parent) : ConfigTaskWidget(p
m_aircraft->fwRudderChannel->addItems(channels);
m_aircraft->fwAileron1Channel->addItems(channels);
m_aircraft->fwAileron2Channel->addItems(channels);
m_aircraft->multiMotor1->addItems(channels);
m_aircraft->multiMotor2->addItems(channels);
m_aircraft->multiMotor3->addItems(channels);
m_aircraft->multiMotor4->addItems(channels);
m_aircraft->multiMotor5->addItems(channels);
m_aircraft->multiMotor6->addItems(channels);
m_aircraft->multiMotor7->addItems(channels);
m_aircraft->multiMotor8->addItems(channels);
// Setup the Multirotor picture in the Quad settings interface
m_aircraft->quadShape->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_aircraft->quadShape->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
QSvgRenderer *renderer = new QSvgRenderer();
renderer->load(QString(":/configgadget/images/quad-shapes.svg"));
quad = new QGraphicsSvgItem();
quad->setSharedRenderer(renderer);
quad->setElementId("quad-plus");
QGraphicsScene *scene = new QGraphicsScene(this);
scene->addItem(quad);
scene->setSceneRect(quad->boundingRect());
m_aircraft->quadShape->setScene(scene);
requestAircraftUpdate();
connect(m_aircraft->saveAircraftToSD, SIGNAL(clicked()), this, SLOT(saveAircraftUpdate()));
connect(m_aircraft->saveAircraftToRAM, SIGNAL(clicked()), this, SLOT(sendAircraftUpdate()));
connect(m_aircraft->getAircraftCurrent, SIGNAL(clicked()), this, SLOT(requestAircraftUpdate()));
connect(m_aircraft->fixedWingType, SIGNAL(currentIndexChanged(QString)), this, SLOT(setupAirframeUI(QString)));
connect(m_aircraft->fwAileron1Channel, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleAileron2(int)));
connect(m_aircraft->fwElevator1Channel, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleElevator2(int)));
connect(m_aircraft->multirotorFrameType, SIGNAL(currentIndexChanged(QString)), this, SLOT(setupAirframeUI(QString)));
// connect(m_aircraft->fwAileron1Channel, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleAileron2(int)));
// connect(m_aircraft->fwElevator1Channel, SIGNAL(currentIndexChanged(int)), this, SLOT(toggleElevator2(int)));
connect(parent, SIGNAL(autopilotConnected()),this, SLOT(requestAircraftUpdate()));
@ -101,6 +111,22 @@ ConfigAirframeWidget::~ConfigAirframeWidget()
// Do nothing
}
void ConfigAirframeWidget::showEvent(QShowEvent *event)
{
Q_UNUSED(event)
// Thit fitInView method should only be called now, once the
// widget is shown, otherwise it cannot compute its values and
// the result is usually a ahrsbargraph that is way too small.
m_aircraft->quadShape->fitInView(quad, Qt::KeepAspectRatio);
}
void ConfigAirframeWidget::resizeEvent(QResizeEvent* event)
{
Q_UNUSED(event);
m_aircraft->quadShape->fitInView(quad, Qt::KeepAspectRatio);
}
void ConfigAirframeWidget::toggleAileron2(int index)
{
if (index) {
@ -140,13 +166,12 @@ void ConfigAirframeWidget::requestAircraftUpdate()
obj->requestUpdate();
UAVObjectField *field = obj->getField(QString("AirframeType"));
Q_ASSERT(field);
// m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText(field->getValue().toString()));
// At this stage, we will need to have some hardcoded settings in this code, this
// is not ideal, but here you go.
QString frameType = field->getValue().toString();
setupAirframeUI(frameType);
// Load the throttle curve for fixed wing frames:
// Load the Settings for fixed wing frames:
if (frameType.startsWith("FixedWing")) {
obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
@ -188,6 +213,127 @@ void ConfigAirframeWidget::requestAircraftUpdate()
Q_ASSERT(field);
m_aircraft->fwRudderChannel->setCurrentIndex(m_aircraft->fwRudderChannel->findText(field->getValue().toString()));
if (frameType == "FixedWingElevon") {
// If the airframe is elevon, restore the slider setting
// Find the channel number for Elevon1 (FixedWingRoll1)
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
int chMixerNumber = m_aircraft->fwAileron1Channel->currentIndex()-1;
field = obj->getField(mixerVectors.at(chMixerNumber));
int ti = field->getElementNames().indexOf("Roll");
m_aircraft->elevonSlider1->setValue(field->getDouble(ti)*100);
ti = field->getElementNames().indexOf("Pitch");
m_aircraft->elevonSlider2->setValue(field->getDouble(ti)*100);
}
if (frameType == "FixedWingVtail") {
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
int chMixerNumber = m_aircraft->fwElevator1Channel->currentIndex()-1;
field = obj->getField(mixerVectors.at(chMixerNumber));
int ti = field->getElementNames().indexOf("Yaw");
m_aircraft->elevonSlider1->setValue(field->getDouble(ti)*100);
ti = field->getElementNames().indexOf("Pitch");
m_aircraft->elevonSlider2->setValue(field->getDouble(ti)*100);
}
} else if (frameType == "QuadX" || frameType == "QuadP" ||
frameType == "Hexa" || frameType == "Octo" ) {
//////////////////////////////////////////////////////////////////
// Retrieve Multirotor settings
//////////////////////////////////////////////////////////////////
// First of all, curve settings:
obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
obj->requestUpdate();
field = obj->getField(QString("ThrottleCurve1"));
Q_ASSERT(field);
QList<double> curveValues;
// If the 1st element of the curve is <= -10, then the curve
// is a straight line (that's how the mixer works on the mainboard):
if (field->getValue(0).toInt() <= -10) {
for (double i=0; i<field->getNumElements(); i++) {
curveValues.append(-1.0 + 2*i/(field->getNumElements()-1));
}
} else {
for (unsigned int i=0; i < field->getNumElements(); i++) {
curveValues.append(field->getValue(i).toDouble());
}
}
m_aircraft->multiThrottleCurve->initCurve(curveValues);
obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("ActuatorSettings")));
Q_ASSERT(obj);
if (frameType == "QuadP") {
// Motors 1/2/3/4 are: N / E / S / W
field = obj->getField(QString("VTOLMotorN"));
Q_ASSERT(field);
m_aircraft->multiMotor1->setCurrentIndex(m_aircraft->multiMotor1->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorE"));
Q_ASSERT(field);
m_aircraft->multiMotor2->setCurrentIndex(m_aircraft->multiMotor2->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorS"));
Q_ASSERT(field);
m_aircraft->multiMotor3->setCurrentIndex(m_aircraft->multiMotor3->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorW"));
Q_ASSERT(field);
m_aircraft->multiMotor4->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
} else if (frameType == "QuadX") {
// Motors 1/2/3/4 are: NE / SE / SW / NW
field = obj->getField(QString("VTOLMotorNE"));
Q_ASSERT(field);
m_aircraft->multiMotor1->setCurrentIndex(m_aircraft->multiMotor1->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorSE"));
Q_ASSERT(field);
m_aircraft->multiMotor2->setCurrentIndex(m_aircraft->multiMotor2->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorSW"));
Q_ASSERT(field);
m_aircraft->multiMotor3->setCurrentIndex(m_aircraft->multiMotor3->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorNW"));
Q_ASSERT(field);
m_aircraft->multiMotor4->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
} else if (frameType == "Hexa") {
// Motors 1/2/3 4/5/6 are: ???
} else if (frameType == "Octo") {
// Motors 1 to 8 are N / NE / E / etc
field = obj->getField(QString("VTOLMotorN"));
Q_ASSERT(field);
m_aircraft->multiMotor1->setCurrentIndex(m_aircraft->multiMotor1->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorNE"));
Q_ASSERT(field);
m_aircraft->multiMotor2->setCurrentIndex(m_aircraft->multiMotor2->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorE"));
Q_ASSERT(field);
m_aircraft->multiMotor3->setCurrentIndex(m_aircraft->multiMotor3->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorSE"));
Q_ASSERT(field);
m_aircraft->multiMotor4->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorS"));
Q_ASSERT(field);
m_aircraft->multiMotor5->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorSW"));
Q_ASSERT(field);
m_aircraft->multiMotor6->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorW"));
Q_ASSERT(field);
m_aircraft->multiMotor7->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
field = obj->getField(QString("VTOLMotorNW"));
Q_ASSERT(field);
m_aircraft->multiMotor8->setCurrentIndex(m_aircraft->multiMotor4->findText(field->getValue().toString()));
}
obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
// Now, retrieve the Feedforward values:
field = obj->getField(QString("FeedForward"));
Q_ASSERT(field);
m_aircraft->feedForwardSlider->setValue(field->getDouble()*100);
field = obj->getField(QString("AccelTime"));
Q_ASSERT(field);
m_aircraft->accelTime->setValue(field->getDouble());
field = obj->getField(QString("DecelTime"));
Q_ASSERT(field);
m_aircraft->decelTime->setValue(field->getDouble());
}
}
@ -206,8 +352,13 @@ void ConfigAirframeWidget::setupAirframeUI(QString frameType)
m_aircraft->fwRudderLabel->setEnabled(true);
m_aircraft->fwElevator1Channel->setEnabled(true);
m_aircraft->fwElevator1Label->setEnabled(true);
//m_aircraft->fwElevator2Channel->setEnabled(true);
//m_aircraft->fwElevator2Label->setEnabled(true);
m_aircraft->fwElevator2Channel->setEnabled(true);
m_aircraft->fwElevator2Label->setEnabled(true);
m_aircraft->fwAileron1Channel->setEnabled(true);
m_aircraft->fwAileron1Label->setEnabled(true);
m_aircraft->fwAileron2Channel->setEnabled(true);
m_aircraft->fwAileron2Label->setEnabled(true);
m_aircraft->fwAileron1Label->setText("Aileron 1");
m_aircraft->fwAileron2Label->setText("Aileron 2");
m_aircraft->fwElevator1Label->setText("Elevator 1");
@ -228,7 +379,8 @@ void ConfigAirframeWidget::setupAirframeUI(QString frameType)
m_aircraft->fwElevator1Label->setText("Elevator 1");
m_aircraft->fwElevator2Label->setText("Elevator 2");
m_aircraft->elevonMixBox->setHidden(false);
m_aircraft->elevonLabel1->setText("Roll / Pitch");
m_aircraft->elevonLabel1->setText("Roll");
m_aircraft->elevonLabel2->setText("Pitch");
} else if (frameType == "FixedWingVtail" || frameType == "Vtail") {
m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText("Fixed Wing"));
@ -240,14 +392,45 @@ void ConfigAirframeWidget::setupAirframeUI(QString frameType)
m_aircraft->fwElevator1Label->setText("Vtail 1");
m_aircraft->fwElevator2Label->setText("Vtail 2");
m_aircraft->elevonMixBox->setHidden(false);
//m_aircraft->fwElevator2Channel->setEnabled(true);
//m_aircraft->fwElevator2Label->setEnabled(true);
m_aircraft->fwElevator2Channel->setEnabled(true);
m_aircraft->fwElevator2Label->setEnabled(true);
m_aircraft->fwAileron1Label->setText("Aileron 1");
m_aircraft->fwAileron2Label->setText("Aileron 2");
m_aircraft->elevonLabel1->setText("Rudder / Pitch");
m_aircraft->elevonLabel1->setText("Rudder");
m_aircraft->elevonLabel2->setText("Pitch");
} else if (frameType == "QuadX" || frameType == "Quad X") {
m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText("Multirotor"));
m_aircraft->multirotorFrameType->setCurrentIndex(m_aircraft->multirotorFrameType->findText("Quad X"));
quad->setElementId("quad-X");
m_aircraft->quadShape->fitInView(quad, Qt::KeepAspectRatio);
m_aircraft->multiMotor5->setEnabled(false);
m_aircraft->multiMotor6->setEnabled(false);
m_aircraft->multiMotor7->setEnabled(false);
m_aircraft->multiMotor8->setEnabled(false);
} else if (frameType == "QuadP" || frameType == "Quad +") {
m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText("Multirotor"));
m_aircraft->multirotorFrameType->setCurrentIndex(m_aircraft->multirotorFrameType->findText("Quad +"));
quad->setElementId("quad-plus");
m_aircraft->quadShape->fitInView(quad, Qt::KeepAspectRatio);
m_aircraft->multiMotor5->setEnabled(false);
m_aircraft->multiMotor6->setEnabled(false);
m_aircraft->multiMotor7->setEnabled(false);
m_aircraft->multiMotor8->setEnabled(false);
} else if (frameType == "Hexa" || frameType == "Hexacopter") {
m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText("Multirotor"));
m_aircraft->multirotorFrameType->setCurrentIndex(m_aircraft->multirotorFrameType->findText("Hexacopter"));
m_aircraft->multiMotor5->setEnabled(true);
m_aircraft->multiMotor6->setEnabled(true);
m_aircraft->multiMotor7->setEnabled(false);
m_aircraft->multiMotor8->setEnabled(false);
} else if (frameType == "Octo" || frameType == "Octocopter") {
m_aircraft->aircraftType->setCurrentIndex(m_aircraft->aircraftType->findText("Multirotor"));
m_aircraft->multirotorFrameType->setCurrentIndex(m_aircraft->multirotorFrameType->findText("Octocopter"));
m_aircraft->multiMotor5->setEnabled(true);
m_aircraft->multiMotor6->setEnabled(true);
m_aircraft->multiMotor7->setEnabled(true);
m_aircraft->multiMotor8->setEnabled(true);
}
}
/**
@ -258,7 +441,6 @@ void ConfigAirframeWidget::resetField(UAVObjectField * field)
for (unsigned int i=0;i<field->getNumElements();i++) {
field->setValue(0,i);
}
}
/**
@ -295,7 +477,7 @@ bool ConfigAirframeWidget::setupFrameFixedWing()
((m_aircraft->fwAileron1Channel->currentText() == "None") &&
(m_aircraft->fwRudderChannel->currentText() == "None"))) {
// TODO: explain the problem in the UI
m_aircraft->fwStatusLabel->setText("WARNING: check channel assignment");
m_aircraft->fwStatusLabel->setText("ERROR: check channel assignment");
return false;
}
// Now setup the channels:
@ -329,15 +511,8 @@ bool ConfigAirframeWidget::setupFrameFixedWing()
obj->updated();
// Save the curve:
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
field = obj->getField("ThrottleCurve1");
QList<double> curve = m_aircraft->fixedWingThrottle->getCurve();
for (int i=0;i<curve.length();i++) {
field->setValue(curve.at(i),i);
}
// ... and compute the matrix:
// In order to make code a bit nicer, we assume:
// - Channel dropdowns start with 'None', then 0 to 7
@ -391,10 +566,19 @@ bool ConfigAirframeWidget::setupFrameFixedWing()
ti = field->getElementNames().indexOf("Roll");
field->setValue(1, ti);
}
}
} // Else we have no ailerons. Our consistency check guarantees we have
// rudder in this case, so we're fine with it too.
// Elevator
eng = m_aircraft->fwElevator1Channel->currentIndex()-1;
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(1, ti);
// Only set Elevator 2 if it is defined
eng = m_aircraft->fwElevator2Channel->currentIndex()-1;
if (eng > -1) {
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
@ -402,16 +586,6 @@ bool ConfigAirframeWidget::setupFrameFixedWing()
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(1, ti);
// Only set Elevator 2 if Aileron 1 is defined
eng = m_aircraft->fwElevator2Channel->currentIndex()-1;
if (eng > -1) {
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(1, ti);
}
}
obj->updated();
@ -432,7 +606,7 @@ bool ConfigAirframeWidget::setupFrameElevon()
m_aircraft->fwAileron1Channel->currentText() == "None" ||
m_aircraft->fwAileron2Channel->currentText() == "None") {
// TODO: explain the problem in the UI
m_aircraft->fwStatusLabel->setText("WARNING: check channel assignment");
m_aircraft->fwStatusLabel->setText("ERROR: check channel assignment");
return false;
}
@ -461,12 +635,6 @@ bool ConfigAirframeWidget::setupFrameElevon()
// Save the curve:
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
field = obj->getField("ThrottleCurve1");
QList<double> curve = m_aircraft->fixedWingThrottle->getCurve();
for (int i=0;i<curve.length();i++) {
field->setValue(curve.at(i),i);
}
// ... and compute the matrix:
// In order to make code a bit nicer, we assume:
// - Channel dropdowns start with 'None', then 0 to 7
@ -508,9 +676,9 @@ bool ConfigAirframeWidget::setupFrameElevon()
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(1, ti);
field->setDouble((double)m_aircraft->elevonSlider2->value()/100, ti);
ti = field->getElementNames().indexOf("Roll");
field->setValue(1,ti);
field->setDouble((double)m_aircraft->elevonSlider1->value()/100,ti);
}
eng = m_aircraft->fwAileron2Channel->currentIndex()-1;
@ -520,9 +688,9 @@ bool ConfigAirframeWidget::setupFrameElevon()
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(1, ti);
field->setDouble((double)m_aircraft->elevonSlider2->value()/100, ti);
ti = field->getElementNames().indexOf("Roll");
field->setValue(-1,ti);
field->setDouble(-(double)m_aircraft->elevonSlider1->value()/100,ti);
}
obj->updated();
@ -530,61 +698,238 @@ bool ConfigAirframeWidget::setupFrameElevon()
return true;
}
/**
Setup VTail
*/
bool ConfigAirframeWidget::setupFrameVtail()
{
// Check coherence:
// - At least Pitch1 and Pitch2, and engine
if (m_aircraft->fwEngineChannel->currentText() == "None" ||
m_aircraft->fwElevator1Channel->currentText() == "None" ||
m_aircraft->fwElevator2Channel->currentText() == "None") {
// TODO: explain the problem in the UI
m_aircraft->fwStatusLabel->setText("WARNING: check channel assignment");
return false;
}
resetActuators();
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("ActuatorSettings")));
Q_ASSERT(obj);
// Elevons
UAVObjectField *field = obj->getField("FixedWingPitch1");
Q_ASSERT(field);
field->setValue(m_aircraft->fwElevator1Channel->currentText());
field = obj->getField("FixedWingPitch2");
Q_ASSERT(field);
field->setValue(m_aircraft->fwElevator2Channel->currentText());
field = obj->getField("FixedWingRoll1");
Q_ASSERT(field);
field->setValue(m_aircraft->fwAileron1Channel->currentText());
field = obj->getField("FixedWingRoll2");
Q_ASSERT(field);
field->setValue(m_aircraft->fwAileron2Channel->currentText());
// Throttle
field = obj->getField("FixedWingThrottle");
Q_ASSERT(field);
field->setValue(m_aircraft->fwEngineChannel->currentText());
obj->updated();
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
// ... and compute the matrix:
// In order to make code a bit nicer, we assume:
// - Channel dropdowns start with 'None', then 0 to 7
// 1. Assign the servo/motor/none for each channel
// Disable all
foreach(QString mixer, mixerTypes) {
field = obj->getField(mixer);
Q_ASSERT(field);
field->setValue("Disabled");
}
// and set only the relevant channels:
// Engine
int eng = m_aircraft->fwEngineChannel->currentIndex()-1;
field = obj->getField(mixerTypes.at(eng));
field->setValue("Motor");
field = obj->getField(mixerVectors.at(eng));
// First of all reset the vector
resetField(field);
int ti = field->getElementNames().indexOf("ThrottleCurve1");
field->setValue(1, ti);
eng = m_aircraft->fwAileron1Channel->currentIndex()-1;
if (eng > -1) {
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Roll");
field->setDouble(1,ti);
}
eng = m_aircraft->fwAileron2Channel->currentIndex()-1;
if (eng > -1) {
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Roll");
field->setDouble(-1,ti);
}
// Now compute the VTail
eng = m_aircraft->fwElevator1Channel->currentIndex()-1;
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setDouble((double)m_aircraft->elevonSlider2->value()/100, ti);
ti = field->getElementNames().indexOf("Yaw");
field->setDouble((double)m_aircraft->elevonSlider1->value()/100,ti);
eng = m_aircraft->fwElevator2Channel->currentIndex()-1;
field = obj->getField(mixerTypes.at(eng));
field->setValue("Servo");
field = obj->getField(mixerVectors.at(eng));
resetField(field);
ti = field->getElementNames().indexOf("Pitch");
field->setDouble((double)m_aircraft->elevonSlider2->value()/100, ti);
ti = field->getElementNames().indexOf("Yaw");
field->setDouble(-(double)m_aircraft->elevonSlider1->value()/100,ti);
obj->updated();
m_aircraft->fwStatusLabel->setText("Mixer generated");
return true;
}
/**
Set up a Quad-X
#ifdef MATRIX_QUAD_X
#define MATRIX_OUTPUTS 4
#define MATRIX_QUAD
//Note the offset is 0 for a servo (zero in the middle) and -1
//for a motor (zero at min pulse width)
//Throttle needs to be double because normal throttle range is 0 to +1 instead of -1 to +1
//as with pitch, roll, yaw. The final output range is -1 to +1
const float matrixGains[MATRIX_OUTPUTS][MAX_COMMANDS + 1]=
Help function: setupQuadMotor
*/
void ConfigAirframeWidget::setupQuadMotor(int channel, double pitch, double roll, double yaw)
{
// pitch roll yaw throttle offset
{0.5 ,0.5 ,0.5 ,1.8 ,-1 }, //Front left motor (CW)
{0.5 ,-0.5 ,-0.5 ,1.8 ,-1 }, //Front right motor(CCW)
{-0.5 ,-0.5 ,0.5 ,1.8 ,-1 }, //rear right motor (CW)
{-0.5 ,0.5 ,-0.5 ,1.8 ,-1 }, //Rear left motor (CCW)
};
#ifdef MATRIX_QUAD_PLUS
#define MATRIX_OUTPUTS 4
#define MATRIX_QUAD
//Note the offset is 0 for a servo (zero in the middle) and -1
//for a motor (zero at min pulse width)
//Throttle needs to be double because normal throttle range is 0 to +1 instead of -1 to +1
//as with pitch, roll, yaw. The final output range is -1 to +1
const float matrixGains[MATRIX_OUTPUTS][MAX_COMMANDS + 1]=
{
// pitch roll yaw throttle offset
{1 ,0 ,0.5 ,1.8 ,-1 }, //Front motor (CW)
{0 ,-1 ,-0.5 ,1.8 ,-1 }, //Right motor(CCW)
{-1 ,0 ,0.5 ,1.8 ,-1 }, //Rear motor (CW)
{0 ,1 ,-0.5 ,1.8 ,-1 }, //Left motor (CCW)
};
#endif
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
UAVObjectField *field = obj->getField(mixerTypes.at(channel));
field->setValue("Motor");
field = obj->getField(mixerVectors.at(channel));
// First of all reset the vector
resetField(field);
int ti = field->getElementNames().indexOf("ThrottleCurve1");
field->setValue(1, ti);
ti = field->getElementNames().indexOf("Roll");
field->setValue(roll,ti);
ti = field->getElementNames().indexOf("Pitch");
field->setValue(pitch,ti);
ti = field->getElementNames().indexOf("Yaw");
field->setValue(yaw,ti);
}
/**
Set up a Quad-X or Quad-P
*/
bool ConfigAirframeWidget::setupQuad(bool pLayout)
{
// Check coherence:
// - Four engines have to be defined
if (m_aircraft->multiMotor1->currentText() == "None" ||
m_aircraft->multiMotor2->currentText() == "None" ||
m_aircraft->multiMotor3->currentText() == "None" ||
m_aircraft->multiMotor4->currentText() == "None") {
m_aircraft->mrStatusLabel->setText("ERROR: Assign 4 motor channels");
return false;
}
resetActuators();
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("ActuatorSettings")));
Q_ASSERT(obj);
UAVObjectField *field;
// Save the actuator channels for motor 1 to 4 (N/E/S/W)
field = pLayout ? obj->getField("VTOLMotorN") : obj->getField("VTOLMotorNW");
Q_ASSERT(field);
field->setValue(m_aircraft->multiMotor1->currentText());
field = pLayout ? obj->getField("VTOLMotorE") : obj->getField("VTOLMotorNE");
Q_ASSERT(field);
field->setValue(m_aircraft->multiMotor2->currentText());
field = pLayout ? obj->getField("VTOLMotorS") : obj->getField("VTOLMotorSE");
Q_ASSERT(field);
field->setValue(m_aircraft->multiMotor3->currentText());
field = pLayout ? obj->getField("VTOLMotorW") : obj->getField("VTOLMotorSW");
Q_ASSERT(field);
field->setValue(m_aircraft->multiMotor4->currentText());
obj->updated(); // Save...
// Now, setup the mixer:
obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
// 1. Assign the servo/motor/none for each channel
// Disable all
foreach(QString mixer, mixerTypes) {
field = obj->getField(mixer);
Q_ASSERT(field);
field->setValue("Disabled");
}
// and set only the relevant channels:
// Motor 1 to 4, X Layout:
// pitch roll yaw throttle offset
// {0.5 ,0.5 ,0.5 ,1.8 ,-1 }, //Front left motor (CW)
// {0.5 ,-0.5 ,-0.5 ,1.8 ,-1 }, //Front right motor(CCW)
// {-0.5 ,-0.5 ,0.5 ,1.8 ,-1 }, //rear right motor (CW)
// {-0.5 ,0.5 ,-0.5 ,1.8 ,-1 }, //Rear left motor (CCW)
//
// Motor 1 to 4, P Layout:
// pitch roll yaw throttle offset
// {1 ,0 ,0.5 ,1.8 ,-1 }, //Front motor (CW)
// {0 ,-1 ,-0.5 ,1.8 ,-1 }, //Right motor(CCW)
// {-1 ,0 ,0.5 ,1.8 ,-1 }, //Rear motor (CW)
// {0 ,1 ,-0.5 ,1.8 ,-1 }, //Left motor (CCW)
int channel = m_aircraft->multiMotor1->currentIndex()-1;
pLayout ? setupQuadMotor(channel, 1, 0, 0.5)
: setupQuadMotor(channel, 0.5, 0.5, 0.5);
channel = m_aircraft->multiMotor2->currentIndex()-1;
pLayout ? setupQuadMotor(channel, 0, -1, -0.5)
: setupQuadMotor(channel, 0.5, -0.5, -0.5);
channel = m_aircraft->multiMotor3->currentIndex()-1;
pLayout ? setupQuadMotor(channel, -1, 0, 0.5)
: setupQuadMotor(channel, -0.5, -0.5, 0.5);
channel = m_aircraft->multiMotor4->currentIndex()-1;
pLayout ? setupQuadMotor(channel, 0, 1, -0.5)
: setupQuadMotor(channel, -0.5, 0.5, -0.5);
obj->updated();
m_aircraft->mrStatusLabel->setText("SUCCESS: Mixer Saved OK");
return true;
}
/**
Sends the config to the board (airframe type)
We do all the tasks commong to all airframes, or family of airframes, and
we call additional methods for specific frames, so that we do not have a code
that is too heavy.
*/
void ConfigAirframeWidget::sendAircraftUpdate()
{
ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
QString airframeType;
if (m_aircraft->aircraftType->currentText() == "Fixed Wing") {
// Save the curve (common to all Fixed wing frames)
UAVDataObject *obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
UAVObjectField* field = obj->getField("ThrottleCurve1");
QList<double> curve = m_aircraft->fixedWingThrottle->getCurve();
for (int i=0;i<curve.length();i++) {
field->setValue(curve.at(i),i);
}
if (m_aircraft->fixedWingType->currentText() == "Elevator aileron rudder" ) {
airframeType = "FixedWing";
setupFrameFixedWing();
@ -593,13 +938,46 @@ void ConfigAirframeWidget::sendAircraftUpdate()
setupFrameElevon();
} else { // Vtail
airframeType = "FixedWingVtail";
m_aircraft->fwStatusLabel->setText("Mixed Not Implemented");
setupFrameVtail();
}
} else if (m_aircraft->aircraftType->currentText() == "Multirotor") {
// We can already setup the feedforward here, as it is common to all platforms
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("MixerSettings")));
Q_ASSERT(obj);
UAVObjectField* field = obj->getField(QString("FeedForward"));
Q_ASSERT(field);
field->setDouble((double)m_aircraft->feedForwardSlider->value()/100);
field = obj->getField(QString("AccelTime"));
Q_ASSERT(field);
field->setDouble(m_aircraft->accelTime->value());
field = obj->getField(QString("DecelTime"));
Q_ASSERT(field);
field->setDouble(m_aircraft->decelTime->value());
// Curve is also common to all quads:
field = obj->getField("ThrottleCurve1");
QList<double> curve = m_aircraft->multiThrottleCurve->getCurve();
for (int i=0;i<curve.length();i++) {
field->setValue(curve.at(i),i);
}
if (m_aircraft->multirotorFrameType->currentText() == "Quad +") {
airframeType = "QuadP";
setupQuad(true);
} else if (m_aircraft->multirotorFrameType->currentText() == "Quad X") {
airframeType = "QuadX";
setupQuad(false);
} else if (m_aircraft->multirotorFrameType->currentText() == "Hexacopter") {
airframeType = "Hexa";
} else if (m_aircraft->multirotorFrameType->currentText() == "Octocopter") {
airframeType = "Octo";
}
} else {
airframeType = "FixedWing";
}
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(objManager->getObject(QString("SystemSettings")));
UAVDataObject* obj = dynamic_cast<UAVDataObject*>(getObjectManager()->getObject(QString("SystemSettings")));
Q_ASSERT(obj);
UAVObjectField* field = obj->getField(QString("AirframeType"));
field->setValue(airframeType);

View File

@ -49,12 +49,17 @@ private:
Ui_AircraftWidget *m_aircraft;
bool setupFrameFixedWing();
bool setupFrameElevon();
bool setupFrameVtail();
bool setupQuad(bool pLayout);
void resetField(UAVObjectField * field);
void resetActuators();
//void setMixerChannel(int channelNumber, bool channelIsMotor, QList<double> vector);
void setupQuadMotor(int channel, double roll, double pitch, double yaw);
QStringList mixerTypes;
QStringList mixerVectors;
QGraphicsSvgItem *quad;
private slots:
void requestAircraftUpdate();
@ -64,6 +69,11 @@ private slots:
void toggleAileron2(int index);
void toggleElevator2(int index);
protected:
void showEvent(QShowEvent *event);
void resizeEvent(QResizeEvent *event);
};
#endif // CONFIGAIRFRAMEWIDGET_H

View File

@ -7,5 +7,6 @@
<file>images/AHRS-v1.3.png</file>
<file>images/paper-plane.svg</file>
<file>images/curve-bg.svg</file>
<file>images/quad-shapes.svg</file>
</qresource>
</RCC>

View File

@ -9,77 +9,21 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1052.3622"
height="744.09448"
id="svg2"
width="744.09448819"
height="1052.3622047"
id="svg2917"
version="1.1"
inkscape:version="0.47 r22583"
sodipodi:docname="quad-shapes.svg">
<defs
id="defs4">
id="defs2919">
<inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 526.18109 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="744.09448 : 526.18109 : 1"
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
id="perspective10" />
<inkscape:perspective
id="perspective3598"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3598-7"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3598-3"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3787"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3812"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3837"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3862"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
<inkscape:perspective
id="perspective3893"
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
inkscape:vp_z="1 : 0.5 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_x="0 : 0.5 : 1"
sodipodi:type="inkscape:persp3d" />
id="perspective2925" />
</defs>
<sodipodi:namedview
id="base"
@ -88,38 +32,48 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.98994949"
inkscape:cx="500.32237"
inkscape:cy="220.64359"
inkscape:zoom="1.979899"
inkscape:cx="400.42368"
inkscape:cy="914.60167"
inkscape:document-units="px"
inkscape:current-layer="quad-X"
inkscape:current-layer="layer1"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-to-guides="true"
inkscape:snap-grids="true"
inkscape:window-width="1366"
inkscape:window-height="693"
inkscape:window-x="0"
inkscape:window-y="24"
inkscape:window-maximized="1"
showguides="true"
inkscape:guide-bbox="true">
inkscape:window-maximized="1">
<sodipodi:guide
orientation="0,1"
position="615.1829,267.69042"
id="guide3751" />
<sodipodi:guide
orientation="1,0"
position="540.43161,247.48737"
id="guide3753" />
<sodipodi:guide
orientation="1,0"
position="753.5738,135.36044"
id="guide3755" />
position="394.46457,984.89873"
id="guide2927" />
<sodipodi:guide
orientation="0,1"
position="655.589,87.883271"
id="guide3757" />
position="386.38335,864.69058"
id="guide2929" />
<sodipodi:guide
orientation="1,0"
position="442.95189,999.54594"
id="guide2931" />
<sodipodi:guide
orientation="0,1"
position="152.53303,923.57143"
id="guide3709" />
<sodipodi:guide
orientation="1,0"
position="88.571429,923.57143"
id="guide3711" />
<sodipodi:guide
orientation="1,0"
position="346.48232,984.89873"
id="guide3713" />
</sodipodi:namedview>
<metadata
id="metadata7">
id="metadata2922">
<rdf:RDF>
<cc:Work
rdf:about="">
@ -133,389 +87,382 @@
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-188.30535,184.5505)">
id="layer1">
<g
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
id="g3675"
transform="matrix(-1,0,0,1,639.78188,112)">
id="quad-plus"
inkscape:label="#g3890">
<path
id="path3721"
d="m 88.571429,129.03959 127.857141,0"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" />
<path
id="path3723"
d="m 152.03811,74.534523 0,107.113367"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" />
<path
transform="translate(-0.50000024,0.50000029)"
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -47.47717,0 23.738585,23.738585 0 1 1 47.47717,0 z"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path2935"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="translate(-66,54)"
sodipodi:type="arc"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
id="path3715"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -47.47717,0 23.738585,23.738585 0 1 1 47.47717,0 z" />
<path
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -47.47717,0 23.738585,23.738585 0 1 1 47.47717,0 z"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3717"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
sodipodi:type="arc"
transform="translate(64,54)" />
<path
transform="translate(0,108)"
sodipodi:type="arc"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
id="path3719"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -47.47717,0 23.738585,23.738585 0 1 1 47.47717,0 z" />
<text
id="text3725"
y="82.219322"
x="143.85715"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
style="font-size:24px"
y="82.219322"
x="143.85715"
id="tspan3727"
sodipodi:role="line">1</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="209.85715"
y="138.21933"
id="text3729"><tspan
sodipodi:role="line"
id="tspan3731"
x="209.85715"
y="138.21933"
style="font-size:24px">2</tspan></text>
<text
id="text3733"
y="190.21933"
x="145.85715"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
style="font-size:24px"
y="190.21933"
x="145.85715"
id="tspan3735"
sodipodi:role="line">3</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="79.857147"
y="136.21933"
id="text3737"><tspan
sodipodi:role="line"
id="tspan3739"
x="79.857147"
y="136.21933"
style="font-size:24px">4</tspan></text>
<g
id="quad-plus"
inkscape:label="#g3696">
id="g3748">
<path
id="path3630"
d="m 247.48737,381.85227 282.84272,0"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
transform="matrix(-1,0,0,1,639.78188,-112)" />
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -45.662,-9.104071"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3744"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
transform="matrix(0.76640028,0,0,0.76640028,35.249661,18.029229)"
sodipodi:start="0"
sodipodi:end="3.5351905"
sodipodi:open="true" />
<path
id="path3632"
d="m 391.00187,260.40259 0,241.42646"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
transform="matrix(-1,0,0,1,639.78188,-112)" />
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 138.92857,74.147895 -2.85714,-5.714285 -5.35714,3.928571"
id="path3746" />
</g>
<g
transform="translate(0,108)"
id="g3752">
<path
transform="matrix(-1,0,0,1,474.49429,-174.48732)"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
sodipodi:ry="62.857143"
sodipodi:rx="62.857143"
sodipodi:cy="329.50504"
sodipodi:cx="225.71428"
id="path2816"
style="opacity:0.87866109000000003;fill:#d1d1d1;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(-1,0,0,1,474.49429,55.18182)"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
sodipodi:ry="62.857143"
sodipodi:rx="62.857143"
sodipodi:cy="329.50504"
sodipodi:cx="225.71428"
id="path2816-2"
style="opacity:0.87866109000000003;fill:#d1d1d1;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(-1,0,0,1,339.20668,-59.65276)"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
sodipodi:ry="62.857143"
sodipodi:rx="62.857143"
sodipodi:cy="329.50504"
sodipodi:cx="225.71428"
id="path2816-3"
style="opacity:0.87866109000000003;fill:#d1d1d1;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(-1,0,0,1,614.33367,-59.65276)"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
sodipodi:ry="62.857143"
sodipodi:rx="62.857143"
sodipodi:cy="329.50504"
sodipodi:cx="225.71428"
id="path2816-7"
style="opacity:0.87866109000000003;fill:#d1d1d1;fill-opacity:1;stroke:none"
sodipodi:open="true"
sodipodi:end="3.5351905"
sodipodi:start="0"
transform="matrix(0.76640028,0,0,0.76640028,35.249661,18.029229)"
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3754"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-3.124577 0.61685,-6.218415 1.81517,-9.104071" />
<path
id="path3756"
d="m 138.92857,74.147895 -2.85714,-5.714285 -5.35714,3.928571"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<path
sodipodi:open="true"
sodipodi:end="3.5351905"
sodipodi:start="0"
transform="matrix(0.76640028,0,0,0.76640028,-30.17891,72.386372)"
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3868"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -45.662,-9.104071" />
<path
d="m 176.7767,75.039597 a 23.738585,23.738585 0 1 1 -45.662,-9.104071"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3874"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
transform="matrix(0.76640028,0,0,0.76640028,99.82109,72.386372)"
sodipodi:start="0"
sodipodi:end="3.5351905"
sodipodi:open="true" />
<path
id="path3882"
d="m 101.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
d="m 101.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
id="path3884" />
<path
id="path3886"
d="m 101.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 231.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
id="path3888" />
</g>
<g
id="quad-X"
inkscape:label="#g4034">
<path
id="path4030"
d="m 346.48232,88.676654 96.46957,82.832506"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:nodetypes="cc" />
<path
id="path4032"
d="M 346.48232,172.51932 431.84021,87.161425"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:nodetypes="cc" />
<g
transform="translate(-62.055796,15.66244)"
id="g3998">
<path
transform="translate(255.5,0.50000029)"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-13.110459 10.62813,-23.738585 23.73859,-23.738585 13.11045,0 23.73858,10.628126 23.73858,23.738585 z"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3924"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<text
id="text3932"
y="82.219322"
x="399.85715"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
style="font-size:24px"
y="82.219322"
x="399.85715"
id="tspan3934"
sodipodi:role="line">1</tspan></text>
<g
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
id="g3638"
transform="matrix(-1,0,0,1,636.67095,-112)">
id="g3948"
transform="translate(256,0)">
<path
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-3.124577 0.61685,-6.218415 1.81517,-9.104071"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3950"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3634"
sodipodi:cx="76.266518"
sodipodi:cy="77.086395"
sodipodi:rx="43.941635"
sodipodi:ry="43.941635"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
transform="translate(314.73535,188.16057)"
transform="matrix(0.76640028,0,0,0.76640028,35.249661,18.029229)"
sodipodi:start="0"
sodipodi:end="3.5710428"
sodipodi:end="3.5351905"
sodipodi:open="true" />
<path
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
id="path3636"
transform="translate(188.30535,204.16057)" />
</g>
<g
transform="matrix(-1,0,0,1,636.67095,122)"
id="g3663"
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none">
<path
sodipodi:open="true"
sodipodi:end="3.5710428"
sodipodi:start="0"
transform="translate(314.73535,188.16057)"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
sodipodi:ry="43.941635"
sodipodi:rx="43.941635"
sodipodi:cy="77.086395"
sodipodi:cx="76.266518"
id="path3665"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
transform="translate(188.30535,204.16057)"
id="path3667"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
id="g3669"
transform="translate(-274.39854,4)">
<path
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3671"
sodipodi:cx="76.266518"
sodipodi:cy="77.086395"
sodipodi:rx="43.941635"
sodipodi:ry="43.941635"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
transform="translate(314.73535,188.16057)"
sodipodi:start="0"
sodipodi:end="3.5710428"
sodipodi:open="true" />
<path
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
id="path3673"
transform="translate(188.30535,204.16057)" />
</g>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="-261.85617"
y="169.59779"
id="text3763"
transform="scale(-1,1)"><tspan
sodipodi:role="line"
id="tspan3765"
x="-261.85617"
y="169.59779">1</tspan></text>
<text
id="text3767"
y="284.69601"
x="-125.6799"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"
transform="scale(-1,1)"><tspan
y="284.69601"
x="-125.6799"
id="tspan3769"
sodipodi:role="line">2</tspan></text>
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="-261.42648"
y="399.24741"
id="text3771"
transform="scale(-1,1)"><tspan
sodipodi:role="line"
id="tspan3773"
x="-261.42648"
y="399.24741">3</tspan></text>
<text
id="text3775"
y="284.43234"
x="-401.19751"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"
transform="scale(-1,1)"><tspan
y="284.43234"
x="-401.19751"
id="tspan3777"
sodipodi:role="line">4</tspan></text>
<g
transform="translate(0.72844944,4)"
id="g3879"
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none">
<path
sodipodi:open="true"
sodipodi:end="3.5710428"
sodipodi:start="0"
transform="translate(314.73535,188.16057)"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
sodipodi:ry="43.941635"
sodipodi:rx="43.941635"
sodipodi:cy="77.086395"
sodipodi:cx="76.266518"
id="path3881"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
transform="translate(188.30535,204.16057)"
id="path3883"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 138.92857,74.147895 -2.85714,-5.714285 -5.35714,3.928571"
id="path3952" />
</g>
</g>
<g
inkscape:label="#g3696"
id="quad-X"
transform="matrix(0.76637382,-0.64239487,0.64239487,0.76637382,-562.69849,224.32158)">
<path
transform="matrix(-1,0,0,1,639.78188,-112)"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 247.48737,381.85227 282.84272,0"
id="path3717" />
<path
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="M 228.28398,133.32975 276.12336,408.0507"
id="path3719" />
transform="translate(29.913774,-19.106578)"
id="g4013">
<path
transform="translate(256,108)"
sodipodi:type="arc"
style="opacity:0.87866109;fill:#d1d1d1;fill-opacity:1;stroke:none"
id="path3721"
sodipodi:cx="225.71428"
sodipodi:cy="329.50504"
sodipodi:rx="62.857143"
sodipodi:ry="62.857143"
d="m 288.57142,329.50504 a 62.857143,62.857143 0 1 1 -125.71428,0 62.857143,62.857143 0 1 1 125.71428,0 z"
transform="matrix(-1,0,0,1,449.84658,-193.82959)" />
<path
sodipodi:type="arc"
style="opacity:0.87866109;fill:#d1d1d1;fill-opacity:1;stroke:none"
id="path3723"
sodipodi:cx="225.71428"
sodipodi:cy="329.50504"
sodipodi:rx="62.857143"
sodipodi:ry="62.857143"
d="m 288.57142,329.50504 a 62.857143,62.857143 0 1 1 -125.71428,0 62.857143,62.857143 0 1 1 125.71428,0 z"
transform="matrix(-1,0,0,1,498.74355,78.144384)" />
<path
sodipodi:type="arc"
style="opacity:0.87866109;fill:#d1d1d1;fill-opacity:1;stroke:none"
id="path3725"
sodipodi:cx="225.71428"
sodipodi:cy="329.50504"
sodipodi:rx="62.857143"
sodipodi:ry="62.857143"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
transform="matrix(-1,0,0,1,339.20668,-59.65276)" />
<path
sodipodi:type="arc"
style="opacity:0.87866109;fill:#d1d1d1;fill-opacity:1;stroke:none"
id="path3727"
sodipodi:cx="225.71428"
sodipodi:cy="329.50504"
sodipodi:rx="62.857143"
sodipodi:ry="62.857143"
d="m 288.57142,329.50504 c 0,34.71504 -28.1421,62.85714 -62.85714,62.85714 -34.71504,0 -62.85714,-28.1421 -62.85714,-62.85714 0,-34.71505 28.1421,-62.85715 62.85714,-62.85715 34.71504,0 62.85714,28.1421 62.85714,62.85715 z"
transform="matrix(-1,0,0,1,614.33367,-59.65276)" />
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
id="path3930"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-13.110459 10.62813,-23.738585 23.73859,-23.738585 13.11045,0 23.73858,10.628126 23.73858,23.738585 z" />
<text
id="text3940"
y="190.21933"
x="401.85715"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
style="font-size:24px"
y="190.21933"
x="401.85715"
id="tspan3942"
sodipodi:role="line">3</tspan></text>
<g
transform="matrix(0.17465766,0.98462922,-0.98462922,0.17465766,420.53389,-295.20368)"
id="g3729"
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none">
transform="translate(256,108)"
id="g3954">
<path
sodipodi:open="true"
sodipodi:end="3.5710428"
sodipodi:end="3.5351905"
sodipodi:start="0"
transform="translate(314.73535,188.16057)"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
sodipodi:ry="43.941635"
sodipodi:rx="43.941635"
sodipodi:cy="77.086395"
sodipodi:cx="76.266518"
id="path3731"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
transform="translate(188.30535,204.16057)"
id="path3733"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
style="stroke-width:5;stroke-miterlimit:4;stroke-dasharray:none"
id="g3735"
transform="matrix(0.17465766,0.98462922,-0.98462922,0.17465766,470.65227,-22.205893)">
<path
transform="matrix(0.76640028,0,0,0.76640028,35.249661,18.029229)"
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3737"
sodipodi:cx="76.266518"
sodipodi:cy="77.086395"
sodipodi:rx="43.941635"
sodipodi:ry="43.941635"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
transform="translate(314.73535,188.16057)"
sodipodi:start="0"
sodipodi:end="3.5710428"
sodipodi:open="true" />
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3956"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-3.124577 0.61685,-6.218415 1.81517,-9.104071" />
<path
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
id="path3739"
transform="translate(188.30535,204.16057)" />
</g>
<g
id="g3759"
transform="matrix(-0.99677811,0.08020845,0.08020845,0.99677811,756.35346,-27.446074)">
<path
sodipodi:open="true"
sodipodi:end="3.5710428"
sodipodi:start="0"
transform="translate(314.73535,188.16057)"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
sodipodi:ry="43.941635"
sodipodi:rx="43.941635"
sodipodi:cy="77.086395"
sodipodi:cx="76.266518"
id="path3747"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
transform="translate(188.30535,204.16057)"
id="path3749"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
id="path3958"
d="m 138.92857,74.147895 -2.85714,-5.714285 -5.35714,3.928571"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
</g>
<g
transform="translate(3.4442035,34.893422)"
id="g4021">
<path
transform="translate(190,54)"
sodipodi:type="arc"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
id="path3926"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-13.110459 10.62813,-23.738585 23.73859,-23.738585 13.11045,0 23.73858,10.628126 23.73858,23.738585 z" />
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="-484.25562"
y="-28.259306"
id="text3763-9"
transform="matrix(-0.76637382,-0.64239487,-0.64239487,0.76637382,0,0)"><tspan
x="335.85715"
y="136.21933"
id="text3944"><tspan
sodipodi:role="line"
id="tspan3765-6"
x="-484.25562"
y="-28.259306">1</tspan></text>
<text
transform="matrix(-0.76637382,-0.64239487,-0.64239487,0.76637382,0,0)"
id="text3910"
y="-25.15958"
x="-271.11383"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="-25.15958"
x="-271.11383"
id="tspan3912"
sodipodi:role="line">2</tspan></text>
id="tspan3946"
x="335.85715"
y="136.21933"
style="font-size:24px">4</tspan></text>
<path
sodipodi:open="true"
sodipodi:end="3.5351905"
sodipodi:start="0"
transform="matrix(0.76640028,0,0,0.76640028,225.82109,72.386372)"
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3960"
sodipodi:cx="153.03812"
sodipodi:cy="75.039597"
sodipodi:rx="23.738585"
sodipodi:ry="23.738585"
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-3.124577 0.61685,-6.218415 1.81517,-9.104071" />
<path
id="path3964"
d="m 357.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 357.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
id="path3966" />
<path
id="path3968"
d="m 357.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
transform="translate(-34.086226,-37.837559)"
id="g4006">
<path
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-13.110459 10.62813,-23.738585 23.73859,-23.738585 13.11045,0 23.73858,10.628126 23.73858,23.738585 z"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3928"
style="fill:#a3a3a3;fill-opacity:1;stroke:none"
sodipodi:type="arc"
transform="translate(320,54)" />
<text
xml:space="preserve"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
x="-272.9758"
y="148.46133"
id="text3914"
transform="matrix(-0.76637382,-0.64239487,-0.64239487,0.76637382,0,0)"><tspan
x="465.85715"
y="138.21933"
id="text3936"><tspan
sodipodi:role="line"
id="tspan3916"
x="-272.9758"
y="148.46133">3</tspan></text>
<text
transform="matrix(-0.76637382,-0.64239487,-0.64239487,0.76637382,0,0)"
id="text3918"
y="151.59932"
x="-483.6925"
style="font-size:40px;font-style:normal;font-weight:normal;fill:#000000;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans"
xml:space="preserve"><tspan
y="151.59932"
x="-483.6925"
id="tspan3920"
sodipodi:role="line">4</tspan></text>
<g
transform="matrix(-0.99677811,0.08020845,0.08020845,0.99677811,482.1726,-26.65301)"
id="g3922">
<path
sodipodi:type="arc"
style="fill:none;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="path3924"
sodipodi:cx="76.266518"
sodipodi:cy="77.086395"
sodipodi:rx="43.941635"
sodipodi:ry="43.941635"
d="m 120.20815,77.086395 c 0,24.268295 -19.67334,43.941635 -43.941632,43.941635 -24.268295,0 -43.941635,-19.67334 -43.941635,-43.941635 0,-6.314528 1.360941,-12.554884 3.990127,-18.29602"
transform="translate(314.73535,188.16057)"
sodipodi:start="0"
sodipodi:end="3.5710428"
sodipodi:open="true" />
<path
style="fill:none;stroke:#000000;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 152.53303,50.82243 10.60661,-7.576144 3.53553,10.606602 0,0"
id="path3926"
transform="translate(188.30535,204.16057)" />
</g>
id="tspan3938"
x="465.85715"
y="138.21933"
style="font-size:24px">2</tspan></text>
<path
d="m 176.7767,75.039597 c 0,13.110458 -10.62813,23.738584 -23.73858,23.738584 -13.11046,0 -23.73859,-10.628126 -23.73859,-23.738584 0,-3.124577 0.61685,-6.218415 1.81517,-9.104071"
sodipodi:ry="23.738585"
sodipodi:rx="23.738585"
sodipodi:cy="75.039597"
sodipodi:cx="153.03812"
id="path3962"
style="fill:none;stroke:#000000;stroke-width:2.60960245;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc"
transform="matrix(0.76640028,0,0,0.76640028,355.82109,72.386372)"
sodipodi:start="0"
sodipodi:end="3.5351905"
sodipodi:open="true" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 487.07143,134.50504 4.28571,-3.92857 2.85715,5.35714"
id="path3970" />
</g>
</g>
</g>

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -30,6 +30,7 @@
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QStyleOption>
#include <QDebug>
#include "mixercurveline.h"
#include "mixercurvepoint.h"
@ -111,9 +112,11 @@ QVariant Node::itemChange(GraphicsItemChange change, const QVariant &value)
// Stay inside graph
if (newPos.y() < 0)
newPos.setY(0);
if (newPos.y() > graph->height())
newPos.setY(graph->height());
return newPos;
double h = graph->sceneRect().height();
//qDebug() << h << " - " << newPos.y();
if (newPos.y() > h)
newPos.setY(h);
return newPos;
}
case ItemPositionHasChanged:
foreach (Edge *edge, edgeList)