diff --git a/common/lc_lxf.cpp b/common/lc_lxf.cpp index 5f81796d..1914b7c3 100644 --- a/common/lc_lxf.cpp +++ b/common/lc_lxf.cpp @@ -5,8 +5,203 @@ #include "lc_mainwindow.h" #include "piece.h" #include "lc_file.h" +#include -bool lcImportLDDFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups) +static bool lcLoadLDrawXML(std::map& MaterialTable, std::map& BrickTable, std::map>& TransformTable) { - return false; -} \ No newline at end of file + QFile File(lcGetPiecesLibrary()->mLibraryDir.absoluteFilePath(QLatin1String("ldraw.xml"))); + QByteArray Data; + + if (File.open(QIODevice::ReadOnly)) + Data = File.readAll(); + else + { + QResource Resource(":/resources/ldraw.xml"); + + if (Resource.isValid()) + { + if (Resource.isCompressed()) + Data = qUncompress(Resource.data(), Resource.size()); + else + Data = QByteArray::fromRawData((const char*)Resource.data(), Resource.size()); + } + } + + if (Data.isEmpty()) + return false; + + QDomDocument Document; + Document.setContent(QString::fromUtf8(Data)); + + QDomElement Root = Document.documentElement(); + if (Root.tagName() != QLatin1String("LDrawMapping")) + return false; + + for (QDomNode Node = Root.firstChild(); !Node.isNull(); Node = Node.nextSibling()) + { + QDomElement Element = Node.toElement(); + QString ElementName = Element.tagName(); + + if (ElementName == QLatin1String("Material")) + { + int LDrawColor = Element.attribute(QLatin1String("ldraw")).toInt(); + int LegoColor = Element.attribute(QLatin1String("lego")).toInt(); + + MaterialTable[LegoColor] = LDrawColor; + } + else if (ElementName == QLatin1String("Brick")) + { + QString LDrawID = Element.attribute(QLatin1String("ldraw")).toUpper(); + LDrawID.chop(4); + int LegoID = Element.attribute(QLatin1String("lego")).toInt(); + + BrickTable.emplace(std::make_pair(LegoID, std::move(LDrawID.toStdString()))); + } + else if (ElementName == QLatin1String("Transformation")) + { + QString LDrawID = Element.attribute(QLatin1String("ldraw")).toUpper(); + LDrawID.chop(4); + + lcVector3 Translation; + lcVector4 AxisAngle; + + Translation[0] = Element.attribute(QLatin1String("tx")).toFloat(); + Translation[1] = Element.attribute(QLatin1String("ty")).toFloat(); + Translation[2] = Element.attribute(QLatin1String("tz")).toFloat(); + AxisAngle[0] = Element.attribute(QLatin1String("ax")).toFloat(); + AxisAngle[1] = Element.attribute(QLatin1String("ay")).toFloat(); + AxisAngle[2] = Element.attribute(QLatin1String("az")).toFloat(); + AxisAngle[3] = Element.attribute(QLatin1String("angle")).toFloat(); + + TransformTable.emplace(std::make_pair(std::move(LDrawID.toStdString()), std::make_pair(Translation, AxisAngle))); + } + } + + return true; +} + +bool lcImportLXFMLFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups) +{ + std::map MaterialTable; + std::map BrickTable; + std::map> TransformTable; + + if (!lcLoadLDrawXML(MaterialTable, BrickTable, TransformTable)) + return false; + + QDomDocument Document; + Document.setContent(FileData); + + QDomElement Root = Document.documentElement(); + if (Root.tagName() != QLatin1String("LXFML")) + return false; + + for (QDomNode SectionNode = Root.firstChild(); !SectionNode.isNull(); SectionNode = SectionNode.nextSibling()) + { + QDomElement SectionElement = SectionNode.toElement(); + if (SectionElement.tagName() != QLatin1String("Bricks")) + continue; + + for (QDomNode BrickNode = SectionElement.firstChild(); !BrickNode.isNull(); BrickNode = BrickNode.nextSibling()) + { + QDomElement BrickElement = BrickNode.toElement(); + if (BrickElement.tagName() != QLatin1String("Brick")) + continue; + + int NumBrickPieces = 0; + + for (QDomNode PartNode = BrickElement.firstChild(); !PartNode.isNull(); PartNode = PartNode.nextSibling()) + { + QDomElement PartElement = PartNode.toElement(); + if (PartElement.tagName() != QLatin1String("Part")) + continue; + + bool BoneFound = false; + lcMatrix44 WorldMatrix; + + for (QDomNode BoneNode = PartElement.firstChild(); !BoneNode.isNull(); BoneNode = BoneNode.nextSibling()) + { + QDomElement BoneElement = BoneNode.toElement(); + if (BoneElement.tagName() != QLatin1String("Bone")) + continue; + + QString BoneTransform = BoneElement.attribute(QLatin1String("transformation")); + QStringList BoneElements = BoneTransform.split(','); + + if (BoneElements.size() != 12) + continue; + + WorldMatrix[0][0] = BoneElements[0].toFloat(); + WorldMatrix[0][1] = BoneElements[1].toFloat(); + WorldMatrix[0][2] = BoneElements[2].toFloat(); + WorldMatrix[0][3] = 0.0f; + WorldMatrix[1][0] = BoneElements[3].toFloat(); + WorldMatrix[1][1] = BoneElements[4].toFloat(); + WorldMatrix[1][2] = BoneElements[5].toFloat(); + WorldMatrix[1][3] = 0.0f; + WorldMatrix[2][0] = BoneElements[6].toFloat(); + WorldMatrix[2][1] = BoneElements[7].toFloat(); + WorldMatrix[2][2] = BoneElements[8].toFloat(); + WorldMatrix[3][3] = 0.0f; + WorldMatrix[3][0] = BoneElements[9].toFloat(); + WorldMatrix[3][1] = BoneElements[10].toFloat(); + WorldMatrix[3][2] = BoneElements[11].toFloat(); + WorldMatrix[3][3] = 1.0f; + + BoneFound = true; + break; + } + + if (!BoneFound) + continue; + + QString LegoID = PartElement.attribute(QLatin1String("designID")); + int Material = PartElement.attribute(QLatin1String("materials")).split(',').first().toInt(); + + PieceInfo* Info = nullptr; + const auto BrickIt = BrickTable.find(LegoID.toInt()); + if (BrickIt != BrickTable.end()) + Info = lcGetPiecesLibrary()->FindPiece(BrickIt->second.c_str(), nullptr, true, false); + else + Info = lcGetPiecesLibrary()->FindPiece(LegoID.toLatin1(), nullptr, true, false); + + const auto ColorIt = MaterialTable.find(Material); + int ColorCode = 16; + if (ColorIt != MaterialTable.end()) + ColorCode = ColorIt->second; + + const auto TransformIt = TransformTable.find(BrickIt != BrickTable.end() ? BrickIt->second : LegoID.toStdString()); + if (TransformIt != TransformTable.end()) + { + const lcVector4& AxisAngle = TransformIt->second.second; + const lcVector3& TransformTranslation = TransformIt->second.first; + + lcMatrix44 Transform = lcMatrix44FromAxisAngle(lcVector3(AxisAngle[0], AxisAngle[1], AxisAngle[2]), -AxisAngle[3]); + Transform.SetTranslation(lcMul30(-TransformTranslation, Transform)); + + WorldMatrix = lcMul(Transform, WorldMatrix); + } + + WorldMatrix = lcMatrix44(lcVector4(WorldMatrix[0][0], -WorldMatrix[0][1], -WorldMatrix[0][2], 0.0f), lcVector4(-WorldMatrix[1][0], WorldMatrix[1][1], WorldMatrix[1][2], 0.0f), + lcVector4(-WorldMatrix[2][0], WorldMatrix[2][1], WorldMatrix[2][2], 0.0f), lcVector4(WorldMatrix[3][0] * 25.0f, -WorldMatrix[3][1] * 25.0f, -WorldMatrix[3][2] * 25.0f, 1.0f)); + + lcPiece* Piece = new lcPiece(nullptr); + Piece->SetPieceInfo(Info, false); + Piece->Initialize(lcMatrix44LDrawToLeoCAD(WorldMatrix), 1); + Piece->SetColorCode(ColorCode); + Pieces.Add(Piece); + NumBrickPieces++; + } + + if (NumBrickPieces > 1) + { + lcArray Group; + for (int PieceIdx = Pieces.GetSize() - NumBrickPieces; PieceIdx < Pieces.GetSize(); PieceIdx++) + Group.Add(Pieces[PieceIdx]); + Groups.Add(Group); + } + } + } + + return true; +} diff --git a/common/lc_lxf.h b/common/lc_lxf.h index f3cf964c..10a8f182 100644 --- a/common/lc_lxf.h +++ b/common/lc_lxf.h @@ -2,5 +2,5 @@ #include "lc_array.h" -bool lcImportLDDFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups); +bool lcImportLXFMLFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups); diff --git a/common/lc_math.h b/common/lc_math.h index 79aebb16..e93a9b57 100644 --- a/common/lc_math.h +++ b/common/lc_math.h @@ -755,6 +755,17 @@ inline lcMatrix33 lcMatrix33FromAxisAngle(const lcVector3& Axis, const float Rad return m; } +inline lcMatrix33 lcMatrix33Transpose(const lcMatrix33& m) +{ + lcMatrix33 t; + + t.r[0] = lcVector3(m[0][0], m[1][0], m[2][0]); + t.r[1] = lcVector3(m[0][1], m[1][1], m[2][1]); + t.r[2] = lcVector3(m[0][2], m[1][2], m[2][2]); + + return t; +} + inline lcMatrix33 lcMatrix33AffineInverse(const lcMatrix33& m) { lcMatrix33 Inv; @@ -1309,9 +1320,9 @@ inline lcMatrix44 lcMatrix44LDrawToLeoCAD(const lcMatrix44& Matrix) { lcMatrix44 m; - m.r[0] = lcVector4(Matrix[0][0], Matrix[2][0], -Matrix[1][0], 0.0f); - m.r[1] = lcVector4(Matrix[0][2], Matrix[2][2], -Matrix[1][2], 0.0f); - m.r[2] = lcVector4(-Matrix[0][1], -Matrix[2][1], Matrix[1][1], 0.0f); + m.r[0] = lcVector4(Matrix[0][0], Matrix[0][2], -Matrix[0][1], 0.0f); + m.r[1] = lcVector4(Matrix[2][0], Matrix[2][2], -Matrix[2][1], 0.0f); + m.r[2] = lcVector4(-Matrix[1][0], -Matrix[1][2], Matrix[1][1], 0.0f); m.r[3] = lcVector4(Matrix[3][0], Matrix[3][2], -Matrix[3][1], 1.0f); return m; diff --git a/common/lc_model.cpp b/common/lc_model.cpp index c9d90fc0..4d228868 100644 --- a/common/lc_model.cpp +++ b/common/lc_model.cpp @@ -950,7 +950,7 @@ bool lcModel::LoadLDD(const QString& FileData) lcArray Pieces; lcArray> Groups; - if (!lcImportLDDFile(FileData, Pieces, Groups)) + if (!lcImportLXFMLFile(FileData, Pieces, Groups)) return false; for (lcPiece* Piece : Pieces) diff --git a/leocad.pro b/leocad.pro index a7cc8e0b..c6618731 100644 --- a/leocad.pro +++ b/leocad.pro @@ -3,7 +3,7 @@ TEMPLATE = app greaterThan(QT_MAJOR_VERSION, 4) { QT *= printsupport - QT += concurrent + QT += concurrent xml } INCLUDEPATH += qt common diff --git a/leocad.qrc b/leocad.qrc index 42c40819..0e1b35ff 100644 --- a/leocad.qrc +++ b/leocad.qrc @@ -93,6 +93,7 @@ resources/library.zip resources/ldconfig.ldr resources/minifig.ini + resources/ldraw.xml resources/leocad_fr.qm resources/leocad_pt.qm diff --git a/resources/ldraw.xml b/resources/ldraw.xml new file mode 100644 index 00000000..16274c14 --- /dev/null +++ b/resources/ldraw.xml @@ -0,0 +1,7301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file