// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 Werner Mayer * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ViewProviderInspection.h" using namespace InspectionGui; bool ViewProviderInspection::addflag = false; App::PropertyFloatConstraint::Constraints ViewProviderInspection::floatRange = {1.0, 64.0, 1.0}; PROPERTY_SOURCE(InspectionGui::ViewProviderInspection, Gui::ViewProviderDocumentObject) ViewProviderInspection::ViewProviderInspection() { ADD_PROPERTY_TYPE( OutsideGrayed, (false), "", (App::PropertyType)(App::Prop_Output | App::Prop_Hidden), "" ); ADD_PROPERTY_TYPE( PointSize, (1.0), "Display", (App::PropertyType)(App::Prop_None /*App::Prop_Hidden*/), "" ); PointSize.setConstraints(&floatRange); pcColorRoot = new SoSeparator(); pcColorRoot->ref(); pcMatBinding = new SoMaterialBinding; pcMatBinding->ref(); pcColorMat = new SoMaterial; pcColorMat->ref(); pcColorStyle = new SoDrawStyle(); pcColorRoot->addChild(pcColorStyle); pcCoords = new SoCoordinate3; pcCoords->ref(); // simple color bar pcColorBar = new Gui::SoFCColorBar; pcColorBar->Attach(this); Gui::SoFCColorBarNotifier::instance().attach(pcColorBar); pcColorBar->ref(); pcColorBar->setRange(-0.1f, 0.1f, 3); pcLinkRoot = new SoGroup; pcLinkRoot->ref(); pcPointStyle = new SoDrawStyle(); pcPointStyle->ref(); pcPointStyle->style = SoDrawStyle::POINTS; pcPointStyle->pointSize = PointSize.getValue(); SelectionStyle.setValue(1); // BBOX } ViewProviderInspection::~ViewProviderInspection() { try { pcColorRoot->unref(); pcCoords->unref(); pcMatBinding->unref(); pcColorMat->unref(); pcLinkRoot->unref(); pcPointStyle->unref(); deleteColorBar(); } catch (Base::Exception& e) { Base::Console().destructorError( "ViewProviderInspection", "ViewProviderInspection::deleteColorBar() threw an exception: %s\n", e.what() ); } catch (...) { Base::Console().destructorError( "ViewProviderInspection", "ViewProviderInspection destructor threw an unknown exception" ); } } void ViewProviderInspection::onChanged(const App::Property* prop) { if (prop == &OutsideGrayed) { if (pcColorBar) { pcColorBar->setOutsideGrayed(OutsideGrayed.getValue()); pcColorBar->Notify(0); } } else if (prop == &PointSize) { pcPointStyle->pointSize = PointSize.getValue(); } else { inherited::onChanged(prop); } } void ViewProviderInspection::hide() { inherited::hide(); pcColorStyle->style = SoDrawStyle::INVISIBLE; } void ViewProviderInspection::show() { inherited::show(); pcColorStyle->style = SoDrawStyle::FILLED; } void ViewProviderInspection::deleteColorBar() { Gui::SoFCColorBarNotifier::instance().detach(pcColorBar); pcColorBar->Detach(this); pcColorBar->unref(); } void ViewProviderInspection::attach(App::DocumentObject* pcFeat) { // creates the standard viewing modes inherited::attach(pcFeat); SoShapeHints* flathints = new SoShapeHints; flathints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE; flathints->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE; SoGroup* pcColorShadedRoot = new SoGroup(); pcColorShadedRoot->addChild(flathints); // color shaded ------------------------------------------ SoDrawStyle* pcFlatStyle = new SoDrawStyle(); pcFlatStyle->style = SoDrawStyle::FILLED; pcColorShadedRoot->addChild(pcFlatStyle); pcColorShadedRoot->addChild(pcColorMat); pcColorShadedRoot->addChild(pcMatBinding); pcColorShadedRoot->addChild(pcLinkRoot); addDisplayMaskMode(pcColorShadedRoot, "ColorShaded"); // Check for an already existing color bar Gui::SoFCColorBar* pcBar = ((Gui::SoFCColorBar*)findFrontRootOfType(Gui::SoFCColorBar::getClassTypeId())); if (pcBar) { float fMin = pcColorBar->getMinValue(); float fMax = pcColorBar->getMaxValue(); // Attach to the foreign color bar and delete our own bar pcBar->Attach(this); pcBar->ref(); pcBar->setRange(fMin, fMax, 3); pcBar->Notify(0); deleteColorBar(); pcColorBar = pcBar; } pcColorRoot->addChild(pcColorBar); } bool ViewProviderInspection::setupFaces(const Data::ComplexGeoData* data) { std::vector points; std::vector faces; // set the Distance property to the correct size to sync size of material node with number // of vertices/points of the referenced geometry double accuracy = data->getAccuracy(); data->getFaces(points, faces, accuracy); if (faces.empty()) { return false; } setupCoords(points); setupFaceIndexes(faces); return true; } bool ViewProviderInspection::setupLines(const Data::ComplexGeoData* data) { std::vector points; std::vector lines; double accuracy = data->getAccuracy(); data->getLines(points, lines, accuracy); if (lines.empty()) { return false; } setupCoords(points); setupLineIndexes(lines); return true; } bool ViewProviderInspection::setupPoints( const Data::ComplexGeoData* data, App::PropertyContainer* container ) { std::vector points; std::vector normals; std::vector normals_d; double accuracy = data->getAccuracy(); data->getPoints(points, normals_d, accuracy); if (points.empty()) { return false; } normals.reserve(normals_d.size()); std::transform( normals_d.cbegin(), normals_d.cend(), std::back_inserter(normals), [](const Base::Vector3d& p) { return Base::toVector(p); } ); // If getPoints() doesn't deliver normals check a second property if (normals.empty() && container) { App::Property* propN = container->getPropertyByName("Normal"); if (propN && propN->isDerivedFrom()) { normals = static_cast(propN)->getValues(); } } setupCoords(points); if (!normals.empty() && normals.size() == points.size()) { setupNormals(normals); } this->pcLinkRoot->addChild(this->pcPointStyle); this->pcLinkRoot->addChild(new SoPointSet()); return true; } void ViewProviderInspection::setupCoords(const std::vector& points) { this->pcLinkRoot->addChild(this->pcCoords); this->pcCoords->point.setNum(points.size()); SbVec3f* pts = this->pcCoords->point.startEditing(); for (size_t i = 0; i < points.size(); i++) { const Base::Vector3d& p = points[i]; pts[i].setValue((float)p.x, (float)p.y, (float)p.z); } this->pcCoords->point.finishEditing(); } void ViewProviderInspection::setupNormals(const std::vector& normals) { SoNormal* normalNode = new SoNormal(); normalNode->vector.setNum(normals.size()); SbVec3f* norm = normalNode->vector.startEditing(); std::size_t i = 0; for (const auto& it : normals) { norm[i++].setValue(it.x, it.y, it.z); } normalNode->vector.finishEditing(); this->pcLinkRoot->addChild(normalNode); } void ViewProviderInspection::setupLineIndexes(const std::vector& lines) { SoIndexedLineSet* line = new SoIndexedLineSet(); this->pcLinkRoot->addChild(line); line->coordIndex.setNum(3 * lines.size()); int32_t* indices = line->coordIndex.startEditing(); unsigned long j = 0; for (const auto& it : lines) { indices[3 * j + 0] = it.I1; indices[3 * j + 1] = it.I2; indices[3 * j + 2] = SO_END_LINE_INDEX; j++; } line->coordIndex.finishEditing(); } void ViewProviderInspection::setupFaceIndexes(const std::vector& faces) { SoIndexedFaceSet* face = new SoIndexedFaceSet(); this->pcLinkRoot->addChild(face); face->coordIndex.setNum(4 * faces.size()); int32_t* indices = face->coordIndex.startEditing(); unsigned long j = 0; for (const auto& it : faces) { indices[4 * j + 0] = it.I1; indices[4 * j + 1] = it.I2; indices[4 * j + 2] = it.I3; indices[4 * j + 3] = SO_END_FACE_INDEX; j++; } face->coordIndex.finishEditing(); } void ViewProviderInspection::updateData(const App::Property* prop) { // set to the expected size if (prop->isDerivedFrom()) { App::GeoFeature* object = static_cast(prop)->getValue(); const App::PropertyComplexGeoData* propData = object ? object->getPropertyOfGeometry() : nullptr; if (propData) { Gui::coinRemoveAllChildren(this->pcLinkRoot); const Data::ComplexGeoData* data = propData->getComplexData(); if (!setupFaces(data)) { if (!setupLines(data)) { setupPoints(data, object); } } } } else if (prop->is()) { // force an update of the Inventor data nodes if (this->pcObject) { App::Property* link = this->pcObject->getPropertyByName("Actual"); if (link) { updateData(link); } setDistances(); } } else if (prop->is()) { if (strcmp(prop->getName(), "SearchRadius") == 0) { float fSearchRadius = ((App::PropertyFloat*)prop)->getValue(); this->search_radius = fSearchRadius; pcColorBar->setRange(-fSearchRadius, fSearchRadius, 4); pcColorBar->Notify(0); } } } SoSeparator* ViewProviderInspection::getFrontRoot() const { return pcColorRoot; } void ViewProviderInspection::setDistances() { if (!pcObject) { return; } App::Property* pDistances = pcObject->getPropertyByName("Distances"); if (!pDistances) { SoDebugError::post("ViewProviderInspection::setDistances", "Unknown property 'Distances'"); return; } if (!pDistances->is()) { SoDebugError::post( "ViewProviderInspection::setDistances", "Property 'Distances' has type %s (Inspection::PropertyDistanceList was expected)", pDistances->getTypeId().getName() ); return; } // distance values const std::vector& fValues = static_cast(pDistances)->getValues(); if ((int)fValues.size() != this->pcCoords->point.getNum()) { pcMatBinding->value = SoMaterialBinding::OVERALL; return; } if (pcColorMat->diffuseColor.getNum() != static_cast(fValues.size())) { pcColorMat->diffuseColor.setNum(static_cast(fValues.size())); } if (pcColorMat->transparency.getNum() != static_cast(fValues.size())) { pcColorMat->transparency.setNum(static_cast(fValues.size())); } SbColor* cols = pcColorMat->diffuseColor.startEditing(); float* tran = pcColorMat->transparency.startEditing(); unsigned long j = 0; for (std::vector::const_iterator jt = fValues.begin(); jt != fValues.end(); ++jt, j++) { Base::Color col = pcColorBar->getColor(*jt); cols[j] = SbColor(col.r, col.g, col.b); if (pcColorBar->isVisible(*jt)) { tran[j] = 0.0f; } else { tran[j] = 0.8f; } } pcColorMat->diffuseColor.finishEditing(); pcColorMat->transparency.finishEditing(); pcMatBinding->value = SoMaterialBinding::PER_VERTEX_INDEXED; } QIcon ViewProviderInspection::getIcon() const { // Get the icon of the view provider to the associated feature QIcon px = inherited::getIcon(); App::Property* pActual = pcObject->getPropertyByName("Actual"); if (pActual && pActual->isDerivedFrom()) { App::DocumentObject* docobj = ((App::PropertyLink*)pActual)->getValue(); if (docobj) { Gui::Document* doc = Gui::Application::Instance->getDocument(docobj->getDocument()); Gui::ViewProvider* view = doc->getViewProvider(docobj); px = view->getIcon(); } } return px; } void ViewProviderInspection::setDisplayMode(const char* ModeName) { if (strcmp("Visual Inspection", ModeName) == 0) { setDistances(); setDisplayMaskMode("ColorShaded"); } inherited::setDisplayMode(ModeName); } std::vector ViewProviderInspection::getDisplayModes() const { // add modes std::vector StrList; StrList.emplace_back("Visual Inspection"); return StrList; } void ViewProviderInspection::OnChange(Base::Subject& /*rCaller*/, int /*rcReason*/) { setActiveMode(); } namespace InspectionGui { // Proxy class that receives an asynchronous custom event class ViewProviderProxyObject: public QObject { public: explicit ViewProviderProxyObject(QWidget* w) : QObject(nullptr) , widget(w) {} ~ViewProviderProxyObject() override = default; void customEvent(QEvent*) override { if (!widget.isNull()) { QList flags = widget->findChildren(); if (!flags.isEmpty()) { int ret = QMessageBox::question( Gui::getMainWindow(), QObject::tr("Remove annotations"), QObject::tr("Do you want to remove all annotations?"), QMessageBox::Yes, QMessageBox::No ); if (ret == QMessageBox::Yes) { for (auto it : flags) { it->deleteLater(); } } } } this->deleteLater(); } static void addFlag(Gui::View3DInventorViewer* view, const QString& text, const SoPickedPoint* point) { Gui::Flag* flag = new Gui::Flag; QPalette p; p.setColor(QPalette::Window, QColor(85, 0, 127)); p.setColor(QPalette::Text, QColor(220, 220, 220)); flag->setPalette(p); flag->setText(text); flag->setOrigin(point->getPoint()); Gui::GLFlagWindow* flags = nullptr; std::list glItems = view->getGraphicsItemsOfType( Gui::GLFlagWindow::getClassTypeId() ); if (glItems.empty()) { flags = new Gui::GLFlagWindow(view); view->addGraphicsItem(flags); } else { flags = static_cast(glItems.front()); } flags->addFlag(flag, Gui::FlagLayout::BottomLeft); } private: QPointer widget; }; } // namespace InspectionGui void ViewProviderInspection::inspectCallback(void* ud, SoEventCallback* n) { Gui::View3DInventorViewer* view = static_cast(n->getUserData()); const SoEvent* ev = n->getEvent(); if (ev->getTypeId() == SoMouseButtonEvent::getClassTypeId()) { const SoMouseButtonEvent* mbe = static_cast(ev); // Mark all incoming mouse button events as handled, especially, to deactivate the selection // node n->getAction()->setHandled(); n->setHandled(); if (mbe->getButton() == SoMouseButtonEvent::BUTTON2 && mbe->getState() == SoButtonEvent::UP) { n->setHandled(); // context-menu QMenu menu; QAction* fl = menu.addAction(QObject::tr("Annotation")); fl->setCheckable(true); fl->setChecked(addflag); QAction* cl = menu.addAction(QObject::tr("Leave Info Mode")); QAction* id = menu.exec(QCursor::pos()); if (fl == id) { addflag = fl->isChecked(); } else if (cl == id) { // post an event to a proxy object to make sure to avoid problems // when opening a modal dialog QApplication::postEvent( new ViewProviderProxyObject(view->getGLWidget()), new QEvent(QEvent::User) ); view->setEditing(false); view->getWidget()->setCursor(QCursor(Qt::ArrowCursor)); view->setRedirectToSceneGraph(false); view->setRedirectToSceneGraphEnabled(false); view->setSelectionEnabled(true); view->removeEventCallback(SoButtonEvent::getClassTypeId(), inspectCallback, ud); } } else if (mbe->getButton() == SoMouseButtonEvent::BUTTON1 && mbe->getState() == SoButtonEvent::UP) { const SoPickedPoint* point = n->getPickedPoint(); if (!point) { Base::Console().message("No point picked.\n"); return; } n->setHandled(); // check if we have picked one a node of the view provider we are insterested in Gui::ViewProvider* vp = view->getViewProviderByPathFromTail(point->getPath()); if (vp && vp->isDerivedFrom()) { ViewProviderInspection* that = static_cast(vp); QString info = that->inspectDistance(point); Gui::getMainWindow()->setPaneText(1, info); if (addflag) { ViewProviderProxyObject::addFlag(view, info, point); } else { Gui::ToolTip::showText(QCursor::pos(), info); } } else { // the nearest picked point was not part of the view provider SoRayPickAction action(view->getSoRenderManager()->getViewportRegion()); action.setPickAll(true); action.setPoint(mbe->getPosition()); action.apply(view->getSoRenderManager()->getSceneGraph()); const SoPickedPointList& pps = action.getPickedPointList(); for (int i = 0; i < pps.getLength(); ++i) { const SoPickedPoint* point = pps[i]; vp = view->getViewProviderByPathFromTail(point->getPath()); if (vp && vp->isDerivedFrom()) { ViewProviderInspection* self = static_cast(vp); QString info = self->inspectDistance(point); Gui::getMainWindow()->setPaneText(1, info); if (addflag) { ViewProviderProxyObject::addFlag(view, info, point); } else { Gui::ToolTip::showText(QCursor::pos(), info); } break; } } } } } // toggle between inspection and navigation mode else if (ev->getTypeId().isDerivedFrom(SoKeyboardEvent::getClassTypeId())) { const SoKeyboardEvent* const ke = static_cast(ev); if (ke->getState() == SoButtonEvent::DOWN && ke->getKey() == SoKeyboardEvent::ESCAPE) { SbBool toggle = view->isRedirectedToSceneGraph(); view->setRedirectToSceneGraph(!toggle); n->setHandled(); } } } namespace InspectionGui { float calcArea(const SbVec3f& v1, const SbVec3f& v2, const SbVec3f& v3) { SbVec3f a = v2 - v1; SbVec3f b = v3 - v1; return a.cross(b).length() / 2.0f; } bool calcWeights( const SbVec3f& v1, const SbVec3f& v2, const SbVec3f& v3, const SbVec3f& p, float& w0, float& w1, float& w2 ) { float fAreaABC = calcArea(v1, v2, v3); float fAreaPBC = calcArea(p, v2, v3); float fAreaPCA = calcArea(p, v3, v1); float fAreaPAB = calcArea(p, v1, v2); w0 = fAreaPBC / fAreaABC; w1 = fAreaPCA / fAreaABC; w2 = fAreaPAB / fAreaABC; return fabs(w0 + w1 + w2 - 1.0f) < 0.001f; } } // namespace InspectionGui QString ViewProviderInspection::inspectDistance(const SoPickedPoint* pp) const { QString info; const SoDetail* detail = pp->getDetail(pp->getPath()->getTail()); if (detail && detail->getTypeId() == SoFaceDetail::getClassTypeId()) { // get the distances of the three points of the picked facet const SoFaceDetail* facedetail = static_cast(detail); App::Property* pDistance = this->pcObject->getPropertyByName("Distances"); if (pDistance && pDistance->is()) { Inspection::PropertyDistanceList* dist = static_cast( pDistance ); int index1 = facedetail->getPoint(0)->getCoordinateIndex(); int index2 = facedetail->getPoint(1)->getCoordinateIndex(); int index3 = facedetail->getPoint(2)->getCoordinateIndex(); float fVal1 = (*dist)[index1]; float fVal2 = (*dist)[index2]; float fVal3 = (*dist)[index3]; App::Property* pActual = this->pcObject->getPropertyByName("Actual"); if (pActual && pActual->isDerivedFrom()) { float fSearchRadius = this->search_radius; if (fVal1 > fSearchRadius || fVal2 > fSearchRadius || fVal3 > fSearchRadius) { info = QObject::tr("Distance: > %1").arg(fSearchRadius); } else if (fVal1 < -fSearchRadius || fVal2 < -fSearchRadius || fVal3 < -fSearchRadius) { info = QObject::tr("Distance: < %1").arg(-fSearchRadius); } else { SoSearchAction searchAction; searchAction.setType(SoCoordinate3::getClassTypeId()); searchAction.setInterest(SoSearchAction::FIRST); searchAction.apply(pp->getPath()->getNodeFromTail(1)); SoPath* selectionPath = searchAction.getPath(); if (selectionPath) { SoCoordinate3* coords = static_cast(selectionPath->getTail()); const SbVec3f& v1 = coords->point[index1]; const SbVec3f& v2 = coords->point[index2]; const SbVec3f& v3 = coords->point[index3]; const SbVec3f& p = pp->getObjectPoint(); // get the weights float w1, w2, w3; calcWeights(v1, v2, v3, p, w1, w2, w3); float fVal = w1 * fVal1 + w2 * fVal2 + w3 * fVal3; info = QObject::tr("Distance: %1").arg(fVal); } } } } } else if (detail && detail->getTypeId() == SoPointDetail::getClassTypeId()) { // safe downward cast, know the type const SoPointDetail* pointdetail = static_cast(detail); // get the distance of the picked point int index = pointdetail->getCoordinateIndex(); App::Property* prop = this->pcObject->getPropertyByName("Distances"); if (prop && prop->is()) { Inspection::PropertyDistanceList* dist = static_cast( prop ); float fVal = (*dist)[index]; info = QObject::tr("Distance: %1").arg(fVal); } } return info; } // ----------------------------------------------- PROPERTY_SOURCE(InspectionGui::ViewProviderInspectionGroup, Gui::ViewProviderDocumentObjectGroup) /** * Creates the view provider for an object group. */ ViewProviderInspectionGroup::ViewProviderInspectionGroup() = default; ViewProviderInspectionGroup::~ViewProviderInspectionGroup() = default; /** * Returns the pixmap for the opened list item. */ QIcon ViewProviderInspectionGroup::getIcon() const { // clang-format off static const char * const ScanViewOpen[]={ "16 16 10 1", "c c #000000", ". c None", "h c #808000", "# c #808080", "a c #ffff00", "r c #ff0000", "o c #ffff00", "g c #00ff00", "t c #00ffff", "b c #0000ff", "................", "...#####........", "..#hhhhh#.......", ".#hhhhhhh######.", ".#hhhhhhhhhhhh#c", ".#hhhhhhhhhhhh#c", ".#hhhhhhhhhhhh#c", "#############h#c", "#aaaaaaaaaa##h#c", "#aarroggtbbac##c", ".#arroggtbbaac#c", ".#aarroggtbbac#c", "..#aaaaaaaaaa#cc", "..#############c", "...ccccccccccccc", "................"}; static const char * const ScanViewClosed[] = { "16 16 9 1", "c c #000000", ". c None", "# c #808080", "a c #ffff00", "r c #ff0000", "o c #ffff00", "g c #00ff00", "t c #00ffff", "b c #0000ff", "................", "...#####........", "..#aaaaa#.......", ".#aaaaaaa######.", ".#aaaaaaaaaaaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aarroggtbbaa#c", ".#aaaaaaaaaaaa#c", ".##############c", "..cccccccccccccc", "................"}; // clang-format on QIcon groupIcon; groupIcon.addPixmap(QPixmap(ScanViewClosed), QIcon::Normal, QIcon::Off); groupIcon.addPixmap(QPixmap(ScanViewOpen), QIcon::Normal, QIcon::On); return groupIcon; }