diff --git a/common/lc_commands.cpp b/common/lc_commands.cpp index 50a82c10..111922fd 100644 --- a/common/lc_commands.cpp +++ b/common/lc_commands.cpp @@ -45,6 +45,13 @@ lcCommand gCommands[LC_NUM_COMMANDS] = QT_TRANSLATE_NOOP("Status", "Save a picture of the current view"), QT_TRANSLATE_NOOP("Shortcut", "") }, + // LC_FILE_IMPORT_LDD + { + QT_TRANSLATE_NOOP("Action", "File.Import.LDD"), + QT_TRANSLATE_NOOP("Menu", "&LEGO Digital Designer..."), + QT_TRANSLATE_NOOP("Status", "Import a file in LEGO Digital Designer LXF format"), + QT_TRANSLATE_NOOP("Shortcut", "") + }, // LC_FILE_EXPORT_3DS { QT_TRANSLATE_NOOP("Action", "File.Export.3DS"), diff --git a/common/lc_commands.h b/common/lc_commands.h index c60aac39..d7db17e3 100644 --- a/common/lc_commands.h +++ b/common/lc_commands.h @@ -9,6 +9,7 @@ enum lcCommandId LC_FILE_SAVE, LC_FILE_SAVEAS, LC_FILE_SAVE_IMAGE, + LC_FILE_IMPORT_LDD, LC_FILE_EXPORT_3DS, LC_FILE_EXPORT_HTML, LC_FILE_EXPORT_BRICKLINK, diff --git a/common/lc_lxf.cpp b/common/lc_lxf.cpp new file mode 100644 index 00000000..037eec7c --- /dev/null +++ b/common/lc_lxf.cpp @@ -0,0 +1,564 @@ +#include "lc_global.h" +#include "lc_lxf.h" +#include "lc_library.h" +#include "lc_application.h" +#include "lc_mainwindow.h" +#include "piece.h" +#include "lc_file.h" + +// Based on https://gitlab.com/sylvainls/lxf2ldr + +#define LC_READ_XML_INT(attribute, variable) \ + variable = Reader.attributes().value(attribute).toInt(&Ok); \ + if (!Ok) \ + return false; +#define LC_READ_XML_FLOAT(attribute, variable) \ + variable = Reader.attributes().value(attribute).toFloat(&Ok); \ + if (!Ok) \ + return false; + +inline QMatrix4x4 lcLDDRotationMatrix(double ax, double ay, double az, double Angle) +{ + QMatrix4x4 Matrix; + Matrix.rotate(Angle, ax, ay, az); + return Matrix; +} + +struct lcLDDTransformation +{ + QVector3D Translation; + QMatrix4x4 Rotation; + + lcLDDTransformation(const QVector3D& vtrans, const QMatrix4x4& vrot) + : Translation(vtrans), Rotation(vrot) + { + } + + lcLDDTransformation() + { + } +}; + +struct lcLDDSubstitute +{ + QString LDrawFile; + const lcLDDTransformation* Transformation; + + lcLDDSubstitute() + : Transformation(nullptr) + { + } + + lcLDDSubstitute(const QString& vdatfile, const lcLDDTransformation *const vtransformation) + : LDrawFile(vdatfile), Transformation(vtransformation) + { + } +}; + +struct lcLDDSimpleSubstitute +{ + QString LDrawFile; + bool Overwrite; + + lcLDDSimpleSubstitute() + : Overwrite(false) + { + } + + lcLDDSimpleSubstitute(const QString& vdatfile, bool voverwrite) + : LDrawFile(vdatfile), Overwrite(voverwrite) + { + } +}; + +typedef QMap lcLDDColorMap; +typedef QMap lcLDDDecorationMap; + +struct DecorMatch +{ + int UseColor; + QList Colors; + lcLDDDecorationMap Decorations; + + DecorMatch() + : UseColor(0) + { + } + + DecorMatch(int vusecolor, const QList& vcolors, const lcLDDDecorationMap& vdecorations) + : UseColor(vusecolor), Colors(vcolors), Decorations(vdecorations) + { + } +}; + +struct lcLDDAssembly +{ + int id; + QStringList Parts; + + lcLDDAssembly() + { + } + + lcLDDAssembly(int vid, const QStringList vparts) + : id(vid), Parts(vparts) + { + } +}; + +static bool lcLDDReadLDrawXML(QMap& MaterialTable, QMap& BrickTable, QMap& TransformationTable, QMap& AssemblyTable) +{ + QString DiskFileName(lcGetPiecesLibrary()->mLibraryDir.absoluteFilePath(QLatin1String("ldraw.xml"))); + + QXmlStreamReader Reader; + QFile DiskFile(DiskFileName); + + if (DiskFile.open(QFile::ReadOnly | QFile::Text)) + Reader.setDevice(&DiskFile); + else + { + QResource Resource(QLatin1String(":/resources/ldraw.xml")); + + if (Resource.isValid()) + { + QByteArray Data; + + if (Resource.isCompressed()) + Data = qUncompress(Resource.data(), Resource.size()); + else + Data = QByteArray::fromRawData((const char*)Resource.data(), Resource.size()); + + Reader.addData(QString::fromUtf8(Data)); + } + } + + if (!Reader.readNextStartElement() || Reader.name() != "LDrawMapping") + return false; + + bool Ok; + + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Material") + { + int Lego, LDraw; + LC_READ_XML_INT("lego", Lego); + LC_READ_XML_INT("ldraw", LDraw); + + MaterialTable[Lego] = LDraw; + Reader.skipCurrentElement(); + } + else if (Reader.name() == "Brick") + { + int Lego; + LC_READ_XML_INT("lego", Lego); + + BrickTable[Lego] = Reader.attributes().value("ldraw").toString().toLower(); + Reader.skipCurrentElement(); + } + else if (Reader.name() == "Transformation") + { + float tx, ty, tz, ax, ay, az, Angle; + LC_READ_XML_FLOAT("tx", tx); + LC_READ_XML_FLOAT("ty", ty); + LC_READ_XML_FLOAT("tz", tz); + LC_READ_XML_FLOAT("ax", ax); + LC_READ_XML_FLOAT("ay", ay); + LC_READ_XML_FLOAT("az", az); + LC_READ_XML_FLOAT("angle", Angle); + + TransformationTable[Reader.attributes().value("ldraw").toString().toLower()] = lcLDDTransformation(QVector3D(-tx, -ty, -tz), lcLDDRotationMatrix(ax, ay, az, -180.0 * Angle / M_PI)); + Reader.skipCurrentElement(); + } + else if (Reader.name() == "Assembly") + { + int Lego; + LC_READ_XML_INT("lego", Lego); + + QStringList Parts; + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Part") + Parts << Reader.attributes().value("ldraw").toString().toLower(); + + Reader.skipCurrentElement(); + } + AssemblyTable[Lego] = lcLDDAssembly(Lego, Parts); + } + else + { + Reader.skipCurrentElement(); + } + } + + return !Reader.hasError(); +} + +static QString lcLDDGetLDrawPart(int LegoID, const QMap& BrickTable) +{ + if (BrickTable.contains(LegoID)) + return BrickTable.value(LegoID); + return QString("%1.dat").arg(LegoID); +} + +static const lcLDDTransformation lcLDDGetLDrawTransform(const QString& LDraw, const QMap& TransformationTable) +{ + return TransformationTable.value(LDraw); +} + +static int lcLDDGetLDrawColor(int LegoColor, const QMap& MaterialTable) +{ + if (MaterialTable.contains(LegoColor)) + return MaterialTable.value(LegoColor); + return LegoColor; +} + +static bool lcLDDReadDecors(QMap& DecorationTable) +{ + lcDiskFile DiskFile(lcGetPiecesLibrary()->mLibraryDir.absoluteFilePath(QLatin1String("decors_lxf2ldr.yaml"))); + lcMemFile MemFile; + lcFile* File; + + if (DiskFile.Open(QIODevice::ReadOnly)) + File = &DiskFile; + else + { + QResource Resource(":/resources/decors_lxf2ldr.yaml"); + + if (Resource.isValid()) + { + QByteArray Data; + + if (Resource.isCompressed()) + Data = qUncompress(Resource.data(), Resource.size()); + else + Data = QByteArray::fromRawData((const char*)Resource.data(), Resource.size()); + + MemFile.WriteBuffer(Data.constData(), Data.size()); + MemFile.Seek(0, SEEK_SET); + File = &MemFile; + } + } + + char Line[1024]; + int CurrentLegoId = -1; + bool ParsingColors = true; + int UseColor = 0; + QList Colors; + lcLDDDecorationMap Decorations; + + auto AddDecor = [&]() + { + if (CurrentLegoId == -1) + return; + + DecorationTable[CurrentLegoId] = DecorMatch(UseColor, Colors, Decorations); + + UseColor = 0; + Colors.clear(); + Decorations.clear(); + CurrentLegoId = -1; + }; + + while (File->ReadLine(Line, sizeof(Line))) + { + char* Comment = strchr(Line, '#'); + if (Comment) + *Comment = 0; + + if (*Line == 0) + { + AddDecor(); + continue; + } + + int Indent = 0; + for (char* c = Line; *c == ' '; c++) + Indent++; + + if (Indent == 0) + { + AddDecor(); + + if (!strchr(Line, ':')) + continue; + + CurrentLegoId = atoi(Line); + } + else if (Indent == 2) + { + char* Separator = strchr(Line, ':'); + if (!Separator) + continue; + *Separator = 0; + Separator++; + + if (!strcmp(Line + 2, "usecolor")) + UseColor = atoi(Separator); + else if (!strcmp(Line + 2, "colors")) + ParsingColors = true; + else if (!strcmp(Line + 2, "decorations")) + ParsingColors = false; + } + else if (Indent == 4) + { + if (ParsingColors) + { + if (Line[4] == '-') + Colors << lcLDDColorMap(); + } + else + { + static const QMap RotationTable = + { + { "x", lcLDDRotationMatrix(1, 0, 0, 90) }, + { "xx", lcLDDRotationMatrix(1, 0, 0, 180) }, + { "xxx", lcLDDRotationMatrix(1, 0, 0, -90) }, + { "y", lcLDDRotationMatrix(0, 1, 0, 90) }, + { "yy", lcLDDRotationMatrix(0, 1, 0, 180) }, + { "yyy", lcLDDRotationMatrix(0, 1, 0, -90) }, + { "y7p/6", lcLDDRotationMatrix(0, 1, 0, 210) }, + { "z", lcLDDRotationMatrix(0, 0, 1, 90) }, + { "zz", lcLDDRotationMatrix(0, 0, 1, 180) }, + { "zzz", lcLDDRotationMatrix(0, 0, 1, -90) }, + }; + + char* Separator = strchr(Line, ':'); + if (!Separator) + continue; + *Separator = 0; + Separator++; + + char* Key = Line + 4; + + if (Key[0] != '\'') + continue; + Key++; + + char* Quote = strchr(Key, '\''); + if (!Quote) + continue; + *Quote = 0; + + QStringList SubList = QString(Separator).trimmed().split(' '); + QString LDrawFile = SubList.first().toLower(); + QString Rotation; + if (SubList.size() > 1) + Rotation = SubList.at(1); + if (RotationTable.contains(Rotation)) + Decorations.insert(Key, lcLDDSubstitute(LDrawFile, new lcLDDTransformation(QVector3D(), RotationTable[Rotation]))); + else + Decorations.insert(Key, lcLDDSubstitute(LDrawFile, 0)); + } + } + else if (Indent == 6) + { + if (ParsingColors) + { + char* Separator = strchr(Line, ':'); + if (!Separator) + continue; + *Separator = 0; + Separator++; + + int LegoColor = atoi(Line + 4); + + QStringList SimpleList = QString(Separator).trimmed().split(' '); + QString LDrawFile = SimpleList.first().toLower(); + bool Overwrite = false; + if (SimpleList.size() > 1) + Overwrite = SimpleList.at(1).toUpper() == QStringLiteral("OW"); + + if (!Colors.empty()) + Colors.last().insert(LegoColor, lcLDDSimpleSubstitute(LDrawFile, Overwrite)); + } + } + } + + return true; +} + +static void lcLDDGetDecorationSubstitute(int lego, const QString& decorations, const QList& colors, int& usecolor, lcLDDSubstitute& substitute, const QMap& DecorationTable) +{ + if (!DecorationTable.contains(lego)) + return; + + const DecorMatch decor = DecorationTable.value(lego); + usecolor = decor.UseColor; + const int maxcol = qMin(colors.size(), decor.Colors.size()); + + for (int i = 0; i < maxcol; ++i) + { + const int curcol = colors.at(i) == 0 ? colors.first() : colors.at(i); + if (decor.Colors.at(i).contains(curcol)) + { + const lcLDDSimpleSubstitute sub = decor.Colors.at(i).value(curcol); + if (substitute.LDrawFile.isEmpty() || sub.Overwrite) + substitute = lcLDDSubstitute(sub.LDrawFile, 0); + } + } + + if (decor.Decorations.contains(decorations)) + substitute = decor.Decorations.value(decorations); +} + +bool lcImportLDDFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups) +{ + QMap MaterialTable; + QMap BrickTable; + QMap TransformationTable; + QMap AssemblyTable; + + if (!lcLDDReadLDrawXML(MaterialTable, BrickTable, TransformationTable, AssemblyTable)) + { + QMessageBox::information(gMainWindow, QApplication::tr("Error"), QApplication::tr("Error loading conversion data from ldraw.xml.")); + return false; + } + + QMap DecorationTable; + lcLDDReadDecors(DecorationTable); + + QXmlStreamReader Reader(FileData); + Reader.readNext(); + + if (!Reader.readNextStartElement() || Reader.name() != "LXFML") + return false; + + lcPiecesLibrary* Library = lcGetPiecesLibrary(); + bool Ok; + + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Bricks") + { + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Brick") + { + int NumPieces = Pieces.GetSize(); + + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Part") + { + int DesignId; + LC_READ_XML_INT("designID", DesignId); + + const QString Decoration = Reader.attributes().value("decoration").toString(); + + const QStringList ColorList = Reader.attributes().value("materials").toString().split(','); + QList PartColors; + for (const QString& color_txt : ColorList) + { + PartColors << color_txt.toInt(&Ok); + if (!Ok) + return false; + } + + QStringList Bones; + while (Reader.readNextStartElement()) + { + if (Reader.name() == "Bone") + Bones << Reader.attributes().value("transformation").toString(); + Reader.skipCurrentElement(); + } + + if (Bones.isEmpty()) + return false; + + QList Positions; + QList Rotations; + + for (const QString& Bone : Bones) + { + const QStringList FloatList = Bone.split(','); + + if (FloatList.size() != 12) + return false; + + float Matrix[12]; + + for (int i = 0; i < FloatList.size(); ++i) + { + Matrix[i] = FloatList.at(i).toFloat(&Ok); + if (!Ok) + return false; + } + Positions << QVector3D(Matrix[9], Matrix[10], Matrix[11]); + QMatrix4x4 Rotation(Matrix[0], Matrix[3], Matrix[6], 0, Matrix[1], Matrix[4], Matrix[7], 0, Matrix[2], Matrix[5], Matrix[8], 0, 0, 0, 0, 1); + Rotation.optimize(); + Rotations << Rotation; + } + + QString PartID = lcLDDGetLDrawPart(DesignId, BrickTable); + const lcLDDTransformation LDrawTransform = lcLDDGetLDrawTransform(PartID, TransformationTable); + + int UseColor = 0; + lcLDDSubstitute Substitute; + lcLDDGetDecorationSubstitute(DesignId, Decoration, PartColors, UseColor, Substitute, DecorationTable); + if (!Substitute.LDrawFile.isEmpty()) + PartID = Substitute.LDrawFile; + + QList LDrawColors; + int ColorCode = lcLDDGetLDrawColor(PartColors.first(), MaterialTable); + for (int Color : PartColors) + LDrawColors << (Color ? lcLDDGetLDrawColor(Color, MaterialTable) : ColorCode); + ColorCode = LDrawColors.at(UseColor); + + const lcLDDTransformation *const SubTransform = Substitute.Transformation; + const static QMatrix4x4 Axes = lcLDDRotationMatrix(1, 0, 0, 180); + const static QVector3D Zero; + + const QMatrix4x4 Rot = Rotations.first() * LDrawTransform.Rotation; + const QMatrix4x4 ldr_rot = Axes * (SubTransform ? (Rot * SubTransform->Rotation) : Rot) * Axes; + + const QVector3D Move = (SubTransform ? SubTransform->Translation : Zero) + LDrawTransform.Translation; + const QVector3D ldr_pos = Axes * (Positions.first() + (Rot * Move)) * 25; + + + lcMatrix44 LDrawMatrix(lcVector4(ldr_rot(0, 0), ldr_rot(1, 0), ldr_rot(2, 0), 0.0f), lcVector4(ldr_rot(0, 1), ldr_rot(1, 1), ldr_rot(2, 1), 0.0f), + lcVector4(ldr_rot(0, 2), ldr_rot(1, 2), ldr_rot(2, 2), 0.0f), lcVector4(ldr_pos[0], ldr_pos[1], ldr_pos[2], 1.0f)); + + float* Matrix = LDrawMatrix; + lcMatrix44 Transform(lcVector4(Matrix[0], Matrix[2], -Matrix[1], 0.0f), lcVector4(Matrix[8], Matrix[10], -Matrix[9], 0.0f), + lcVector4(-Matrix[4], -Matrix[6], Matrix[5], 0.0f), lcVector4(Matrix[12], Matrix[14], -Matrix[13], 1.0f)); + + PartID = PartID.toUpper(); + PartID.chop(4); + PieceInfo* Info = Library->FindPiece(PartID.toLatin1().constData(), nullptr, true, false); + + lcPiece* Piece = new lcPiece(nullptr); + Piece->SetPieceInfo(Info, false); + Piece->Initialize(Transform, 1); + Piece->SetColorCode(ColorCode); + Pieces.Add(Piece); + } + else + { + Reader.skipCurrentElement(); + } + } + + if (Pieces.GetSize() != NumPieces + 1) + { + lcArray Group; + for (int PieceIdx = NumPieces; PieceIdx < Pieces.GetSize(); PieceIdx++) + Group.Add(Pieces[PieceIdx]); + Groups.Add(Group); + } + } + else + Reader.skipCurrentElement(); + } + } + else if (Reader.name() == "GroupSystems") + { + // todo: read ldd groups + Reader.skipCurrentElement(); + } + else + Reader.skipCurrentElement(); + } + + return true; +} diff --git a/common/lc_lxf.h b/common/lc_lxf.h new file mode 100644 index 00000000..f3cf964c --- /dev/null +++ b/common/lc_lxf.h @@ -0,0 +1,6 @@ +#pragma once + +#include "lc_array.h" + +bool lcImportLDDFile(const QString& FileData, lcArray& Pieces, lcArray>& Groups); + diff --git a/common/lc_mainwindow.cpp b/common/lc_mainwindow.cpp index c79ef4a4..de9d2e2d 100644 --- a/common/lc_mainwindow.cpp +++ b/common/lc_mainwindow.cpp @@ -378,6 +378,8 @@ void lcMainWindow::CreateMenus() FileMenu->addAction(mActions[LC_FILE_SAVE]); FileMenu->addAction(mActions[LC_FILE_SAVEAS]); FileMenu->addAction(mActions[LC_FILE_SAVE_IMAGE]); + QMenu* ImportMenu = FileMenu->addMenu(tr("&Import")); + ImportMenu->addAction(mActions[LC_FILE_IMPORT_LDD]); QMenu* ExportMenu = FileMenu->addMenu(tr("&Export")); ExportMenu->addAction(mActions[LC_FILE_EXPORT_3DS]); ExportMenu->addAction(mActions[LC_FILE_EXPORT_BRICKLINK]); @@ -1922,6 +1924,26 @@ void lcMainWindow::MergeProject() } } +void lcMainWindow::ImportLDD() +{ + if (!SaveProjectIfModified()) + return; + + QString LoadFileName = QFileDialog::getOpenFileName(this, tr("Import"), QString(), tr("LEGO Diginal Designer Files (*.lxf);;All Files (*.*)")); + if (LoadFileName.isEmpty()) + return; + + Project* NewProject = new Project(); + + if (NewProject->ImportLDD(LoadFileName)) + { + g_App->SetProject(NewProject); + UpdateAllViews(); + } + else + delete NewProject; +} + bool lcMainWindow::SaveProject(const QString& FileName) { QString SaveFileName; @@ -2048,6 +2070,10 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId) lcGetActiveProject()->SaveImage(); break; + case LC_FILE_IMPORT_LDD: + ImportLDD(); + break; + case LC_FILE_EXPORT_3DS: lcGetActiveProject()->Export3DStudio(QString()); break; diff --git a/common/lc_mainwindow.h b/common/lc_mainwindow.h index 792f7f57..88ba6d67 100644 --- a/common/lc_mainwindow.h +++ b/common/lc_mainwindow.h @@ -251,6 +251,7 @@ public: void NewProject(); bool OpenProject(const QString& FileName); void MergeProject(); + void ImportLDD(); bool SaveProject(const QString& FileName); bool SaveProjectIfModified(); bool SetModelFromFocus(); diff --git a/common/lc_model.cpp b/common/lc_model.cpp index 20506813..6ba64abb 100644 --- a/common/lc_model.cpp +++ b/common/lc_model.cpp @@ -21,6 +21,7 @@ #include "lc_qeditgroupsdialog.h" #include "lc_selectbycolordialog.h" #include "lc_qutils.h" +#include "lc_lxf.h" void lcModelProperties::LoadDefaults() { @@ -944,6 +945,33 @@ bool lcModel::LoadBinary(lcFile* file) return true; } +bool lcModel::LoadLDD(const QString& FileData) +{ + lcArray Pieces; + lcArray> Groups; + + if (!lcImportLDDFile(FileData, Pieces, Groups)) + return false; + + for (lcPiece* Piece : Pieces) + AddPiece(Piece); + + for (const lcArray& Group : Groups) + { + lcGroup* NewGroup = AddGroup(tr("Group #"), nullptr); + for (lcPiece* Piece : Group) + Piece->SetGroup(NewGroup); + } + + lcPiecesLibrary* Library = lcGetPiecesLibrary(); + CalculateStep(mCurrentStep); + Library->WaitForLoadQueue(); + Library->mBuffersDirty = true; + Library->UnloadUnusedParts(); + + return true; +} + void lcModel::Merge(lcModel* Other) { for (int PieceIdx = 0; PieceIdx < Other->mPieces.GetSize(); PieceIdx++) diff --git a/common/lc_model.h b/common/lc_model.h index 350ea937..a190ef81 100644 --- a/common/lc_model.h +++ b/common/lc_model.h @@ -198,6 +198,7 @@ public: void SaveLDraw(QTextStream& Stream, bool SelectedOnly) const; void LoadLDraw(QIODevice& Device, Project* Project); bool LoadBinary(lcFile* File); + bool LoadLDD(const QString& FileData); void SplitMPD(QIODevice& Device); void Merge(lcModel* Other); diff --git a/common/project.cpp b/common/project.cpp index 36cc0574..b66f4ef4 100644 --- a/common/project.cpp +++ b/common/project.cpp @@ -12,6 +12,7 @@ #include "lc_application.h" #include "lc_profile.h" #include "lc_file.h" +#include "lc_zipfile.h" #include "lc_qimagedialog.h" #include "lc_qmodellistdialog.h" #include "lc_qpovraydialog.h" @@ -403,6 +404,53 @@ void Project::Merge(Project* Other) mModified = true; } +bool Project::ImportLDD(const QString& FileName) +{ + lcZipFile ZipFile; + + if (!ZipFile.OpenRead(FileName)) + return false; + + lcMemFile XMLFile; + if (!ZipFile.ExtractFile("IMAGE100.LXFML", XMLFile)) + return false; + + mModels.DeleteAll(); + lcModel* Model = new lcModel(QString()); + + if (Model->LoadLDD(QString::fromUtf8((const char*)XMLFile.mBuffer))) + { + mModels.Add(Model); + Model->SetSaved(); + } + else + delete Model; + + if (mModels.IsEmpty()) + return false; + + if (mModels.GetSize() == 1) + { + lcModel* Model = mModels[0]; + + if (Model->GetProperties().mName.isEmpty()) + Model->SetName(QFileInfo(FileName).completeBaseName()); + } + + for (int ModelIdx = 0; ModelIdx < mModels.GetSize(); ModelIdx++) + mModels[ModelIdx]->CreatePieceInfo(this); + + lcArray UpdatedModels; + UpdatedModels.AllocGrow(mModels.GetSize()); + + for (int ModelIdx = 0; ModelIdx < mModels.GetSize(); ModelIdx++) + mModels[ModelIdx]->UpdatePieceInfo(UpdatedModels); + + mModified = false; + + return true; +} + void Project::GetModelParts(lcArray& ModelParts) { if (mModels.IsEmpty()) diff --git a/common/project.h b/common/project.h index 59c3b5d8..4edc36d9 100644 --- a/common/project.h +++ b/common/project.h @@ -65,6 +65,7 @@ public: bool Save(const QString& FileName); bool Save(QTextStream& Stream); void Merge(Project* Other); + bool ImportLDD(const QString& FileName); void SaveImage(); void Export3DStudio(const QString& FileName); diff --git a/leocad.qrc b/leocad.qrc index 42c40819..3b4f1a43 100644 --- a/leocad.qrc +++ b/leocad.qrc @@ -93,6 +93,8 @@ resources/library.zip resources/ldconfig.ldr resources/minifig.ini + resources/ldraw.xml + resources/decors_lxf2ldr.yaml resources/leocad_fr.qm resources/leocad_pt.qm diff --git a/resources/decors_lxf2ldr.yaml b/resources/decors_lxf2ldr.yaml new file mode 100644 index 00000000..2181bf41 --- /dev/null +++ b/resources/decors_lxf2ldr.yaml @@ -0,0 +1,699 @@ +--- +# Format: +# "" +# legoid: +# usecolor: +# colors: +# - # where +# : [OW] +# … +# decorations: +# : [rotation] +# … +# "" +# +# decorations beat colors. +# +# OW means that color beats previous colors. +# +# is one of x, xx, xxx, y, yy, yyy, y7p/6 +# meaning resp. 90° around x, 180° around x, 270° around x, … y, … z, 210° around y. + +93094: + usecolor: 1 + colors: + - # handle + 194: 93094.dat + 1: 25866c02.dat + - # stick +10066: + colors: + - # hair + - # ears + 138: 10066p01.dat # dark tan + 312: 10066p02.dat # dark flesh + 330: 10066p03.dat # olive green +10128: + colors: + - # body + - # trousers + 268: 10128p01.dat # dark purple + 138: 10128p02.dat # dark tan + - # ? + - # hair +10908: + colors: + - # chin + - # face + 297: 10908p01.dat # gold + 310: 10908p01.dat # gold + 309: 10908p03.dat # silver + 315: 10908p03.dat # silver + - # eyes + 1: 10908p01.dat # white + 23: 10908p02.dat OW # blue + - # eyelids + # 26: p0[1234] # black +11055: + colors: + - # flag + - # face 1 + - # face 2 + decorations: + '57592,55510': 2335p03.dat +11459: + usecolor: 1 + colors: + - # handle + 312: 11459p01.dat # dark flesh + - # plunger +13787: + colors: + - # hat + 37: 13787p01.dat # bright green + - # ears +13792: + colors: + - # mask + - # glass + 26: 13792p01.dat # black (with stripes) +13808: + colors: + - # handle + - # body + - # mouth + 26: 13808p01.dat # black +15284: + colors: + - # hair + - # visor + 22: 15284p01.dat # dark pink +15400: + usecolor: 1 + colors: + - # brick + 199: 15400c01.dat # dark bluish grey + - # top +15427: + colors: + - # hair + - # lock + 124: 15427p02.dat # magenta + 322: 15427p01.dat # magenta + medium azure + decorations: + '606285': 15427p01.dat +16000: + colors: + - # arm + - # lower arm + 24: 3818p01.dat # yellow +16001: + colors: + - # arm + - # lower arm + 24: 3819p01.dat # yellow +16175: + colors: + - # helmet + - # hair + 192: 16175p01.dat # reddish brown + 312: 16175p02.dat # medium dark flesh +16178: + colors: + - # helmet + - # hair + 308: 16178p01.dat # dark brown +2634: + colors: + - # frame + - # glass + 42: 2634p01.dat # trans light blue + 111: 2634p02.dat # trans black +30152: + usecolor: 1 + colors: + - # glass + 41: 30152c02.dat # trans red + 40: 30152c01.dat # trans clear + - # handle +30258: + colors: + - # sign + - # front + decorations: + '93337': 15210pc0.dat zz +30261: + colors: + - # sign + - # front + decorations: + '57818': 30261p02.dat +30287: + colors: + - # hat + - # fur + 1: 30287p01.dat # white +30361: + colors: + - # cylinder + - # front + decorations: + '86700': 30361dps1.dat yy + '94274': 30361dps8.dat yy +30367: + colors: + - # dome + - # large band + - # thin band + - # + - # + - # + decorations: + '0,0,94270,94271,94272': 30367cps0.dat yy +30480: + colors: + - # head + - # eyes + 3: 30480ps0.dat # light yellow +30483: + colors: + - # head + - # nose + 26: 30483p01.dat # black + - # + - # +30527: + colors: + - # tube + - # stud tip + 26: 30527p01.dat # black + 27: 30527p02.dat # dark grey + 199: 30527p03.dat # dark bluish grey + - # antistud tip + 26: 30527p01.dat # black + 27: 30527p02.dat # dark grey + 199: 30527p03.dat # dark bluish grey +45406: + colors: + - # cockpit + - # glass + 111: 45406c01.dat # trans black + 40: 45406c02.dat # trans clear + 42: 45406c03.dat # trans light blue +54654: + usecolor: 1 + colors: + - # bottom + 194: 54654c02.dat # light bluish grey + 199: 54654c03.dat # dark bluish grey + 106: 54654c04.dat # orange + 1: 54654c06.dat # white + - # top + - # tip + 1: 54654c05.dat # white +54779: + usecolor: 1 + colors: + - # top + 194: 54779p01.dat # light bluish grey + 199: 54779p02.dat # dark bluish grey + - # bottom +54923: + colors: + - # cockpit + - # glass + 111: 54923c01.dat # trans black + 42: 54923c02.dat # trans light blue +57028: + colors: + - # arrow + - # head + 26: 57028a.dat # black + 119: 57028b.dat # lime + 194: 57028c.dat # grey + 199: 57028c.dat # grey + 208: 57028c.dat # grey + 2: 57028c.dat # grey + 27: 57028c.dat # grey + 24: 57028d.dat # yellow +60672: + usecolor: 1 + colors: + - # arm + 298: 60672c01.dat # pearlescent grey + 315: 60672c01.dat # pearlescent grey + 309: 60672c01.dat # pearlescent grey + 179: 60672c01.dat # pearlescent grey + 316: 60672c01.dat # pearlescent grey + 139: 60672c02.dat # pearlescent copper + - # skin +60673: + usecolor: 1 + colors: + - # arm + 298: 60673c01.dat # pearlescent grey + 315: 60673c01.dat # pearlescent grey + 309: 60673c01.dat # pearlescent grey + 179: 60673c01.dat # pearlescent grey + 316: 60673c01.dat # pearlescent grey + 139: 60673c02.dat # pearlescent copper + - # skin +60797: + colors: + - # door + - # glass + 42: 60797c01.dat # trans light blue + 111: 60797c02.dat # trans black + 192: 60797c03.dat # reddish brown +61406: + colors: + - # spike + - # tip + 24: 61406p01.dat # yellow + 194: 61406p02.dat # light bluish grey + 106: 61406p03.dat # orange + 119: 61406p04.dat # lime + 26: 61406p05.dat # black +62537: + colors: + - # underside + - # left side + 26: 62537p02.dat # black + 23: 62537p01.dat # blue + - # pompoms + 23: 62537p01.dat # blue # (one) + 1: 62537p02.dat # white + 21: 62537p03.dat # red # (one) + - # (don’t work) + - # (don’t work) + - # right side + 21: 62537p03.dat # red +6567: + colors: + - # windscreen + - # glass + 40: 6567p01.dat # trans clear + 111: 6567p02.dat # trans black + 42: 6567p03.dat # trans light blue +76029: + colors: + - # window + - # glass + 111: 76029c01.dat # trans black + 43: 76029c02.dat # trans blue + 42: 76029c03.dat # trans light blue + 40: 76029c04.dat # trans clear + 48: 76029c05.dat # trans green +76033: + colors: + - # window + - # glass + 43: 76033c01.dat # trans dark blue + 42: 76033c02.dat # trans light blue +76040: + usecolor: 1 + colors: + - # base + 199: 76040c05.dat # dark bluish grey + 27: 76040c04.dat # dark grey + 192: 76040c06.dat # reddish brown + - # top +76041: + colors: + - # door + - # glass + 40: 76041c01.dat # trans clear +85947: + colors: + - # + - # eye? (don’t work) + - # tongue + - # "=1 eye? (don’t work)" + - # + decorations: + '0,0,0,93500': 85947p01.dat + '0,0,93849,93500': 85947p01.dat +85959: + usecolor: 1 + colors: + - # flame + 182: 85959p01.dat # trans orange + 44: 85959p02.dat # trans yellow + 48: 85959p03.dat # trans green + 40: 85959p04.dat # trans clear + 227: 85959p05.dat # trans bright green + 126: 85959p06.dat # trans purple + - # base + - # + - # + decorations: + '87354,87355,0': 85959p01.dat + '87354,0,0': 85959p01.dat + '0,87355,0': 85959p01.dat + '0,87354,87355': 85959p01.dat +90391: + colors: + - # staff + - # blade + 179: 90391p02.dat # flat silver + 315: 90391p01.dat # metallic silver + 309: 90391p01.dat # metallic silver +90393: + colors: + - # handle + - # mike + 315: 90393p01.dat # metallic silver + 309: 90393p01.dat # metallic silver +9044: + colors: + - # base + - # tap + 309: 9044c02.dat # chrome + 315: 9044c02.dat # chrome + 2: 9044c01.dat # light grey +90462: + colors: + - # wig + - # front stud + - # + decorations: + '92597': 90462bpq1.dat +92254: + colors: + - # hair + - # hat + 26: 92254p01.dat # black + 124: 92254p02.dat # magenta +93549: + usecolor: 1 + colors: + - # bottle + 40: 93549c01.dat # trans clear + - # content +93552: + colors: + - # paintbrush + - # brush tip + 28: 93552p01.dat # green + - # ring + 179: 93552p01.dat # flat silver +93564: + colors: + - # guitar + - # plugs + - # + - # + decorations: + '0,600099,600385': 93564p02.dat + '0,95101,96912': 93564p01.dat +93568: + colors: + - # pie + - # cream + 1: 93568p01.dat # white + 24: 93568p02.dat # yellow + decorations: + '97265': 93568p01.dat +95330: + colors: + - # handle + - # head + 21: 95330p01.dat # red + - # blade + 315: 95330p01.dat # silver + 309: 95330p01.dat # silver + 179: 95330p01.dat # silver +98382: + colors: + - # bear + - # sole + - # muzzle + 5: 98382p01.dat # tan + - # belly? + - # + decorations: + '0,99596,73156,0': 98382p01.dat +98388: + colors: + - # bird + - # beak + 124: 98388p03.dat # magenta + 21: 98388p01.dat # red + 24: 98388p02.dat # yellow + - # + - # +98568: + colors: + - # sword + - # blade + 227: 98568p01.dat # trans bright green + 179: 98568p02.dat # flat silver + 143: 98568p03.dat # trans medium blue +99013: + colors: + - # blade + - # tip + 26: 99013p01.dat # black +99243: + colors: + - # mask + - # feathers tips + - # right eye + jaw + - # + decorations: + '600100,600949,600950': 99243p01.dat +2431: + decorations: + '55728': 2431pc0.dat + '58858': 2431bp52.dat + '600778': 2431p01.dat + '610633': 2431p03.dat +2528: + decorations: + '601244': 2528p30.dat +3003: + decorations: + '57239,57308': 3003pe2.dat + '607795,0': 3003px1.dat + '610491,0': 3003px0.dat + '88625,88624': 3003pe3.dat +3004: + decorations: + '610489,0,0': 3004ptc1.dat +3005: + decorations: + '56918': 3005pe1.dat + '88620': 3005pe4.dat + '88627': 3005pe4.dat + '95551': 3005pf0.dat +3010: + decorations: + '618499,0': 3010pb1.dat +3039: + decorations: + '55123': 3039pce.dat + '55132': 3039pc9.dat + '55159': 3039pca.dat + '56057': 3039pc6.dat + '56571': 3039pcb.dat + '86666': 3039pcc.dat +3040: + decorations: + '55161': 3040p06.dat +3068: + decorations: + '610486': 3068bpx1.dat + '63404': 3068bp33.dat + '73023': 3068bpt3.dat + '55124': 3068bp72.dat + '55350': 3068bp72.dat + '97353': 3068bp71.dat +3069: + decorations: + '55035': 3069bpc3.dat + '55128': 3069bps1.dat + '55347': 3069bps6.dat yy + '55506': 3069bpw2.dat + '55513': 3069bp02.dat yy + '56756': 3069bp08.dat yy + '58229': 3069bp0c.dat + '60382': 3069bpc1.dat yy + '609694': 3069bpw4.dat yy + '609700': 3069bpw3.dat yy + '609713': 3069bpc7.dat yy + '62292': 3069bp01.dat + '93074': 3069bp17.dat + '55160': 3069bp80.dat + '58812': 3069bp80.dat +3070: + decorations: + '55071': 3070bp07.dat yyy +3678: + decorations: + '0,85232': 3678bp4x.dat +3846: + decorations: + '602054': 3846p4j.dat +4150: + decorations: + '55729': 4150ps4.dat + '59487': 4150p03.dat + '87078': 4150p02.dat +4492: + decorations: + '58002,58000': 4492p02.dat +12888: + decorations: + '603963,603962': 12888p01.dat +13789: + decorations: + '605406': 13789p01.dat + '605407': 13792p01.dat +14769: + decorations: + '55729': 14769p06.dat + '606885': 14769p04.dat yy + '606886': 14769p02.dat yy + '606887': 14769p01.dat yy + '87078': 14769p07.dat +15210: + decorations: + '93337': 15210pc0.dat zz +15530: + decorations: + '606112': 15530p01.dat +19727: + decorations: + '611560': 19727p01.dat +19729: + decorations: + '610378': 19729p02.dat + '611551': 19729p01.dat +30126: + decorations: + '88247': 30126p01.dat +30372: + decorations: + '58425': 30372ps0.dat +32474: + decorations: + '606117': 32474p01.dat y +44336: + decorations: + '87833': 44336p01.dat +44341: + decorations: + '87834': 44341p01.dat +44342: + decorations: + '87835': 44342p01.dat +44343: + decorations: + '87836': 44343p01.dat +44375: + decorations: + '60488': 44375p03.dat +59231: + decorations: + '59645': 59231p4h.dat +75902: + decorations: + '604009': 75902p03.dat +85984: + decorations: + '611013,0,0': 85984p70.dat + '616009,0,0': 85984pc1.dat +90397: + decorations: + '95059': 90397p02.dat +90508: + decorations: + '92125': 90508p01.dat +91884: + decorations: + '600331': 91884p04.dat + '95056': 91884p03.dat + '99762': 91884p02.dat +93551: + decorations: + '94470': 93551p01.dat +95228: + decorations: + '97373': 95228p01.dat +98138: + decorations: + '603159': 98138p07.dat y7p/6 + '610549': 98138p0c.dat + '610752': 98138p0b.dat +98372: + decorations: + '99754,99590': 98372p01.dat +98384: + decorations: + '99598': 98384p01.dat +3626: + decorations: + '55196,0,0': 3626cp01.dat + '59715,0,0': 3626bp06.dat + '606096,0,0': 3626bpq3.dat + '64901,0,0': 3626bp3r.dat + '89320,0,0': 3626cp8k.dat + '93417,0,0': 3626bp61.dat + '94062,0,0': 3626bpq2.dat + '94097,0,0': 3626bpq4.dat + '99567,0,0': 3626bp05.dat + '99571,0,0': 3626cp8c.dat + '0,0,608148': 3626cp8e.dat yy +3814: + decorations: + '55059,0': 973p8z.dat + '55127,0': 973p8j.dat + '55129,0': 973p6j.dat + '55661,0': 973p8x.dat + '55662,0': 973p7q.dat + '55663,0': 973p7u.dat + '55663,93077': 973p7n.dat + '55665,0': 973p7v.dat + '56288,0': 973p1r.dat + '56289,0': 973p7h.dat + '56753,0': 973p8g.dat + '57056,0': 973p7z.dat + '57497,0': 973p2d.dat + '60153,0': 973p1j.dat + '606424,0': 973p8i.dat + '608149,0': 973p91.dat + '610390,0': 973p90.dat + '612774,612775': 973p8s.dat + '612842,612843': 973p8r.dat + '612844,612845': 973p8q.dat + '88183,0': 973pc11.dat + '89174,0': 973ps1.dat + '89309,89310': 973p7w.dat + '89311,89312': 973p7j.dat + '91323,0': 973pc23.dat + '92100,0': 973pr5.dat + '93200,0': 973p7t.dat + '93319,93338': 973p7m.dat + '93429,0': 973pc3g.dat + '96692,97941': 973p7y.dat + '99158,99157': 973p8k.dat + '99672,0': 973pc67.dat +3815: + decorations: + '606647,0': 3815p8i.dat + '95112,0': 3815pc44.dat + '99713,0': 3815pc67.dat +3816: + decorations: + '73244,0,0': 3816pba.dat + '95030,0,0': 3816pc44.dat + '99716,605096,0': 3816pc67.dat + '99716,99717,0': 3816pc67.dat + '0,602300,0': 3816paw.dat +3817: + decorations: + '606649,0,0': 3817p8h.dat + '95031,0,0': 3817pc44.dat + '99718,605097,0': 3817pc67.dat + '99718,99719,0': 3817pc67.dat + '0,602298,0': 3817paw.dat diff --git a/resources/ldraw.xml b/resources/ldraw.xml new file mode 100644 index 00000000..538243ef --- /dev/null +++ b/resources/ldraw.xml @@ -0,0 +1,17477 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +