/**************************************************************************** * MeshLab o o * * An extendible mesh processor o o * * _ O _ * * Copyright(C) 2005, 2009 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ /* A class to represent the ui for the pickpoints plugin * * @author Oscar Barney */ #include #include #include #include #include "editpickpoints.h" #include "pickpointsDialog.h" #include #include #include using namespace vcg; class GetClosestFace { typedef GridStaticPtr MetroMeshGrid; typedef tri::FaceTmark MarkerFace; public: GetClosestFace() {} void init(CMeshO *_m) { m = _m; if (m) { unifGrid.Set(m->face.begin(), m->face.end()); markerFunctor.SetMesh(m); dist_upper_bound = m->bbox.Diag() / 10.0f; } } CMeshO *m; MetroMeshGrid unifGrid; MarkerFace markerFunctor; Scalarm dist_upper_bound; CMeshO::FaceType * getFace(Point3m &p) { assert(m); // the results Point3m closestPt; Scalarm dist = dist_upper_bound; const CMeshO::CoordType &startPt = p; // compute distance between startPt and the mesh S2 CMeshO::FaceType *nearestF = 0; vcg::face::PointDistanceBaseFunctor PDistFunct; dist = dist_upper_bound; nearestF = unifGrid.GetClosest(PDistFunct, markerFunctor, startPt, dist_upper_bound, dist, closestPt); if (dist == dist_upper_bound) qDebug() << "Dist is = upper bound"; return nearestF; } }; PickedPointTreeWidgetItem::PickedPointTreeWidgetItem( Point3m &intputPoint, CMeshO::FaceType::NormalType &faceNormal, QString name, bool _active) : QTreeWidgetItem(1001) { //name setName(name); active = _active; //would set the checkbox but qt doesnt allow a way to do this in the constructor //set point and normal setPointAndNormal(intputPoint, faceNormal); } void PickedPointTreeWidgetItem::setName(QString name) { setText(0, name); } QString PickedPointTreeWidgetItem::getName() { return text(0); } void PickedPointTreeWidgetItem::setPointAndNormal(Point3m &intputPoint, CMeshO::FaceType::NormalType &faceNormal) { point[0] = intputPoint[0]; point[1] = intputPoint[1]; point[2] = intputPoint[2]; normal[0] = faceNormal[0]; normal[1] = faceNormal[1]; normal[2] = faceNormal[2]; QString tempString; //x tempString.setNum(point[0]); setText(1, tempString); //y tempString.setNum(point[1]); setText(2, tempString); //z tempString.setNum(point[2]); setText(3, tempString); } Point3m PickedPointTreeWidgetItem::getPoint() { return point; } Point3m PickedPointTreeWidgetItem::getNormal() { return normal; } void PickedPointTreeWidgetItem::clearPoint() { point.SetZero(); //x setText(1, ""); //y setText(2, ""); //z setText(3, ""); setActive(false); } bool PickedPointTreeWidgetItem::isActive() { return active; } void PickedPointTreeWidgetItem::setActive(bool value) { active = value; //stupid way QT makes you get a widget associated with this item QTreeWidget * treeWidget = this->treeWidget(); assert(treeWidget); QWidget *widget = treeWidget->itemWidget(this, 4); assert(widget); QCheckBox *checkBox = qobject_cast(widget); assert(checkBox); checkBox->setChecked(value); } void PickedPointTreeWidgetItem::toggleActive(bool value) { active = value; } PickPointsDialog::PickPointsDialog(EditPickPointsPlugin *plugin, QWidget *parent) : QDockWidget(parent) { parentPlugin = plugin; //qt standard setup step PickPointsDialog::ui.setupUi(this); //setup borrowed from alighnDialog.cpp this->setWidget(ui.frame); this->setFeatures(QDockWidget::AllDockWidgetFeatures); this->setAllowedAreas(Qt::LeftDockWidgetArea); QPoint p = parent->mapToGlobal(QPoint(0, 0)); this->setFloating(true); this->setGeometry(p.x() + (parent->width() - width()), p.y() + 40, width(), height()); //now stuff specific to pick points QStringList headerNames; headerNames << "Point Name" << "X" << "Y" << "Z" << "active"; ui.pickedPointsTreeWidget->setHeaderLabels(headerNames); //init some variables //set to nothing for now lastPointToMove = 0; itemToMove = 0; meshModel = 0; _glArea = 0; //start at 0 pointCounter = 0; //start with no template setTemplateName(""); currentMode = ADD_POINT; recordPointForUndo = false; getClosestFace = new GetClosestFace(); //signals and slots connect(ui.removePointButton, SIGNAL(clicked()), this, SLOT(removeHighlightedPoint())); //rename when rename button clicked connect(ui.renamePointButton, SIGNAL(clicked()), this, SLOT(renameHighlightedPoint())); //rename on double click of point connect(ui.pickedPointsTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(renameHighlightedPoint())); connect(ui.clearPointButton, SIGNAL(clicked()), this, SLOT(clearHighlightedPoint())); connect(ui.pickPointModeRadioButton, SIGNAL(toggled(bool)), this, SLOT(togglePickMode(bool))); connect(ui.movePointRadioButton, SIGNAL(toggled(bool)), this, SLOT(toggleMoveMode(bool))); connect(ui.selectPointRadioButton, SIGNAL(toggled(bool)), this, SLOT(toggleSelectMode(bool))); connect(ui.saveButton, SIGNAL(clicked()), this, SLOT(savePointsToFile())); connect(ui.loadPointsButton, SIGNAL(clicked()), this, SLOT(askUserForFileAndLoadPoints())); connect(ui.removeAllPointsButton, SIGNAL(clicked()), this, SLOT(clearPointsButtonClicked())); connect(ui.saveTemplateButton, SIGNAL(clicked()), this, SLOT(savePointTemplate())); connect(ui.loadTemplateButton, SIGNAL(clicked()), this, SLOT(askUserForFileAndloadTemplate())); connect(ui.clearTemplateButton, SIGNAL(clicked()), this, SLOT(clearTemplateButtonClicked())); connect(ui.addPointToTemplateButton, SIGNAL(clicked()), this, SLOT(addPointToTemplate())); connect(ui.undoButton, SIGNAL(clicked()), this, SLOT(undo())); connect(ui.pickedPointsTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(redrawPoints())); connect(ui.showNormalCheckBox, SIGNAL(clicked()), this, SLOT(redrawPoints())); connect(ui.pinRadioButton, SIGNAL(clicked()), this, SLOT(redrawPoints())); connect(ui.lineRadioButton, SIGNAL(clicked()), this, SLOT(redrawPoints())); } PickPointsDialog::~PickPointsDialog() { delete getClosestFace; } void PickPointsDialog::addMoveSelectPoint(Point3m point, CMeshO::FaceType::NormalType faceNormal) { if (currentMode == ADD_POINT) { QTreeWidgetItem *item = 0; item = ui.pickedPointsTreeWidget->currentItem(); PickedPointTreeWidgetItem *treeItem = 0; if (NULL != item) { treeItem = dynamic_cast(item); } //if we are in template mode or if the highlighted point is not set if ((templateLoaded && NULL != treeItem) || (NULL != treeItem && !treeItem->isActive())) { treeItem->setPointAndNormal(point, faceNormal); treeItem->setActive(true); item = ui.pickedPointsTreeWidget->itemBelow(treeItem); if (NULL != item) { //set the next item to be selected ui.pickedPointsTreeWidget->setCurrentItem(item); } else { //if we just picked the last point go into move mode toggleMoveMode(true); } } else { //use a number as the default name QString name; name.setNum(pointCounter); pointCounter++; addTreeWidgetItemForPoint(point, name, faceNormal, true); } } if (currentMode == MOVE_POINT) { //test to see if there is actually a highlighted item if (NULL != itemToMove) { //for undo if (recordPointForUndo) { lastPointToMove = itemToMove; lastPointPosition = lastPointToMove->getPoint(); lastPointNormal = lastPointToMove->getNormal(); recordPointForUndo = false; } //now change the point itemToMove->setPointAndNormal(point, faceNormal); itemToMove->setActive(true); ui.pickedPointsTreeWidget->setCurrentItem(itemToMove); } } if (currentMode == SELECT_POINT) { ui.pickedPointsTreeWidget->setCurrentItem(itemToMove); } } void PickPointsDialog::recordNextPointForUndo() { recordPointForUndo = true; } void PickPointsDialog::selectOrMoveThisPoint(Point3m point) { qDebug() << "point is: " << point[0] << " " << point[1] << " " << point[2]; //the item closest to the given point PickedPointTreeWidgetItem *closestItem = 0; //the smallest distance from the given point to one in the list //so far.... Scalarm minDistanceSoFar = std::numeric_limits::max(); for (int i = 0; i < pickedPointTreeWidgetItemVector.size(); i++) { PickedPointTreeWidgetItem *item = pickedPointTreeWidgetItemVector.at(i); Point3m tempPoint = item->getPoint(); //qDebug() << "tempPoint is: " << tempPoint[0] << " " << tempPoint[1] << " " << tempPoint[2]; Scalarm temp = std::sqrt(std::pow(point[0] - tempPoint[0], 2) + std::pow(point[1] - tempPoint[1], 2) + std::pow(point[2] - tempPoint[2], 2)); //qDebug() << "distance is: " << temp; if ( minDistanceSoFar > temp) { minDistanceSoFar = temp; closestItem = item; } } //if we found an itme if (NULL != closestItem) { itemToMove = closestItem; //qDebug() << "Try to move: " << closestItem->getName(); } } void PickPointsDialog::redrawPoints() { //parentPlugin->drawPickedPoints(pickedPointTreeWidgetItemVector, meshModel->cm.bbox); assert(_glArea); _glArea->update(); } bool PickPointsDialog::showNormal() { return ui.showNormalCheckBox->isChecked(); } bool PickPointsDialog::drawNormalAsPin() { return ui.pinRadioButton->isChecked(); } void PickPointsDialog::addPoint(Point3m &point, QString &name, bool present) { //bool result = GLPickTri::PickNearestFace(currentMousePosition.x(),gla->height()-currentMousePosition.y(), // mm.cm, face); CMeshO::FaceType *face = 0; //qDebug() << "present: " << present; //now look for the normal if (NULL != meshModel && present) { //need to update the mask meshModel->updateDataMask(MeshModel::MM_FACEMARK); face = getClosestFace->getFace(point); if (NULL == face) qDebug() << "no face found for point: " << name; } //if we find a face add its normal. else add a default one if (NULL != face) addTreeWidgetItemForPoint(point, name, face->N(), present); else { Point3m faceNormal; addTreeWidgetItemForPoint(point, name, faceNormal, present); } } PickedPointTreeWidgetItem * PickPointsDialog::addTreeWidgetItemForPoint(Point3m &point, QString &name, CMeshO::FaceType::NormalType &faceNormal, bool present) { PickedPointTreeWidgetItem *widgetItem = new PickedPointTreeWidgetItem(point, faceNormal, name, present); pickedPointTreeWidgetItemVector.push_back(widgetItem); ui.pickedPointsTreeWidget->addTopLevelItem(widgetItem); //select the newest item ui.pickedPointsTreeWidget->setCurrentItem(widgetItem); //add a checkbox to the widget item's 5th column (QT makes us add it in this strange way) TreeCheckBox *checkBox = new TreeCheckBox(ui.pickedPointsTreeWidget, widgetItem, this); ui.pickedPointsTreeWidget->setItemWidget(widgetItem, 4, checkBox); //set the box to show the proper check checkBox->setChecked(present); //now connect the box to its slot that chanches the checked value of the //PickedPointTreeWidgetItem and draws all the points. dont do this before //set checked or you will have all points that should be drawn, not drawn connect(checkBox, SIGNAL(toggled(bool)), checkBox, SLOT(toggleAndDraw(bool))); return widgetItem; } void PickPointsDialog::clearPoints(bool clearOnlyXYZ) { if (clearOnlyXYZ) { //when using templates just clear the points that were picked but not the names for (int i = 0; i < pickedPointTreeWidgetItemVector.size(); i++) { pickedPointTreeWidgetItemVector.at(i)->clearPoint(); } //if the size is greater than 0 set the first point to be selected if (pickedPointTreeWidgetItemVector.size() > 0) { ui.pickedPointsTreeWidget->setCurrentItem( pickedPointTreeWidgetItemVector.at(0)); } } else { pickedPointTreeWidgetItemVector.clear(); ui.pickedPointsTreeWidget->clear(); pointCounter = 0; } //draw without any points that may have been cleared //parentPlugin->drawPickedPoints(pickedPointTreeWidgetItemVector, meshModel->cm.bbox,painter); assert(_glArea); _glArea->update(); //set to pick mode togglePickMode(true); } void PickPointsDialog::clearTemplate() { //always clear the points clearPoints(false); setTemplateName(""); } void PickPointsDialog::setTemplateName(QString name) { templateName = name; if ("" == templateName) { ui.templateNameLabel->setText("No Template Loaded"); templateLoaded = false; } else { ui.templateNameLabel->setText(templateName); templateLoaded = true; } } void PickPointsDialog::loadPickPointsTemplate(QString filename) { //clear the points tree clearPoints(false); std::vector pointNameVector; PickPointsTemplate::load(filename, &pointNameVector); for (int i = 0; i < pointNameVector.size(); i++) { Point3m point; Point3m faceNormal; PickedPointTreeWidgetItem *widgetItem = addTreeWidgetItemForPoint(point, pointNameVector.at(i), faceNormal, false); widgetItem->clearPoint(); } //select the first item in the list if it exists if (pickedPointTreeWidgetItemVector.size() > 0) { ui.pickedPointsTreeWidget->setCurrentItem(pickedPointTreeWidgetItemVector.at(0)); } setTemplateName(QFileInfo(filename).fileName()); templateWorkingDirectory = filename; } std::vector& PickPointsDialog::getPickedPointTreeWidgetItemVector() { return pickedPointTreeWidgetItemVector; } PickPointsDialog::Mode PickPointsDialog::getMode() { return currentMode; } void PickPointsDialog::setCurrentMeshModel(MeshModel *newMeshModel, QGLWidget *gla) { meshModel = newMeshModel; assert(meshModel); _glArea = gla; assert(_glArea); //make sure undo is cleared lastPointToMove = 0; //clear any points that are still here clearPoints(false); //also clear the template clearTemplate(); //make sure we start in pick mode togglePickMode(true); meshModel->updateDataMask(MeshModel::MM_FACEMARK); //set up the getClosestFace->init(&(meshModel->cm)); //Load the points from meta data if they are there if (vcg::tri::HasPerMeshAttribute(newMeshModel->cm, PickedPoints::Key)) { CMeshO::PerMeshAttributeHandle ppHandle = vcg::tri::Allocator::GetPerMeshAttribute(newMeshModel->cm, PickedPoints::Key); PickedPoints *pickedPoints = ppHandle(); if (NULL != pickedPoints) { const QString &name = pickedPoints->getTemplateName(); setTemplateName(name); std::vector& pickedPointVector = pickedPoints->getPickedPointVector(); PickedPoint *point; for (size_t i = 0; i < pickedPointVector.size(); i++) { point = pickedPointVector.at(i); addPoint(point->point, point->name, point->present); } redrawPoints(); } else { qDebug() << "problem with cast!!"; } } else { QString filename = PickedPoints::getSuggestedPickedPointsFileName(*meshModel); qDebug() << "suggested filename: " << filename; QFile file(filename); if (file.exists()) { loadPoints(filename); } else { //try loading the default template if there are not saved points already tryLoadingDefaultTemplate(); } } } //loads the default template if there is one void PickPointsDialog::tryLoadingDefaultTemplate() { QString filename = PickPointsTemplate::getDefaultTemplateFileName(); QFile file(filename); if (file.exists()) { loadPickPointsTemplate(filename); } //clear all the garbage out of the names clearPoints(true); } void PickPointsDialog::removeHighlightedPoint() { //get highlighted point QTreeWidgetItem *item = ui.pickedPointsTreeWidget->currentItem(); //test to see if there is actually a highlighted item if (NULL != item) { PickedPointTreeWidgetItem* pickedItem = dynamic_cast(item); //remove the point completely std::vector::iterator iterator; iterator = std::find(pickedPointTreeWidgetItemVector.begin(), pickedPointTreeWidgetItemVector.end(), pickedItem); //remove item from vector pickedPointTreeWidgetItemVector.erase(iterator); //free memory used by widget delete pickedItem; //redraw without deleted point redrawPoints(); } else { qDebug("no item picked"); } } void PickPointsDialog::renameHighlightedPoint() { //get highlighted point QTreeWidgetItem *item = ui.pickedPointsTreeWidget->currentItem(); //test to see if there is actually a highlighted item if (NULL != item) { PickedPointTreeWidgetItem* pickedItem = dynamic_cast(item); QString name = pickedItem->getName(); //qDebug("Rename \n"); //qDebug() << name; const QString newName = "newName"; RichParameterSet parameterSet; parameterSet.addParam(new RichString(newName, name, "New Name", "Enter the new name")); GenericParamDialog getNameDialog(this, ¶meterSet); getNameDialog.setWindowModality(Qt::WindowModal); getNameDialog.hide(); //display dialog int result = getNameDialog.exec(); if (result == QDialog::Accepted) { name = parameterSet.getString(newName); //qDebug("New name os \n"); //qDebug() << name; pickedItem->setName(name); //redraw with new point name redrawPoints(); } } } void PickPointsDialog::clearHighlightedPoint() { //get highlighted point QTreeWidgetItem *item = ui.pickedPointsTreeWidget->currentItem(); //test to see if there is actually a highlighted item if (NULL != item) { PickedPointTreeWidgetItem* pickedItem = dynamic_cast(item); pickedItem->clearPoint(); //redraw without deleted point redrawPoints(); } else { qDebug("no item picked"); } } void PickPointsDialog::togglePickMode(bool checked) { if (checked) { QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); //qDebug() << "pick mode"; currentMode = ADD_POINT; //make sure radio button reflects this change ui.pickPointModeRadioButton->setChecked(true); } } void PickPointsDialog::toggleMoveMode(bool checked) { if (checked) { QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); //qDebug() << "move mode"; currentMode = MOVE_POINT; //make sure the radio button reflects this change ui.movePointRadioButton->setChecked(true); } } void PickPointsDialog::toggleSelectMode(bool checked) { if (checked) { QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor)); //qDebug() << "select mode"; currentMode = SELECT_POINT; //make radio button reflect the change ui.selectPointRadioButton->setChecked(true); } } PickedPoints * PickPointsDialog::getPickedPoints() { PickedPoints *pickedPoints = new PickedPoints(); //add all the points for (int i = 0; i < pickedPointTreeWidgetItemVector.size(); i++) { PickedPointTreeWidgetItem *item = pickedPointTreeWidgetItemVector.at(i); pickedPoints->addPoint(item->getName(), item->getPoint(), item->isActive()); } pickedPoints->setTemplateName(templateName); return pickedPoints; } void PickPointsDialog::loadPoints(QString filename) { //clear the points tree and template in case it was loaded clearTemplate(); //get the points from file PickedPoints pickedPoints; pickedPoints.open(filename); const QString &name = pickedPoints.getTemplateName(); setTemplateName(name); std::vector& points = pickedPoints.getPickedPointVector(); for (size_t i = 0; i < points.size(); i++) { PickedPoint *pickedPoint = points.at(i); addPoint(pickedPoint->point, pickedPoint->name, pickedPoint->present); } //redraw with new point name redrawPoints(); } void PickPointsDialog::savePointsToFile() { PickedPoints *pickedPoints = getPickedPoints(); //save to a file if so desired and there are some points to save if (pickedPointTreeWidgetItemVector.size() > 0) { QString suggestion("."); if (NULL != meshModel) { suggestion = PickedPoints::getSuggestedPickedPointsFileName(*meshModel); } QString filename = QFileDialog::getSaveFileName(this, tr("Save File"), suggestion, "*" + PickedPoints::fileExtension); if ("" != filename) { pickedPoints->save(filename, QString(meshModel->shortName())); savePointsToMetaData(); } } } void PickPointsDialog::savePointsToMetaData() { //save the points to the metadata if (NULL != meshModel) { CMeshO::PerMeshAttributeHandle ppHandle = vcg::tri::Allocator::GetPerMeshAttribute(meshModel->cm, PickedPoints::Key); ppHandle() = getPickedPoints(); //qDebug() << "saved points"; } } void PickPointsDialog::askUserForFileAndLoadPoints() { QString suggestion("."); if (NULL != meshModel) suggestion = PickedPoints::getSuggestedPickedPointsFileName(*meshModel); QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), suggestion, "*" + PickedPoints::fileExtension); if ("" != filename) loadPoints(filename); } void PickPointsDialog::savePointTemplate() { std::vector pointNameVector; //add all the points for (int i = 0; i < pickedPointTreeWidgetItemVector.size(); i++) { PickedPointTreeWidgetItem *item = pickedPointTreeWidgetItemVector.at(i); pointNameVector.push_back(item->getName()); } //default if for the filename to be that of the default template QString filename = PickPointsTemplate::getDefaultTemplateFileName(); if (!ui.defaultTemplateCheckBox->isChecked()) { filename = QFileDialog::getSaveFileName(this, tr("Save File"), templateWorkingDirectory, "*" + PickPointsTemplate::fileExtension); //if the user pushes cancel dont do anything if ("" == filename) return; else templateWorkingDirectory = filename; } //add the extension if the user forgot it if (!filename.endsWith(PickPointsTemplate::fileExtension)) filename = filename + PickPointsTemplate::fileExtension; PickPointsTemplate::save(filename, &pointNameVector); setTemplateName(QFileInfo(filename).fileName()); if (ui.defaultTemplateCheckBox->isChecked()) { QMessageBox::information(this, "MeshLab", "Default Template Saved!", QMessageBox::Ok); } } void PickPointsDialog::askUserForFileAndloadTemplate() { QString filename = QFileDialog::getOpenFileName(this, tr("Load File"), templateWorkingDirectory, "*" + PickPointsTemplate::fileExtension); if ("" != filename) loadPickPointsTemplate(filename); } void PickPointsDialog::clearPointsButtonClicked() { QMessageBox messageBox(QMessageBox::Question, "Pick Points", "Are you sure you want to clear all points?", QMessageBox::Yes | QMessageBox::No, this); int returnValue = messageBox.exec(); if (returnValue == QMessageBox::Yes) { //if the template is loaded clear only xyz values clearPoints(templateLoaded); } } void PickPointsDialog::clearTemplateButtonClicked() { QMessageBox messageBox(QMessageBox::Question, "Pick Points", "Are you sure you want to clear the template and any picked points?", QMessageBox::Yes | QMessageBox::No, this); int returnValue = messageBox.exec(); if (returnValue == QMessageBox::Yes) { clearTemplate(); } } void PickPointsDialog::addPointToTemplate() { // if (!templateLoaded) setTemplateName("new Template"); Point3m point; Point3m faceNormal; QString name("new point"); PickedPointTreeWidgetItem *widgetItem = addTreeWidgetItemForPoint(point, name, faceNormal, false); widgetItem->clearPoint(); } void PickPointsDialog::undo() { if (NULL != lastPointToMove) { Point3m tempPoint = lastPointToMove->getPoint(); Point3m tempNormal = lastPointToMove->getNormal(); lastPointToMove->setPointAndNormal(lastPointPosition, lastPointNormal); //set things so you can undo back if need be lastPointPosition = tempPoint; lastPointNormal = tempNormal; redrawPoints(); } }