diff --git a/src/Gui/BitmapFactory.cpp b/src/Gui/BitmapFactory.cpp index 9b781a4d0..4b5adccb9 100644 --- a/src/Gui/BitmapFactory.cpp +++ b/src/Gui/BitmapFactory.cpp @@ -166,6 +166,9 @@ QStringList BitmapFactoryInst::findIconFiles() const files << it->absoluteFilePath(); } +#if QT_VERSION >= 0x040500 + files.removeDuplicates(); +#endif return files; } diff --git a/src/Gui/DlgActionsImp.cpp b/src/Gui/DlgActionsImp.cpp index 6c392426b..664277bcb 100644 --- a/src/Gui/DlgActionsImp.cpp +++ b/src/Gui/DlgActionsImp.cpp @@ -384,7 +384,8 @@ IconDialog::IconDialog(QWidget* parent) QStringList names = BitmapFactory().findIconFiles(); for (QStringList::Iterator it = names.begin(); it != names.end(); ++it) { item = new QListWidgetItem(ui->listWidget); - item->setIcon(QIcon(*it)); + //item->setIcon(QIcon(*it)); + item->setIcon(QIcon(BitmapFactory().pixmap((const char*)it->toUtf8()))); item->setText(QFileInfo(*it).baseName()); item->setToolTip(*it); } diff --git a/src/Gui/PythonConsole.cpp b/src/Gui/PythonConsole.cpp index f82b27063..8c5d2cea9 100644 --- a/src/Gui/PythonConsole.cpp +++ b/src/Gui/PythonConsole.cpp @@ -59,29 +59,29 @@ using namespace Gui; namespace Gui { -static const QChar promptEnd( QLatin1Char(' ') ); //< char for detecting prompt end - -inline int promptLength( const QString &lineStr ) - { return lineStr.indexOf( promptEnd ) + 1; } - -inline QString stripPromptFrom( const QString &lineStr ) - { return lineStr.mid( promptLength(lineStr) ); } - -/** - * cursorBeyond checks if cursor is at a valid position to accept keyEvents. - * @param cursor - cursor to check - * @param limit - cursor that marks the begin of the input region - * @param shift - offset for shifting the limit for non-selection cursors [default: 0] - * @return true if a keyEvent is ok at cursor's position, false otherwise - */ -inline bool cursorBeyond( const QTextCursor &cursor, const QTextCursor &limit, int shift = 0 ) -{ - int pos = limit.position(); - if (cursor.hasSelection()) - return (cursor.selectionStart() >= pos && cursor.selectionEnd() >= pos); - else - return cursor.position() >= (pos + shift); -} +static const QChar promptEnd( QLatin1Char(' ') ); //< char for detecting prompt end + +inline int promptLength( const QString &lineStr ) + { return lineStr.indexOf( promptEnd ) + 1; } + +inline QString stripPromptFrom( const QString &lineStr ) + { return lineStr.mid( promptLength(lineStr) ); } + +/** + * cursorBeyond checks if cursor is at a valid position to accept keyEvents. + * @param cursor - cursor to check + * @param limit - cursor that marks the begin of the input region + * @param shift - offset for shifting the limit for non-selection cursors [default: 0] + * @return true if a keyEvent is ok at cursor's position, false otherwise + */ +inline bool cursorBeyond( const QTextCursor &cursor, const QTextCursor &limit, int shift = 0 ) +{ + int pos = limit.position(); + if (cursor.hasSelection()) + return (cursor.selectionStart() >= pos && cursor.selectionEnd() >= pos); + else + return cursor.position() >= (pos + shift); +} struct PythonConsoleP { @@ -363,7 +363,7 @@ void InteractiveInterpreter::clearBuffer() * Constructs a PythonConsole which is a child of 'parent'. */ PythonConsole::PythonConsole(QWidget *parent) - : TextEdit(parent), WindowParameter( "Editor" ), _sourceDrain(NULL) + : TextEdit(parent), WindowParameter( "Editor" ), _sourceDrain(NULL) { d = new PythonConsoleP(); d->interactive = false; @@ -464,10 +464,10 @@ void PythonConsole::OnChange( Base::Subject &rCaller,const char* sR void PythonConsole::keyPressEvent(QKeyEvent * e) { bool restartHistory = true; - QTextCursor cursor = this->textCursor(); - QTextCursor inputLineBegin = this->inputBegin(); + QTextCursor cursor = this->textCursor(); + QTextCursor inputLineBegin = this->inputBegin(); - if (!cursorBeyond( cursor, inputLineBegin )) + if (!cursorBeyond( cursor, inputLineBegin )) { /** * The cursor is placed not on the input line (or within the prompt string) @@ -482,7 +482,7 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Escape: - case Qt::Key_Backspace: + case Qt::Key_Backspace: this->moveCursor( QTextCursor::End ); break; @@ -511,20 +511,20 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) * - show call tips on period */ QTextBlock inputBlock = inputLineBegin.block(); //< get the last paragraph's text - QString inputLine = inputBlock.text(); - QString inputStrg = stripPromptFrom( inputLine ); + QString inputLine = inputBlock.text(); + QString inputStrg = stripPromptFrom( inputLine ); switch (e->key()) { case Qt::Key_Escape: { - // disable current input string - i.e. put it to history but don't execute it. - if (!inputStrg.isEmpty()) + // disable current input string - i.e. put it to history but don't execute it. + if (!inputStrg.isEmpty()) { - d->history.append( QLatin1String("# ") + inputStrg ); //< put commented string to history ... - inputLineBegin.insertText( QString::fromAscii("# ") ); //< and comment it on console + d->history.append( QLatin1String("# ") + inputStrg ); //< put commented string to history ... + inputLineBegin.insertText( QString::fromAscii("# ") ); //< and comment it on console setTextCursor( inputLineBegin ); - printPrompt(d->interpreter->hasPendingInput() //< print adequate prompt + printPrompt(d->interpreter->hasPendingInput() //< print adequate prompt ? PythonConsole::Incomplete : PythonConsole::Complete); } @@ -533,8 +533,8 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) case Qt::Key_Return: case Qt::Key_Enter: { - d->history.append( inputStrg ); //< put statement to history - runSource( inputStrg ); //< commit input string + d->history.append( inputStrg ); //< put statement to history + runSource( inputStrg ); //< commit input string } break; case Qt::Key_Period: @@ -542,14 +542,14 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) // analyse context and show available call tips int contextLength = cursor.position() - inputLineBegin.position(); TextEdit::keyPressEvent(e); - d->callTipsList->showTips( inputStrg.left( contextLength ) ); + d->callTipsList->showTips( inputStrg.left( contextLength ) ); } break; case Qt::Key_Home: { QTextCursor::MoveMode mode = (e->modifiers() & Qt::ShiftModifier)? QTextCursor::KeepAnchor /* else */ : QTextCursor::MoveAnchor; - cursor.setPosition( inputLineBegin.position(), mode ); + cursor.setPosition( inputLineBegin.position(), mode ); setTextCursor( cursor ); ensureCursorVisible(); } break; @@ -557,7 +557,7 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) case Qt::Key_Up: { // if possible, move back in history - if (d->history.prev( inputStrg )) + if (d->history.prev( inputStrg )) { overrideCursor( d->history.value() ); } restartHistory = false; } break; @@ -585,7 +585,7 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) case Qt::Key_Backspace: { - if (cursorBeyond( cursor, inputLineBegin, +1 )) + if (cursorBeyond( cursor, inputLineBegin, +1 )) { TextEdit::keyPressEvent(e); } } break; @@ -598,9 +598,9 @@ void PythonConsole::keyPressEvent(QKeyEvent * e) // the event and afterwards update the list widget if (d->callTipsList->isVisible()) { d->callTipsList->validateCursor(); } - - // disable history restart if input line changed - restartHistory &= (inputLine != inputBlock.text()); + + // disable history restart if input line changed + restartHistory &= (inputLine != inputBlock.text()); } // any cursor move resets the history to its latest item. if (restartHistory) @@ -647,40 +647,40 @@ void PythonConsole::printPrompt(PythonConsole::Prompt mode) d->error = QString::null; } - if (mode != PythonConsole::Special) - { - // Append the prompt string - QTextCursor cursor = textCursor(); - cursor.beginEditBlock(); - cursor.movePosition(QTextCursor::End); - QTextBlock block = cursor.block(); - - // Python's print command appends a trailing '\n' to the system output. - // In this case, however, we should not add a new text block. We force - // the current block to be normal text (user state = 0) to be highlighted - // correctly and append the '>>> ' or '... ' to this block. - if (block.length() > 1) - cursor.insertBlock(cursor.blockFormat(), cursor.charFormat()); - else - block.setUserState(0); - - switch (mode) - { - case PythonConsole::Incomplete: - cursor.insertText(QString::fromAscii("... ")); - break; - case PythonConsole::Complete: - cursor.insertText(QString::fromAscii(">>> ")); - break; - default: - break; - } - cursor.endEditBlock(); + // Append the prompt string + QTextCursor cursor = textCursor(); - // move cursor to the end - cursor.movePosition(QTextCursor::End); - setTextCursor(cursor); - } + if (mode != PythonConsole::Special) + { + cursor.beginEditBlock(); + cursor.movePosition(QTextCursor::End); + QTextBlock block = cursor.block(); + + // Python's print command appends a trailing '\n' to the system output. + // In this case, however, we should not add a new text block. We force + // the current block to be normal text (user state = 0) to be highlighted + // correctly and append the '>>> ' or '... ' to this block. + if (block.length() > 1) + cursor.insertBlock(cursor.blockFormat(), cursor.charFormat()); + else + block.setUserState(0); + + switch (mode) + { + case PythonConsole::Incomplete: + cursor.insertText(QString::fromAscii("... ")); + break; + case PythonConsole::Complete: + cursor.insertText(QString::fromAscii(">>> ")); + break; + default: + break; + } + cursor.endEditBlock(); + } + // move cursor to the end + cursor.movePosition(QTextCursor::End); + setTextCursor(cursor); } /** @@ -710,17 +710,17 @@ void PythonConsole::appendOutput(const QString& output, int state) */ void PythonConsole::runSource(const QString& line) { - /** - * Check if there's a "source drain", which want's to consume the source in another way then just executing it. - * If so, put the source to the drain and emit a signal to notify the consumer, whoever this may be. - */ - if (this->_sourceDrain) - { - *this->_sourceDrain = line; - Q_EMIT pendingSource(); - return; - } - + /** + * Check if there's a "source drain", which want's to consume the source in another way then just executing it. + * If so, put the source to the drain and emit a signal to notify the consumer, whoever this may be. + */ + if (this->_sourceDrain) + { + *this->_sourceDrain = line; + Q_EMIT pendingSource(); + return; + } + bool incomplete = false; Base::PyGILStateLocker lock; PyObject* default_stdout = PySys_GetObject("stdout"); @@ -730,11 +730,11 @@ void PythonConsole::runSource(const QString& line) d->interactive = true; try { - d->history.markScratch(); //< mark current history position ... + d->history.markScratch(); //< mark current history position ... // launch the command now incomplete = d->interpreter->push(line.toUtf8()); - if (!incomplete) - { d->history.doScratch(); } //< ... and scratch history entries that might have been added by executing the line. + if (!incomplete) + { d->history.doScratch(); } //< ... and scratch history entries that might have been added by executing the line. setFocus(); // if focus was lost } catch (const Base::SystemExitException&) { @@ -845,21 +845,21 @@ void PythonConsole::changeEvent(QEvent *e) TextEdit::changeEvent(e); } -void PythonConsole::mouseReleaseEvent( QMouseEvent *e ) -{ - TextEdit::mouseReleaseEvent( e ); - if (e->button() == Qt::LeftButton) - { - QTextCursor cursor = this->textCursor(); - if (cursor.hasSelection() == false - && cursor < this->inputBegin()) - { - cursor.movePosition( QTextCursor::End ); - this->setTextCursor( cursor ); - } - } -} - +void PythonConsole::mouseReleaseEvent( QMouseEvent *e ) +{ + TextEdit::mouseReleaseEvent( e ); + if (e->button() == Qt::LeftButton) + { + QTextCursor cursor = this->textCursor(); + if (cursor.hasSelection() == false + && cursor < this->inputBegin()) + { + cursor.movePosition( QTextCursor::End ); + this->setTextCursor( cursor ); + } + } +} + /** * Drops the event \a e and writes the right Python command. */ @@ -958,17 +958,17 @@ void PythonConsole::insertFromMimeData (const QMimeData * source) } } -QTextCursor PythonConsole::inputBegin( void ) const -{ - // construct cursor at begin of input line ... - QTextCursor inputLineBegin( this->textCursor() ); - inputLineBegin.movePosition( QTextCursor::End ); - inputLineBegin.movePosition( QTextCursor::StartOfLine ); - // ... and move cursor right beyond the prompt. - inputLineBegin.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptLength( inputLineBegin.block().text() ) ); - return inputLineBegin; -} - +QTextCursor PythonConsole::inputBegin( void ) const +{ + // construct cursor at begin of input line ... + QTextCursor inputLineBegin( this->textCursor() ); + inputLineBegin.movePosition( QTextCursor::End ); + inputLineBegin.movePosition( QTextCursor::StartOfLine ); + // ... and move cursor right beyond the prompt. + inputLineBegin.movePosition( QTextCursor::Right, QTextCursor::MoveAnchor, promptLength( inputLineBegin.block().text() ) ); + return inputLineBegin; +} + QMimeData * PythonConsole::createMimeDataFromSelection () const { QMimeData* mime = new QMimeData(); @@ -990,7 +990,7 @@ QMimeData * PythonConsole::createMimeDataFromSelection () const int pos = b.position(); if ( pos >= s && pos <= e ) { if (b.userState() > -1 && b.userState() < pythonSyntax->maximumUserState()) { - lines << stripPromptFrom( b.text() ); + lines << stripPromptFrom( b.text() ); } } } @@ -1059,7 +1059,7 @@ void PythonConsole::runSourceFromMimeData(const QString& source) QString select = cursor.selectedText(); cursor.removeSelectedText(); last = last + select; - line = stripPromptFrom( cursor.block().text() ); + line = stripPromptFrom( cursor.block().text() ); } // put statement to the history @@ -1111,10 +1111,10 @@ void PythonConsole::runSourceFromMimeData(const QString& source) void PythonConsole::overrideCursor(const QString& txt) { // Go to the last line and the fourth position, right after the prompt - QTextCursor cursor = this->inputBegin(); - int blockLength = this->textCursor().block().text().length(); - - cursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, blockLength ); //<< select text to override + QTextCursor cursor = this->inputBegin(); + int blockLength = this->textCursor().block().text().length(); + + cursor.movePosition( QTextCursor::Right, QTextCursor::KeepAnchor, blockLength ); //<< select text to override cursor.removeSelectedText(); cursor.insertText(txt); // move cursor to the end @@ -1126,7 +1126,7 @@ void PythonConsole::contextMenuEvent ( QContextMenuEvent * e ) { QMenu menu(this); QAction *a; - bool mayPasteHere = cursorBeyond( this->textCursor(), this->inputBegin() ); + bool mayPasteHere = cursorBeyond( this->textCursor(), this->inputBegin() ); a = menu.addAction(tr("&Copy"), this, SLOT(copy()), Qt::CTRL+Qt::Key_C); a->setEnabled(textCursor().hasSelection()); @@ -1144,7 +1144,7 @@ void PythonConsole::contextMenuEvent ( QContextMenuEvent * e ) a = menu.addAction(tr("&Paste"), this, SLOT(paste()), Qt::CTRL+Qt::Key_V); const QMimeData *md = QApplication::clipboard()->mimeData(); - a->setEnabled( mayPasteHere && md && canInsertFromMimeData(md)); + a->setEnabled( mayPasteHere && md && canInsertFromMimeData(md)); a = menu.addAction(tr("Select All"), this, SLOT(selectAll()), Qt::CTRL+Qt::Key_A); a->setEnabled(!document()->isEmpty()); @@ -1226,20 +1226,22 @@ void PythonConsole::onCopyCommand() d->type = PythonConsoleP::Normal; } -QString PythonConsole::readline( void ) -{ - QEventLoop loop; - QString inputBuffer; - - printPrompt( PythonConsole::Special ); - this->_sourceDrain = &inputBuffer; //< enable source drain ... - // ... and wait until we get notified about pendingSource - QObject::connect( this, SIGNAL(pendingSource()), &loop, SLOT(quit()) ); - loop.exec(); - this->_sourceDrain = NULL; //< disable source drain - return inputBuffer.append(QChar::fromAscii('\n')); //< pass a newline here, since the readline-caller may need it! -} - +QString PythonConsole::readline( void ) +{ + QEventLoop loop; + QString inputBuffer; + + printPrompt( PythonConsole::Special ); + this->_sourceDrain = &inputBuffer; //< enable source drain ... + // ... and wait until we get notified about pendingSource + QObject::connect( this, SIGNAL(pendingSource()), &loop, SLOT(quit()) ); + // application is about to quit + if (loop.exec() < 0) + inputBuffer = QLatin1String("quit()"); + this->_sourceDrain = NULL; //< disable source drain + return inputBuffer.append(QChar::fromAscii('\n')); //< pass a newline here, since the readline-caller may need it! +} + // --------------------------------------------------------------------- PythonConsoleHighlighter::PythonConsoleHighlighter(QObject* parent) @@ -1290,7 +1292,7 @@ void PythonConsoleHighlighter::colorChanged(const QString& type, const QColor& c // --------------------------------------------------------------------- ConsoleHistory::ConsoleHistory() -: _scratchBegin(0) +: _scratchBegin(0) { _it = _history.end(); } @@ -1391,28 +1393,28 @@ void ConsoleHistory::restart( void ) _it = _history.end(); } -/** - * markScratch stores the current end index of the history list. - * Note: with simply remembering a start index, it does not work to nest scratch regions. - * However, just replace the index keeping by a stack - in case this is be a concern. - */ -void ConsoleHistory::markScratch( void ) -{ - _scratchBegin = _history.length(); -} - -/** - * doScratch removes the tail of the history list, starting from the index marked lately. - */ -void ConsoleHistory::doScratch( void ) -{ - if (_scratchBegin < _history.length()) - { - _history.erase( _history.begin() + _scratchBegin, _history.end() ); - this->restart(); - } -} - +/** + * markScratch stores the current end index of the history list. + * Note: with simply remembering a start index, it does not work to nest scratch regions. + * However, just replace the index keeping by a stack - in case this is be a concern. + */ +void ConsoleHistory::markScratch( void ) +{ + _scratchBegin = _history.length(); +} + +/** + * doScratch removes the tail of the history list, starting from the index marked lately. + */ +void ConsoleHistory::doScratch( void ) +{ + if (_scratchBegin < _history.length()) + { + _history.erase( _history.begin() + _scratchBegin, _history.end() ); + this->restart(); + } +} + // ----------------------------------------------------- /* TRANSLATOR Gui::PythonInputField */ diff --git a/src/Mod/Complete/Gui/Workbench.cpp b/src/Mod/Complete/Gui/Workbench.cpp index d51928c2f..24cd90935 100644 --- a/src/Mod/Complete/Gui/Workbench.cpp +++ b/src/Mod/Complete/Gui/Workbench.cpp @@ -302,7 +302,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Part_Revolve" << "Part_Mirror" << "Part_Fillet" - << "PartDesign_Chamfer"; + << "Part_Chamfer"; // Drawing **************************************************************************************************** @@ -475,7 +475,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Part_Revolve" << "Part_Mirror" << "Part_Fillet" - << "PartDesign_Chamfer" + << "Part_Chamfer" ; // Sketch based diff --git a/src/Mod/Part/App/modelRefine.cpp b/src/Mod/Part/App/modelRefine.cpp index 25bb9378a..21170523f 100644 --- a/src/Mod/Part/App/modelRefine.cpp +++ b/src/Mod/Part/App/modelRefine.cpp @@ -214,6 +214,11 @@ void FaceAdjacencySplitter::recursiveFind(const TopoDS_Face &face, FaceVectorTyp TopTools_ListIteratorOfListOfShape edgeIt; for (edgeIt.Initialize(edges); edgeIt.More(); edgeIt.Next()) { + //don't try to join across seams. + ShapeAnalysis_Edge edgeCheck; + if(edgeCheck.IsSeam(TopoDS::Edge(edgeIt.Value()), face)) + continue; + const TopTools_ListOfShape &faces = edgeToFaceMap.FindFromKey(edgeIt.Value()); TopTools_ListIteratorOfListOfShape faceIt; for (faceIt.Initialize(faces); faceIt.More(); faceIt.Next()) @@ -421,25 +426,14 @@ GeomAbs_SurfaceType FaceTypedCylinder::getType() const TopoDS_Face FaceTypedCylinder::buildFace(const FaceVectorType &faces) const { + static TopoDS_Face dummy; std::vector boundaries; boundarySplit(faces, boundaries); - static TopoDS_Face dummy; if (boundaries.size() < 1) return dummy; - //take one face and remove all the wires. - TopoDS_Face workFace = faces.at(0); - ShapeBuild_ReShape reshaper; - TopExp_Explorer it; - for (it.Init(workFace, TopAbs_WIRE); it.More(); it.Next()) - reshaper.Remove(it.Current()); - workFace = TopoDS::Face(reshaper.Apply(workFace)); - if (workFace.IsNull()) - return TopoDS_Face(); - - ShapeFix_Face faceFixer(workFace); - - //makes wires + //make wires + std::vector wires; std::vector::iterator boundaryIt; for (boundaryIt = boundaries.begin(); boundaryIt != boundaries.end(); ++boundaryIt) { @@ -448,14 +442,38 @@ TopoDS_Face FaceTypedCylinder::buildFace(const FaceVectorType &faces) const for (it = (*boundaryIt).begin(); it != (*boundaryIt).end(); ++it) wireMaker.Add(*it); if (wireMaker.Error() != BRepLib_WireDone) - continue; - faceFixer.Add(wireMaker.Wire()); + return dummy; + wires.push_back(wireMaker.Wire()); } + if (wires.size() < 1) + return dummy; + std::sort(wires.begin(), wires.end(), ModelRefine::WireSort()); + + //make face from surface and outer wire. + Handle(Geom_CylindricalSurface) surface = Handle(Geom_CylindricalSurface)::DownCast(BRep_Tool::Surface(faces.at(0))); + std::vector::iterator wireIt; + wireIt = wires.begin(); + BRepBuilderAPI_MakeFace faceMaker(surface, *wireIt); + if (!faceMaker.IsDone()) + return dummy; + + //add additional boundaries. + for (wireIt++; wireIt != wires.end(); ++wireIt) + { + faceMaker.Add(*wireIt); + if (!faceMaker.IsDone()) + return dummy; + } + + //fix newly constructed face. Orientation doesn't seem to get fixed the first call. + ShapeFix_Face faceFixer(faceMaker.Face()); + faceFixer.SetContext(new ShapeBuild_ReShape()); if (faceFixer.Perform() > ShapeExtend_DONE5) - return TopoDS_Face(); + return dummy; faceFixer.FixOrientation(); if (faceFixer.Perform() > ShapeExtend_DONE5) - return TopoDS_Face(); + return dummy; + return faceFixer.Face(); } diff --git a/src/Mod/Part/Gui/CMakeLists.txt b/src/Mod/Part/Gui/CMakeLists.txt index ec068eb95..f6a41f935 100644 --- a/src/Mod/Part/Gui/CMakeLists.txt +++ b/src/Mod/Part/Gui/CMakeLists.txt @@ -42,6 +42,7 @@ set(PartGui_MOC_HDRS TaskShapeBuilder.h TaskLoft.h TaskSweep.h + TaskCheckGeometry.h ) fc_wrap_cpp(PartGui_MOC_SRCS ${PartGui_MOC_HDRS}) SOURCE_GROUP("Moc" FILES ${PartGui_MOC_SRCS}) @@ -160,6 +161,8 @@ SET(PartGui_SRCS TaskSweep.cpp TaskSweep.h TaskSweep.ui + TaskCheckGeometry.cpp + TaskCheckGeometry.h ) SET(PartGui_Scripts diff --git a/src/Mod/Part/Gui/Command.cpp b/src/Mod/Part/Gui/Command.cpp index edd0c41e6..11164ce67 100644 --- a/src/Mod/Part/Gui/Command.cpp +++ b/src/Mod/Part/Gui/Command.cpp @@ -62,6 +62,7 @@ #include "TaskShapeBuilder.h" #include "TaskLoft.h" #include "TaskSweep.h" +#include "TaskCheckGeometry.h" //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -1199,6 +1200,39 @@ bool CmdPartRuledSurface::isActive(void) return getActiveGuiDocument(); } +//=========================================================================== +// Part_CheckGeometry +//=========================================================================== + +DEF_STD_CMD_A(CmdCheckGeometry); + +CmdCheckGeometry::CmdCheckGeometry() + : Command("Part_CheckGeometry") +{ + sAppModule = "Part"; + sGroup = QT_TR_NOOP("Part"); + sMenuText = QT_TR_NOOP("Check Geometry"); + sToolTipText = QT_TR_NOOP("Analyzes Geometry For Errors"); + sWhatsThis = sToolTipText; + sStatusTip = sToolTipText; + sPixmap = "Part_CheckGeometry"; +} + +void CmdCheckGeometry::activated(int iMsg) +{ + Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); + if (!dlg) + dlg = new PartGui::TaskCheckGeometryDialog(); + Gui::Control().showDialog(dlg); +} + +bool CmdCheckGeometry::isActive(void) +{ + Base::Type partid = Base::Type::fromName("Part::Feature"); + bool objectsSelected = Gui::Selection().countObjectsOfType(partid) > 0; + return (hasActiveDocument() && !Gui::Control().activeDialog() && objectsSelected); +} + void CreatePartCommands(void) { @@ -1230,5 +1264,5 @@ void CreatePartCommands(void) rcCmdMgr.addCommand(new CmdPartBuilder()); rcCmdMgr.addCommand(new CmdPartLoft()); rcCmdMgr.addCommand(new CmdPartSweep()); + rcCmdMgr.addCommand(new CmdCheckGeometry()); } - diff --git a/src/Mod/Part/Gui/Makefile.am b/src/Mod/Part/Gui/Makefile.am index f214d928d..1ce6da22c 100644 --- a/src/Mod/Part/Gui/Makefile.am +++ b/src/Mod/Part/Gui/Makefile.am @@ -33,6 +33,7 @@ BUILT_SOURCES=\ moc_DlgSettings3DViewPartImp.cpp \ moc_DlgSettingsGeneral.cpp \ moc_Mirroring.cpp \ + moc_TaskCheckGeometry.cpp \ moc_TaskFaceColors.cpp \ moc_TaskShapeBuilder.cpp \ moc_TaskLoft.cpp \ @@ -69,6 +70,8 @@ libPartGui_la_SOURCES=\ DlgSettingsGeneral.h \ Mirroring.cpp \ Mirroring.h \ + TaskCheckGeometry.cpp \ + TaskCheckGeometry.h \ TaskFaceColors.cpp \ TaskFaceColors.h \ TaskShapeBuilder.cpp \ @@ -194,6 +197,7 @@ EXTRA_DIST = \ Resources/icons/Part_Sphere.svg \ Resources/icons/Part_Booleans.svg \ Resources/icons/Part_Chamfer.svg \ + Resources/icons/Part_CheckGeometry.svg \ Resources/icons/Part_Cut.svg \ Resources/icons/Part_Common.svg \ Resources/icons/Part_CreatePrimitives.png \ diff --git a/src/Mod/Part/Gui/Resources/Part.qrc b/src/Mod/Part/Gui/Resources/Part.qrc index f6fbdf0c3..93c983dc2 100644 --- a/src/Mod/Part/Gui/Resources/Part.qrc +++ b/src/Mod/Part/Gui/Resources/Part.qrc @@ -1,48 +1,49 @@ - - - icons/PartFeature.svg - icons/PartFeature.xpm - icons/PartFeatureImport.xpm - icons/Part_Booleans.svg - icons/Part_Box.svg - icons/Part_Chamfer.svg - icons/Part_Common.svg - icons/Part_Cone.svg - icons/Part_Cut.svg - icons/Part_CreatePrimitives.png - icons/Part_CreatePrimitives.svg - icons/Part_Cylinder.svg - icons/Part_Extrude.svg - icons/Part_Fillet.svg - icons/Part_Fuse.svg - icons/Part_Import.svg - icons/Part_Loft.svg - icons/Part_Mirror.svg - icons/Part_MirrorPNG.png - icons/Part_Revolve.svg - icons/Part_RuledSurface.svg - icons/Part_Section.svg - icons/Part_Shapebuilder.png - icons/Part_ShapeInfo.svg - icons/Part_Sphere.svg - icons/Part_Sweep.svg - icons/Part_Torus.svg - icons/preferences-part_design.svg - icons/Tree_Part.svg - translations/Part_af.qm - translations/Part_de.qm - translations/Part_es.qm - translations/Part_fi.qm - translations/Part_fr.qm - translations/Part_hr.qm - translations/Part_it.qm - translations/Part_nl.qm - translations/Part_no.qm - translations/Part_pl.qm - translations/Part_pt.qm - translations/Part_ru.qm - translations/Part_se.qm - translations/Part_uk.qm - translations/Part_zh.qm - - + + + icons/PartFeature.svg + icons/PartFeature.xpm + icons/PartFeatureImport.xpm + icons/Part_Booleans.svg + icons/Part_Box.svg + icons/Part_Chamfer.svg + icons/Part_Common.svg + icons/Part_Cone.svg + icons/Part_Cut.svg + icons/Part_CreatePrimitives.png + icons/Part_CreatePrimitives.svg + icons/Part_Cylinder.svg + icons/Part_Extrude.svg + icons/Part_Fillet.svg + icons/Part_Fuse.svg + icons/Part_Import.svg + icons/Part_Loft.svg + icons/Part_Mirror.svg + icons/Part_MirrorPNG.png + icons/Part_Revolve.svg + icons/Part_RuledSurface.svg + icons/Part_Section.svg + icons/Part_Shapebuilder.png + icons/Part_ShapeInfo.svg + icons/Part_Sphere.svg + icons/Part_Sweep.svg + icons/Part_Torus.svg + icons/preferences-part_design.svg + icons/Tree_Part.svg + icons/Part_CheckGeometry.svg + translations/Part_af.qm + translations/Part_de.qm + translations/Part_es.qm + translations/Part_fi.qm + translations/Part_fr.qm + translations/Part_hr.qm + translations/Part_it.qm + translations/Part_nl.qm + translations/Part_no.qm + translations/Part_pl.qm + translations/Part_pt.qm + translations/Part_ru.qm + translations/Part_se.qm + translations/Part_uk.qm + translations/Part_zh.qm + + diff --git a/src/Mod/Part/Gui/Resources/icons/Part_CheckGeometry.svg b/src/Mod/Part/Gui/Resources/icons/Part_CheckGeometry.svg new file mode 100644 index 000000000..975ce5807 --- /dev/null +++ b/src/Mod/Part/Gui/Resources/icons/Part_CheckGeometry.svg @@ -0,0 +1,286 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.cpp b/src/Mod/Part/Gui/TaskCheckGeometry.cpp new file mode 100644 index 000000000..bf220e3cc --- /dev/null +++ b/src/Mod/Part/Gui/TaskCheckGeometry.cpp @@ -0,0 +1,693 @@ +/*************************************************************************** + * Copyright (c) 2012 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../App/PartFeature.h" +#include +#include +#include +#include +#include +#include +#include "TaskCheckGeometry.h" + +using namespace PartGui; + +QVector buildShapeEnumVector() +{ + QVectornames; + names.push_back(QObject::tr("Compound")); //TopAbs_COMPOUND + names.push_back(QObject::tr("Compound Solid")); //TopAbs_COMPSOLID + names.push_back(QObject::tr("Solid")); //TopAbs_SOLID + names.push_back(QObject::tr("Shell")); //TopAbs_SHELL + names.push_back(QObject::tr("Face")); //TopAbs_FACE + names.push_back(QObject::tr("Wire")); //TopAbs_WIRE + names.push_back(QObject::tr("Edge")); //TopAbs_EDGE + names.push_back(QObject::tr("Vertex")); //TopAbs_VERTEX + names.push_back(QObject::tr("Shape")); //TopAbs_SHAPE + return names; +} + +QString shapeEnumToString(const int &index) +{ + static QVector names = buildShapeEnumVector(); + if (index < 0 || index > TopAbs_SHAPE) + return names.at(8); + return names.at(index); +} + +QVector buildCheckStatusStringVector() +{ + QVectornames; + names.push_back(QObject::tr("No Error")); // BRepCheck_NoError + names.push_back(QObject::tr("Invalid Point On Curve")); // BRepCheck_InvalidPointOnCurve + names.push_back(QObject::tr("Invalid Point On Curve On Surface")); // BRepCheck_InvalidPointOnCurveOnSurface + names.push_back(QObject::tr("Invalid Point On Surface")); // BRepCheck_InvalidPointOnSurface + names.push_back(QObject::tr("No 3D Curve")); // BRepCheck_No3DCurve + names.push_back(QObject::tr("Multiple 3D Curve")); // BRepCheck_Multiple3DCurve + names.push_back(QObject::tr("Invalid 3D Curve")); // BRepCheck_Invalid3DCurve + names.push_back(QObject::tr("No Curve On Surface")); // BRepCheck_NoCurveOnSurface + names.push_back(QObject::tr("Invalid Curve On Surface")); // BRepCheck_InvalidCurveOnSurface + names.push_back(QObject::tr("Invalid Curve On Closed Surface")); // BRepCheck_InvalidCurveOnClosedSurface + names.push_back(QObject::tr("Invalid Same Range Flag")); // BRepCheck_InvalidSameRangeFlag + names.push_back(QObject::tr("Invalid Same Parameter Flag")); // BRepCheck_InvalidSameParameterFlag + names.push_back(QObject::tr("Invalid Degenerated Flag")); // BRepCheck_InvalidDegeneratedFlag + names.push_back(QObject::tr("Free Edge")); // BRepCheck_FreeEdge + names.push_back(QObject::tr("Invalid MultiConnexity")); // BRepCheck_InvalidMultiConnexity + names.push_back(QObject::tr("Invalid Range")); // BRepCheck_InvalidRange + names.push_back(QObject::tr("Empty Wire")); // BRepCheck_EmptyWire + names.push_back(QObject::tr("Redundant Edge")); // BRepCheck_RedundantEdge + names.push_back(QObject::tr("Self Intersecting Wire")); // BRepCheck_SelfIntersectingWire + names.push_back(QObject::tr("No Surface")); // BRepCheck_NoSurface + names.push_back(QObject::tr("Invalid Wire")); // BRepCheck_InvalidWire + names.push_back(QObject::tr("Redundant Wire")); // BRepCheck_RedundantWire + names.push_back(QObject::tr("Intersecting Wires")); // BRepCheck_IntersectingWires + names.push_back(QObject::tr("Invalid Imbrication Of Wires")); // BRepCheck_InvalidImbricationOfWires + names.push_back(QObject::tr("Empty Shell")); // BRepCheck_EmptyShell + names.push_back(QObject::tr("Redundant Face")); // BRepCheck_RedundantFace + names.push_back(QObject::tr("Unorientable Shape")); // BRepCheck_UnorientableShape + names.push_back(QObject::tr("Not Closed")); // BRepCheck_NotClosed + names.push_back(QObject::tr("Not Connected")); // BRepCheck_NotConnected + names.push_back(QObject::tr("Sub Shape Not In Shape")); // BRepCheck_SubshapeNotInShape + names.push_back(QObject::tr("Bad Orientation")); // BRepCheck_BadOrientation + names.push_back(QObject::tr("Bad Orientation Of Sub Shape")); // BRepCheck_BadOrientationOfSubshape + names.push_back(QObject::tr("Invalid Tolerance Value")); // BRepCheck_InvalidToleranceValue + names.push_back(QObject::tr("Check Failed")); // BRepCheck_CheckFail + + return names; +} + +QString checkStatusToString(const int &index) +{ + static QVector names = buildCheckStatusStringVector(); + if (index == -1) + { + return QString(QObject::tr("No Result")); + } + if (index > 33 || index < 0) + { + QString message(QObject::tr("Out Of Enum Range: ")); + QString number; + number.setNum(index); + message += number; + return message; + } + return names.at(index); +} + +ResultEntry::ResultEntry() +{ + viewProvider = 0; + boxSep = 0; + boxSwitch = 0; + parent = 0; + children.clear(); + selectionStrings.clear(); +} + +ResultEntry::~ResultEntry() +{ + if (boxSep) + viewProvider->getRoot()->removeChild(boxSep); + qDeleteAll(children); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ResultModel::ResultModel(QObject *parent) : QAbstractItemModel(parent) +{ + root = 0; +} + +ResultModel::~ResultModel() +{ + if (root) + delete root; +} + +QModelIndex ResultModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!root) + return QModelIndex(); + ResultEntry *parentNode = nodeFromIndex(parent); + if (!parentNode) + return QModelIndex(); + return createIndex(row, column, parentNode->children.at(row)); +} + +QModelIndex ResultModel::parent(const QModelIndex &child) const +{ + ResultEntry *childNode = nodeFromIndex(child); + if (!childNode) + return QModelIndex(); + ResultEntry *parentNode = childNode->parent; + if (!parentNode) + return QModelIndex(); + ResultEntry *grandParentNode = parentNode->parent; + if (!grandParentNode) + return QModelIndex(); + int row = grandParentNode->children.indexOf(parentNode); + return createIndex(row, 0, parentNode); +} + +int ResultModel::rowCount(const QModelIndex &parent) const +{ + ResultEntry *parentNode = nodeFromIndex(parent); + if (!parentNode) + return 0; + return parentNode->children.size(); +} + +int ResultModel::columnCount(const QModelIndex &parent) const +{ + return 3; +} + +QVariant ResultModel::data(const QModelIndex &index, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + ResultEntry *node = nodeFromIndex(index); + if (!node) + return QVariant(); + switch (index.column()) + { + case 0: + return QVariant(node->name); + case 1: + return QVariant(node->type); + case 2: + return QVariant(node->error); + } + return QVariant(); +} + +QVariant ResultModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal || role != Qt::DisplayRole) + return QVariant(); + switch (section) + { + case 0: + return QVariant(QString(tr("Name"))); + case 1: + return QVariant(QString(tr("Type"))); + case 2: + return QVariant(QString(tr("Error"))); + } + return QVariant(); +} + +void ResultModel::setResults(ResultEntry *resultsIn) +{ +#if QT_VERSION >= 0x040600 + this->beginResetModel(); +#endif + if (root) + delete root; + root = resultsIn; +#if QT_VERSION >= 0x040600 + this->endResetModel(); +#else + this->reset(); +#endif +} + +ResultEntry* ResultModel::getEntry(const QModelIndex &index) +{ + return nodeFromIndex(index); +} + +ResultEntry* ResultModel::nodeFromIndex(const QModelIndex &index) const +{ + if (index.isValid()) + return static_cast(index.internalPointer()); + else + return root; +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TaskCheckGeometryResults::TaskCheckGeometryResults(QWidget *parent) : QWidget(parent) +{ + this->setWindowTitle(tr("Check Geometry")); + setupInterface(); + setupFunctionMap(); + goCheck(); +} + +TaskCheckGeometryResults::~TaskCheckGeometryResults() +{ + Gui::Selection().clearSelection(); +} + +void TaskCheckGeometryResults::setupInterface() +{ + message = new QLabel(this); + model = new ResultModel(this); + treeView = new QTreeView(this); + treeView->setModel(model); + treeView->setSelectionMode(QAbstractItemView::SingleSelection); + treeView->setSelectionBehavior(QAbstractItemView::SelectRows); + connect(treeView->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + this, SLOT(currentRowChanged(QModelIndex,QModelIndex))); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(message); + layout->addWidget(treeView); + this->setLayout(layout); +} + +void TaskCheckGeometryResults::goCheck() +{ + Gui::WaitCursor wc; + int selectedCount(0), checkedCount(0), invalidShapes(0); + std::vector selection = Gui::Selection().getSelection(); + std::vector::iterator it; + ResultEntry *theRoot = new ResultEntry(); + for (it = selection.begin(); it != selection.end(); ++it) + { + selectedCount++; + Part::Feature *feature = dynamic_cast((*it).pObject); + if (!feature) + continue; + currentProvider = Gui::Application::Instance->activeDocument()->getViewProvider(feature); + if (!currentProvider) + continue; + TopoDS_Shape shape = feature->Shape.getValue(); + QString baseName; + QTextStream baseStream(&baseName); + baseStream << (*it).DocName; + baseStream << "." << (*it).FeatName; + if (strlen((*it).SubName) > 0) + { + shape = feature->Shape.getShape().getSubShape((*it).SubName); + baseStream << "." << (*it).SubName; + } + + if (shape.IsNull()) + continue; + checkedCount++; + checkedMap.Clear(); + + BRepCheck_Analyzer shapeCheck(shape); + if (!shapeCheck.IsValid()) + { + invalidShapes++; + ResultEntry *entry = new ResultEntry(); + entry->parent = theRoot; + entry->shape = shape; + entry->name = baseName; + entry->type = shapeEnumToString(shape.ShapeType()); + entry->error = QObject::tr("Invalid"); + entry->viewProvider = currentProvider; + getSetupResultBoundingBoxObject().go(entry); + theRoot->children.push_back(entry); + recursiveCheck(shapeCheck, shape, entry); + } + } + model->setResults(theRoot); + treeView->expandAll(); + treeView->header()->resizeSections(QHeaderView::ResizeToContents); + QString aMessage; + QTextStream aStream(&aMessage); + aStream << checkedCount << " processed out of " << selectedCount << " selected\n"; + aStream << invalidShapes << " invalid shapes."; + message->setText(aMessage); + Gui::Selection().clearSelection(); +} + +void TaskCheckGeometryResults::recursiveCheck(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape, + ResultEntry *parent) +{ + ResultEntry *branchNode = parent; + BRepCheck_ListIteratorOfListOfStatus listIt; + if (!shapeCheck.Result(shape).IsNull() && !checkedMap.Contains(shape)) + { + listIt.Initialize(shapeCheck.Result(shape)->Status()); + if (listIt.Value() != BRepCheck_NoError) + { + ResultEntry *entry = new ResultEntry(); + entry->parent = parent; + entry->shape = shape; + entry->type = shapeEnumToString(shape.ShapeType()); + entry->error = checkStatusToString(listIt.Value()); + entry->viewProvider = currentProvider; + dispatchError(entry, listIt.Value()); + parent->children.push_back(entry); + branchNode = entry; + } + } + checkedMap.Add(shape); + + if (shape.ShapeType() == TopAbs_SOLID) + checkSub(shapeCheck, shape, TopAbs_SHELL, branchNode); + if (shape.ShapeType() == TopAbs_EDGE) + checkSub(shapeCheck, shape, TopAbs_VERTEX, branchNode); + if (shape.ShapeType() == TopAbs_FACE) + { + checkSub(shapeCheck, shape, TopAbs_WIRE, branchNode); + checkSub(shapeCheck, shape, TopAbs_EDGE, branchNode); + checkSub(shapeCheck, shape, TopAbs_VERTEX, branchNode); + } + + for (TopoDS_Iterator it(shape); it.More(); it.Next()) + recursiveCheck(shapeCheck, it.Value(), branchNode); +} + +void TaskCheckGeometryResults::checkSub(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape, + const TopAbs_ShapeEnum subType, ResultEntry *parent) +{ + BRepCheck_ListIteratorOfListOfStatus itl; + TopExp_Explorer exp; + for (exp.Init(shape,subType); exp.More(); exp.Next()) + { + const Handle(BRepCheck_Result)& res = shapeCheck.Result(exp.Current()); + const TopoDS_Shape& sub = exp.Current(); + for (res->InitContextIterator(); res->MoreShapeInContext(); res->NextShapeInContext()) + { + if (res->ContextualShape().IsSame(shape)) + { + for (itl.Initialize(res->StatusOnShape()); itl.More(); itl.Next()) + { + if (itl.Value() == BRepCheck_NoError) + break; + checkedMap.Add(sub); + ResultEntry *entry = new ResultEntry(); + entry->parent = parent; + entry->shape = sub; + entry->type = shapeEnumToString(sub.ShapeType()); + entry->error = checkStatusToString(itl.Value()); + entry->viewProvider = currentProvider; + dispatchError(entry, itl.Value()); + parent->children.push_back(entry); + } + } + } + } +} + +void TaskCheckGeometryResults::dispatchError(ResultEntry *entry, const BRepCheck_Status &stat) +{ + std::vector::iterator mapIt; + for (mapIt = functionMap.begin(); mapIt != functionMap.end(); ++mapIt) + { + if ((*mapIt).get<0>() == entry->shape.ShapeType() && (*mapIt).get<1>() == stat) + { + ((*mapIt).get<2>())->go(entry); + return; + } + } + getSetupResultBoundingBoxObject().go(entry); +} + +void TaskCheckGeometryResults::setupFunctionMap() +{ + functionMap.push_back(FunctionMapType(TopAbs_SHELL, BRepCheck_NotClosed, &getSetupResultShellNotClosedObject())); + functionMap.push_back(FunctionMapType(TopAbs_WIRE, BRepCheck_NotClosed, &getSetupResultWireNotClosedObject())); + functionMap.push_back(FunctionMapType(TopAbs_VERTEX, BRepCheck_InvalidPointOnCurve, &getSetupResultInvalidPointCurveObject())); + functionMap.push_back(FunctionMapType(TopAbs_FACE, BRepCheck_IntersectingWires, &getSetupResultIntersectingWiresObject())); + functionMap.push_back(FunctionMapType(TopAbs_EDGE, BRepCheck_InvalidCurveOnSurface, &getSetupResultInvalidCurveSurfaceObject())); + functionMap.push_back(FunctionMapType(TopAbs_EDGE, BRepCheck_InvalidSameParameterFlag, &getSetupResultInvalidSameParameterFlagObject())); + functionMap.push_back(FunctionMapType(TopAbs_FACE, BRepCheck_UnorientableShape, &getSetupResultUnorientableShapeFaceObject())); +} + +void TaskCheckGeometryResults::currentRowChanged (const QModelIndex ¤t, const QModelIndex &previous) +{ + Gui::Selection().clearSelection(); + if (previous.isValid()) + { + ResultEntry *entry = model->getEntry(previous); + if (entry) + { + if (entry->boxSwitch) + entry->boxSwitch->whichChild.setValue(SO_SWITCH_NONE); + } + } + if (current.isValid()) + { + ResultEntry *entry = model->getEntry(current); + if (entry) + { + if (entry->boxSwitch) + entry->boxSwitch->whichChild.setValue(0); + QStringList::Iterator stringIt; + for (stringIt = entry->selectionStrings.begin(); stringIt != entry->selectionStrings.end(); ++stringIt) + { + //need unique delimiter. + QString doc, object, sub; + if (!this->split((*stringIt), doc, object, sub)) + continue; + Gui::Selection().addSelection(doc.toAscii(), object.toAscii(), sub.toAscii()); + } + } + } +} + +bool TaskCheckGeometryResults::split(QString &input, QString &doc, QString &object, QString &sub) +{ + QStringList strings = input.split(QString::fromAscii(".")); + if (strings.size() != 3) + return false; + doc = strings.at(0); + object = strings.at(1); + sub = strings.at(2); + return true; +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +QString SetupResultBase::selectionName(ResultEntry *entry, const TopoDS_Shape &shape) +{ + ResultEntry *parentEntry = entry; + while(parentEntry->name.isEmpty()) + parentEntry = parentEntry->parent; + + QString stringOut; + QTextStream stream(&stringOut); + stream << parentEntry->name; + stream << '.'; + TopTools_IndexedMapOfShape shapeMap; + int index(-1); + + switch (shape.ShapeType()) + { + case TopAbs_FACE: + TopExp::MapShapes(parentEntry->shape, TopAbs_FACE, shapeMap); + stream << "Face"; + break; + case TopAbs_EDGE: + TopExp::MapShapes(parentEntry->shape, TopAbs_EDGE, shapeMap); + stream << "Edge"; + break; + case TopAbs_VERTEX: + TopExp::MapShapes(parentEntry->shape, TopAbs_VERTEX, shapeMap); + stream << "Vertex"; + break; + } + + index = shapeMap.FindIndex(shape); + stream << index; + return stringOut; +} + +void SetupResultBase::addTypedSelection(ResultEntry *entry, const TopoDS_Shape &shape, TopAbs_ShapeEnum type) +{ + TopExp_Explorer it; + for (it.Init(shape, type); it.More(); it.Next()) + { + QString name = selectionName(entry, (it.Current())); + if (!name.isEmpty()) + entry->selectionStrings.append(name); + } +} + +void SetupResultBoundingBox::go(ResultEntry *entry) +{ + entry->boxSep = new SoSeparator(); + entry->viewProvider->getRoot()->addChild(entry->boxSep); + entry->boxSwitch = new SoSwitch(); + entry->boxSep->addChild(entry->boxSwitch); + SoGroup *group = new SoGroup(); + entry->boxSwitch->addChild(group); + entry->boxSwitch->whichChild.setValue(SO_SWITCH_NONE); + SoDrawStyle *dStyle = new SoDrawStyle(); + dStyle->style.setValue(SoDrawStyle::LINES); + dStyle->linePattern.setValue(0xc0c0); + group->addChild(dStyle); + SoMaterial *material = new SoMaterial(); + material->diffuseColor.setValue(255.0, 255.0, 255.0); + material->ambientColor.setValue(255.0, 255.0, 255.0); + group->addChild(material); + + Bnd_Box boundingBox; + BRepBndLib::Add(entry->shape, boundingBox); + Standard_Real xmin, ymin, zmin, xmax, ymax, zmax; + boundingBox.Get(xmin, ymin, zmin, xmax, ymax, zmax); + + double xCenter, yCenter, zCenter; + xCenter = (xmax - xmin)/2 + xmin; + yCenter = (ymax - ymin)/2 + ymin; + zCenter = (zmax - zmin)/2 + zmin; + + SbVec3f boundCenter(xCenter, yCenter, zCenter); + Standard_Real x, y, z; + entry->shape.Location().Transformation().TranslationPart().Coord(x, y, z); + boundCenter -= SbVec3f(x, y, z); + + SoTransform *position = new SoTransform(); + position->translation.setValue(boundCenter); + group->addChild(position); + + SoCube *cube = new SoCube(); + cube->width.setValue(xmax - xmin); + cube->height.setValue(ymax - ymin); + cube->depth.setValue(zmax - zmin); + group->addChild(cube); +} + +SetupResultBoundingBox& PartGui::getSetupResultBoundingBoxObject() +{ + static SetupResultBoundingBox object; + return object; +} + +void SetupResultShellNotClosed::go(ResultEntry *entry) +{ + ShapeAnalysis_FreeBounds shellCheck(entry->shape); + TopoDS_Compound closedWires = shellCheck.GetClosedWires(); + TopoDS_Compound openWires = shellCheck.GetOpenWires(); + + addTypedSelection(entry, closedWires, TopAbs_EDGE); + addTypedSelection(entry, openWires, TopAbs_EDGE); +} + +SetupResultShellNotClosed& PartGui::getSetupResultShellNotClosedObject() +{ + static SetupResultShellNotClosed object; + return object; +} + +void SetupResultWireNotClosed::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_EDGE); +} + +SetupResultWireNotClosed& PartGui::getSetupResultWireNotClosedObject() +{ + static SetupResultWireNotClosed object; + return object; +} + +void SetupResultInvalidPointCurve::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_VERTEX); +} + +SetupResultInvalidPointCurve& PartGui::getSetupResultInvalidPointCurveObject() +{ + static SetupResultInvalidPointCurve object; + return object; +} + +void SetupResultIntersectingWires::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_FACE); +} + +SetupResultIntersectingWires& PartGui::getSetupResultIntersectingWiresObject() +{ + static SetupResultIntersectingWires object; + return object; +} + +void SetupResultInvalidCurveSurface::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_EDGE); +} + +SetupResultInvalidCurveSurface& PartGui::getSetupResultInvalidCurveSurfaceObject() +{ + static SetupResultInvalidCurveSurface object; + return object; +} + +void SetupResultInvalidSameParameterFlag::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_EDGE); +} + +SetupResultInvalidSameParameterFlag& PartGui::getSetupResultInvalidSameParameterFlagObject() +{ + static SetupResultInvalidSameParameterFlag object; + return object; +} + +void SetupResultUnorientableShapeFace::go(ResultEntry *entry) +{ + addTypedSelection(entry, entry->shape, TopAbs_FACE); + getSetupResultBoundingBoxObject().go(entry); +} + +SetupResultUnorientableShapeFace& PartGui::getSetupResultUnorientableShapeFaceObject() +{ + static SetupResultUnorientableShapeFace object; + return object; +} + +//////////////////////////////////////////////////////////////////////////////////////////////// + +TaskCheckGeometryDialog::TaskCheckGeometryDialog() +{ + this->setButtonPosition(TaskDialog::South); + widget = new TaskCheckGeometryResults(); + taskbox = new Gui::TaskView::TaskBox( + Gui::BitmapFactory().pixmap("Part_CheckGeometry"), + widget->windowTitle(), false, 0); + taskbox->groupLayout()->addWidget(widget); + Content.push_back(taskbox); +} + +TaskCheckGeometryDialog::~TaskCheckGeometryDialog() +{ + +} + +#include "moc_TaskCheckGeometry.cpp" diff --git a/src/Mod/Part/Gui/TaskCheckGeometry.h b/src/Mod/Part/Gui/TaskCheckGeometry.h new file mode 100644 index 000000000..8aacf319b --- /dev/null +++ b/src/Mod/Part/Gui/TaskCheckGeometry.h @@ -0,0 +1,219 @@ +/*************************************************************************** + * Copyright (c) 2012 Thomas Anderson * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef TASKCHECKGEOMETRY_H +#define TASKCHECKGEOMETRY_H + +#include +#include +#include +#include +#include +#include +#include + +class SoSeparator; +class SoSwitch; + +namespace PartGui { + +class ResultEntry +{ +public: + ResultEntry(); + ~ResultEntry(); + + TopoDS_Shape shape;//invisible + QString name; + QString type; + QString error; + Gui::ViewProvider *viewProvider; + SoSeparator *boxSep; + SoSwitch *boxSwitch; + ResultEntry *parent; + QList children; + QStringList selectionStrings; +}; + +class SetupResultBase +{ +protected: + SetupResultBase(){} +public: + virtual void go(ResultEntry *entry) = 0; +protected: + QString selectionName(ResultEntry *entry, const TopoDS_Shape &shape); + void addTypedSelection(ResultEntry *entry, const TopoDS_Shape &shape, TopAbs_ShapeEnum type); +}; + +class SetupResultBoundingBox : public SetupResultBase +{ +private: + SetupResultBoundingBox(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultBoundingBox& getSetupResultBoundingBoxObject(); +}; +SetupResultBoundingBox& getSetupResultBoundingBoxObject(); + +class SetupResultShellNotClosed : public SetupResultBase +{ +private: + SetupResultShellNotClosed(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultShellNotClosed& getSetupResultShellNotClosedObject(); +}; +SetupResultShellNotClosed& getSetupResultShellNotClosedObject(); + +class SetupResultWireNotClosed : public SetupResultBase +{ +private: + SetupResultWireNotClosed(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultWireNotClosed& getSetupResultWireNotClosedObject(); +}; +SetupResultWireNotClosed& getSetupResultWireNotClosedObject(); + +class SetupResultInvalidPointCurve : public SetupResultBase +{ +private: + SetupResultInvalidPointCurve(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultInvalidPointCurve& getSetupResultInvalidPointCurveObject(); +}; +SetupResultInvalidPointCurve& getSetupResultInvalidPointCurveObject(); + +class SetupResultIntersectingWires : public SetupResultBase +{ +private: + SetupResultIntersectingWires(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultIntersectingWires& getSetupResultIntersectingWiresObject(); +}; +SetupResultIntersectingWires& getSetupResultIntersectingWiresObject(); + +class SetupResultInvalidCurveSurface : public SetupResultBase +{ +private: + SetupResultInvalidCurveSurface(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultInvalidCurveSurface& getSetupResultInvalidCurveSurfaceObject(); +}; +SetupResultInvalidCurveSurface& getSetupResultInvalidCurveSurfaceObject(); + +class SetupResultInvalidSameParameterFlag : public SetupResultBase +{ +private: + SetupResultInvalidSameParameterFlag(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultInvalidSameParameterFlag& getSetupResultInvalidSameParameterFlagObject(); +}; +SetupResultInvalidSameParameterFlag& getSetupResultInvalidSameParameterFlagObject(); + +class SetupResultUnorientableShapeFace : public SetupResultBase +{ +private: + SetupResultUnorientableShapeFace(){} +public: + virtual void go(ResultEntry *entry); + friend SetupResultUnorientableShapeFace& getSetupResultUnorientableShapeFaceObject(); +}; +SetupResultUnorientableShapeFace& getSetupResultUnorientableShapeFaceObject(); + +typedef boost::tuple FunctionMapType; + +class ResultModel : public QAbstractItemModel +{ + Q_OBJECT +public: + ResultModel(QObject *parent = 0); + ~ResultModel(); + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + virtual int rowCount(const QModelIndex &parent) const; + virtual int columnCount(const QModelIndex &parent) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; +// virtual Qt::ItemFlags flags (const QModelIndex &index) const; + + void setResults(ResultEntry *resultsIn); + ResultEntry* getEntry(const QModelIndex &index); +private: + ResultEntry* nodeFromIndex(const QModelIndex &index) const; + ResultEntry *root; +}; + +class TaskCheckGeometryResults : public QWidget +{ + Q_OBJECT +public: + TaskCheckGeometryResults(QWidget *parent = 0); + ~TaskCheckGeometryResults(); + +private slots: + void currentRowChanged (const QModelIndex ¤t, const QModelIndex &previous); + +private: + void setupInterface(); + void goCheck(); + void recursiveCheck(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape, + ResultEntry *parent); + void checkSub(const BRepCheck_Analyzer &shapeCheck, const TopoDS_Shape &shape, + const TopAbs_ShapeEnum subType, ResultEntry *parent); + void dispatchError(ResultEntry *entry, const BRepCheck_Status &stat); + bool split(QString &input, QString &doc, QString &object, QString &sub); + void setupFunctionMap(); + ResultModel *model; + QTreeView *treeView; + QLabel *message; + TopTools_MapOfShape checkedMap; + Gui::ViewProvider *currentProvider; + std::vector functionMap; +}; + +class TaskCheckGeometryDialog : public Gui::TaskView::TaskDialog +{ + Q_OBJECT +public: + TaskCheckGeometryDialog(); + ~TaskCheckGeometryDialog(); + + virtual QDialogButtonBox::StandardButtons getStandardButtons() const + {return QDialogButtonBox::Close;} + virtual bool isAllowedAlterDocument(void) const + {return false;} + virtual bool needsFullSpace() const {return true;} + +private: + TaskCheckGeometryResults* widget; + Gui::TaskView::TaskBox* taskbox; +}; + +} + +#endif // TASKCHECKGEOMETRY_H diff --git a/src/Mod/Part/Gui/Workbench.cpp b/src/Mod/Part/Gui/Workbench.cpp index d6b6b113a..ddd441b7d 100644 --- a/src/Mod/Part/Gui/Workbench.cpp +++ b/src/Mod/Part/Gui/Workbench.cpp @@ -69,7 +69,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const *part << "Part_Import" << "Part_Export" << "Separator"; *part << prim << "Part_Primitives" << "Part_Builder" << "Separator" << "Part_ShapeFromMesh" << "Part_MakeSolid" << "Part_ReverseShape" - << "Part_SimpleCopy" << "Part_RefineShape" << "Separator" + << "Part_SimpleCopy" << "Part_RefineShape" << "Part_CheckGeometry" << "Separator" << "Part_Boolean" << "Part_CrossSections" << "Part_Extrude" << "Part_Revolve" << "Part_Mirror" << "Part_Fillet" << "Part_Chamfer" << "Part_RuledSurface" << "Part_Loft" << "Part_Sweep"; @@ -94,7 +94,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const Gui::ToolBarItem* boolop = new Gui::ToolBarItem(root); boolop->setCommand("Boolean"); *boolop << "Part_Boolean" << "Part_Cut" << "Part_Fuse" << "Part_Common" - << "Part_Section" << "Part_CrossSections"; + << "Part_CheckGeometry" << "Part_Section" << "Part_CrossSections"; return root; } diff --git a/src/Mod/PartDesign/Gui/Command.cpp b/src/Mod/PartDesign/Gui/Command.cpp index 292b44a63..39280f05f 100644 --- a/src/Mod/PartDesign/Gui/Command.cpp +++ b/src/Mod/PartDesign/Gui/Command.cpp @@ -404,6 +404,7 @@ bool CmdPartDesignGroove::isActive(void) { return hasActiveDocument(); } + //=========================================================================== // PartDesign_Fillet //=========================================================================== @@ -418,7 +419,7 @@ CmdPartDesignFillet::CmdPartDesignFillet() sToolTipText = QT_TR_NOOP("Make a fillet on an edge, face or body"); sWhatsThis = sToolTipText; sStatusTip = sToolTipText; - sPixmap = "Part_Fillet"; + sPixmap = "PartDesign_Fillet"; } void CmdPartDesignFillet::activated(int iMsg) @@ -564,7 +565,7 @@ CmdPartDesignChamfer::CmdPartDesignChamfer() sToolTipText = QT_TR_NOOP("Chamfer the selected edges of a shape"); sWhatsThis = sToolTipText; sStatusTip = sToolTipText; - sPixmap = "Part_Chamfer"; + sPixmap = "PartDesign_Chamfer"; } void CmdPartDesignChamfer::activated(int iMsg) diff --git a/src/Mod/PartDesign/Gui/Resources/Makefile.am b/src/Mod/PartDesign/Gui/Resources/Makefile.am index c3463f2fa..4dd5a2099 100644 --- a/src/Mod/PartDesign/Gui/Resources/Makefile.am +++ b/src/Mod/PartDesign/Gui/Resources/Makefile.am @@ -37,6 +37,8 @@ EXTRA_DIST = \ translations/PartDesign_uk.ts \ translations/PartDesign_zh.qm \ translations/PartDesign_zh.ts \ + icons/PartDesign_Chamfer.svg \ + icons/PartDesign_Fillet.svg \ icons/PartDesign_Groove.svg \ icons/PartDesign_Pad.svg \ icons/PartDesign_Pocket.svg \ diff --git a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc index ae4603434..3f90a9d77 100644 --- a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc +++ b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc @@ -1,5 +1,7 @@ + icons/PartDesign_Chamfer.svg + icons/PartDesign_Fillet.svg icons/PartDesign_Groove.svg icons/PartDesign_Pad.svg icons/PartDesign_Pocket.svg diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Chamfer.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Chamfer.svg new file mode 100644 index 000000000..e0ac0430e --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Chamfer.svg @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Fillet.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Fillet.svg new file mode 100644 index 000000000..0af6cb8b5 --- /dev/null +++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Fillet.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + +