/*************************************************************************** * 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 "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "TaskFaceAppearances.h" #include "ui_TaskFaceAppearances.h" #include "SoBrepFaceSet.h" #include "ViewProviderExt.h" using namespace PartGui; namespace sp = std::placeholders; namespace PartGui { class FaceSelection : public Gui::SelectionFilterGate { const App::DocumentObject* object; public: explicit FaceSelection(const App::DocumentObject* obj) : Gui::SelectionFilterGate(), object(obj) { } bool allow(App::Document* /*pDoc*/, App::DocumentObject* pObj, const char* sSubName) override { if (pObj != this->object) return false; if (!sSubName || sSubName[0] == '\0') return false; std::string element(sSubName); return element.substr(0, 4) == "Face"; } }; } class FaceAppearances::Private { public: using Connection = boost::signals2::connection; Ui_TaskFaceAppearances* ui; QPointer view; ViewProviderPartExt* vp; App::DocumentObject* obj; Gui::Document* doc; std::vector perface; QSet index; bool boxSelection; Connection connectDelDoc; Connection connectDelObj; Connection connectUndoDoc; explicit Private(ViewProviderPartExt* vp) : ui(new Ui_TaskFaceAppearances()), view(nullptr), vp(vp) { obj = vp->getObject(); doc = Gui::Application::Instance->getDocument(obj->getDocument()); // build up map edge->face TopTools_IndexedMapOfShape mapOfShape; TopExp_Explorer xp(static_cast(obj)->Shape.getValue(), TopAbs_FACE); while (xp.More()) { mapOfShape.Add(xp.Current()); xp.Next(); } std::vector current = vp->ShapeAppearance.getValues(); perface = current; perface.resize(mapOfShape.Extent(), perface.front()); boxSelection = false; } ~Private() { delete ui; } bool isVisibleFace(int faceIndex, const SbVec2f& pos, Gui::View3DInventorViewer* viewer) { SoSeparator* root = new SoSeparator; root->ref(); root->addChild(viewer->getSoRenderManager()->getCamera()); root->addChild(vp->getRoot()); SoSearchAction searchAction; searchAction.setType(PartGui::SoBrepFaceSet::getClassTypeId()); searchAction.setInterest(SoSearchAction::FIRST); searchAction.apply(root); SoPath* selectionPath = searchAction.getPath(); SoRayPickAction rp(viewer->getSoRenderManager()->getViewportRegion()); rp.setNormalizedPoint(pos); rp.apply(selectionPath); root->unref(); SoPickedPoint* pick = rp.getPickedPoint(); if (pick) { const SoDetail* detail = pick->getDetail(); if (detail && detail->isOfType(SoFaceDetail::getClassTypeId())) { int index = static_cast(detail)->getPartIndex(); if (faceIndex != index) return false; SbVec3f dir = viewer->getViewDirection(); const SbVec3f& nor = pick->getNormal(); if (dir.dot(nor) > 0) return false; // bottom side points to user return true; } } return false; } void addFacesToSelection(Gui::View3DInventorViewer* /*viewer*/, const Gui::ViewVolumeProjection& proj, const Base::Polygon2d& polygon, const TopoDS_Shape& shape) { try { TopTools_IndexedMapOfShape M; TopExp_Explorer xp_face(shape, TopAbs_FACE); while (xp_face.More()) { M.Add(xp_face.Current()); xp_face.Next(); } App::Document* appdoc = doc->getDocument(); for (Standard_Integer k = 1; k <= M.Extent(); k++) { const TopoDS_Shape& face = M(k); TopExp_Explorer xp_vertex(face, TopAbs_VERTEX); while (xp_vertex.More()) { gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(xp_vertex.Current())); Base::Vector3d pt2d; pt2d = proj(Base::Vector3d(p.X(), p.Y(), p.Z())); if (polygon.Contains(Base::Vector2d(pt2d.x, pt2d.y))) { std::stringstream str; str << "Face" << k; Gui::Selection().addSelection(appdoc->getName(), obj->getNameInDocument(), str.str().c_str()); break; } xp_vertex.Next(); } } } catch (...) { } } static void selectionCallback(void* ud, SoEventCallback* cb) { Gui::View3DInventorViewer* view = static_cast(cb->getUserData()); view->removeEventCallback(SoMouseButtonEvent::getClassTypeId(), selectionCallback, ud); view->setSelectionEnabled(true); std::vector picked = view->getGLPolygon(); SoCamera* cam = view->getSoRenderManager()->getCamera(); SbViewVolume vv = cam->getViewVolume(); Gui::ViewVolumeProjection proj(vv); Base::Polygon2d polygon; if (picked.size() == 2) { SbVec2f pt1 = picked[0]; SbVec2f pt2 = picked[1]; polygon.Add(Base::Vector2d(pt1[0], pt1[1])); polygon.Add(Base::Vector2d(pt1[0], pt2[1])); polygon.Add(Base::Vector2d(pt2[0], pt2[1])); polygon.Add(Base::Vector2d(pt2[0], pt1[1])); } else { for (const auto& it : picked) polygon.Add(Base::Vector2d(it[0], it[1])); } FaceAppearances* self = static_cast(ud); self->d->view = nullptr; if (self->d->obj && self->d->obj->isDerivedFrom()) { cb->setHandled(); const TopoDS_Shape& shape = static_cast(self->d->obj)->Shape.getValue(); self->d->boxSelection = true; self->d->addFacesToSelection(view, proj, polygon, shape); self->d->boxSelection = false; self->d->ui->boxSelection->setChecked(false); self->updatePanel(); view->redraw(); } } }; /* TRANSLATOR PartGui::TaskFaceAppearances */ FaceAppearances::FaceAppearances(ViewProviderPartExt* vp, QWidget* parent) : d(new Private(vp)) { Q_UNUSED(parent); d->ui->setupUi(this); setupConnections(); d->ui->groupBox->setTitle(QString::fromUtf8(vp->getObject()->Label.getValue())); d->ui->buttonCustomAppearance->setDisabled(true); FaceSelection* gate = new FaceSelection(d->vp->getObject()); Gui::Selection().addSelectionGate(gate); //NOLINTBEGIN d->connectDelDoc = Gui::Application::Instance->signalDeleteDocument.connect(std::bind (&FaceAppearances::slotDeleteDocument, this, sp::_1)); d->connectDelObj = Gui::Application::Instance->signalDeletedObject.connect(std::bind (&FaceAppearances::slotDeleteObject, this, sp::_1)); d->connectUndoDoc = d->doc->signalUndoDocument.connect(std::bind (&FaceAppearances::slotUndoDocument, this, sp::_1)); //NOLINTEND } FaceAppearances::~FaceAppearances() { if (d->view) { d->view->stopSelection(); d->view->removeEventCallback(SoMouseButtonEvent::getClassTypeId(), Private::selectionCallback, this); d->view->setSelectionEnabled(true); } Gui::Selection().rmvSelectionGate(); d->connectDelDoc.disconnect(); d->connectDelObj.disconnect(); d->connectUndoDoc.disconnect(); delete d; } void FaceAppearances::setupConnections() { connect(d->ui->defaultButton, &QPushButton::clicked, this, &FaceAppearances::onDefaultButtonClicked); connect(d->ui->boxSelection, &QPushButton::toggled, this, &FaceAppearances::onBoxSelectionToggled); connect(d->ui->widgetMaterial, &MatGui::MaterialTreeWidget::materialSelected, this, &FaceAppearances::onMaterialSelected); connect(d->ui->buttonCustomAppearance, &QPushButton::clicked, this, &FaceAppearances::onButtonCustomAppearanceClicked); } void FaceAppearances::slotUndoDocument(const Gui::Document& Doc) { if (d->doc == &Doc) { d->doc->resetEdit(); Gui::Control().closeDialog(); } } void FaceAppearances::slotDeleteDocument(const Gui::Document& Doc) { if (d->doc == &Doc) Gui::Control().closeDialog(); } void FaceAppearances::slotDeleteObject(const Gui::ViewProvider& obj) { if (d->vp == &obj) Gui::Control().closeDialog(); } void FaceAppearances::onBoxSelectionToggled(bool checked) { Gui::View3DInventor* view = qobject_cast(Gui::getMainWindow()->activeWindow()); // toggle the button state and feature d->boxSelection = checked; if (!checked) { // end box selection mode if (view) view->getViewer()->stopSelection(); } if (view && checked) { Gui::View3DInventorViewer* viewer = view->getViewer(); if (!viewer->isSelecting()) { viewer->startSelection(Gui::View3DInventorViewer::Rubberband); viewer->addEventCallback(SoMouseButtonEvent::getClassTypeId(), Private::selectionCallback, this); // avoid that the selection node handles the event otherwise the callback function won't be // called immediately viewer->setSelectionEnabled(false); d->view = viewer; } } } void FaceAppearances::onDefaultButtonClicked() { std::fill(d->perface.begin(), d->perface.end(), d->vp->ShapeAppearance[0]); d->vp->ShapeAppearance.setValues(d->perface); } void FaceAppearances::onMaterialSelected(const std::shared_ptr& material) { if (!d->index.isEmpty()) { for (int it : d->index) { d->perface[it] = material->getMaterialAppearance(); } d->vp->ShapeAppearance.setValues(d->perface); // new color has been applied, unselect so that users can see this onSelectionChanged(Gui::SelectionChanges::ClrSelection); Gui::Selection().clearSelection(); } } void FaceAppearances::onSelectionChanged(const Gui::SelectionChanges& msg) { // no object selected in the combobox or no sub-element was selected if (!msg.pSubName) return; bool selection_changed = false; if (msg.Type == Gui::SelectionChanges::AddSelection) { // when adding a sub-element to the selection check // whether this is the currently handled object App::Document* doc = d->obj->getDocument(); std::string docname = doc->getName(); std::string objname = d->obj->getNameInDocument(); if (docname == msg.pDocName && objname == msg.pObjectName) { int index = std::atoi(msg.pSubName + 4) - 1; d->index.insert(index); const App::Color& faceColor = d->perface[index].diffuseColor; QColor color; // alpha of App::Color is contrary to the one of QColor color.setRgbF(faceColor.r, faceColor.g, faceColor.b, (1.0 - faceColor.a)); selection_changed = true; } } else if (msg.Type == Gui::SelectionChanges::RmvSelection) { App::Document* doc = d->obj->getDocument(); std::string docname = doc->getName(); std::string objname = d->obj->getNameInDocument(); if (docname == msg.pDocName && objname == msg.pObjectName) { int index = std::atoi(msg.pSubName + 4) - 1; d->index.remove(index); selection_changed = true; } } else if (msg.Type == Gui::SelectionChanges::ClrSelection) { d->index.clear(); selection_changed = true; } if (selection_changed && !d->boxSelection) { updatePanel(); } } void FaceAppearances::updatePanel() { QString faces = QString::fromLatin1("["); int size = d->index.size(); for (int it : d->index) { faces += QString::number(it + 1); if (--size > 0) faces += QString::fromLatin1(","); } faces += QString::fromLatin1("]"); int maxWidth = d->ui->labelElement->width(); QFontMetrics fm(d->ui->labelElement->font()); if (Gui::QtTools::horizontalAdvance(fm, faces) > maxWidth) { faces = fm.elidedText(faces, Qt::ElideMiddle, maxWidth); } d->ui->labelElement->setText(faces); d->ui->buttonCustomAppearance->setDisabled(d->index.isEmpty()); } /** * Opens a dialog that allows to modify the 'ShapeMaterial' property of all selected view providers. */ void FaceAppearances::onButtonCustomAppearanceClicked() { std::vector Provider; Provider.push_back(d->vp); Gui::Dialog::DlgFaceMaterialPropertiesImp dlg("ShapeAppearance", this); dlg.setViewProviders(Provider); dlg.exec(); // Set the face appearance if (!d->index.isEmpty()) { for (int it : d->index) { d->perface[it] = dlg.getCustomAppearance(); } d->vp->ShapeAppearance.setValues(d->perface); // new color has been applied, unselect so that users can see this onSelectionChanged(Gui::SelectionChanges::ClrSelection); Gui::Selection().clearSelection(); } } void FaceAppearances::open() { Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument()); doc->openCommand(QT_TRANSLATE_NOOP("Command", "Change face colors")); } bool FaceAppearances::accept() { Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument()); doc->commitCommand(); doc->resetEdit(); return true; } bool FaceAppearances::reject() { Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument()); doc->abortCommand(); doc->resetEdit(); return true; } void FaceAppearances::changeEvent(QEvent* e) { QWidget::changeEvent(e); if (e->type() == QEvent::LanguageChange) { d->ui->retranslateUi(this); } } /* TRANSLATOR PartGui::TaskFaceAppearances */ TaskFaceAppearances::TaskFaceAppearances(ViewProviderPartExt* vp) { widget = new FaceAppearances(vp); addTaskBox(widget); } TaskFaceAppearances::~TaskFaceAppearances() = default; void TaskFaceAppearances::open() { widget->open(); } void TaskFaceAppearances::clicked(int) { } bool TaskFaceAppearances::accept() { return widget->accept(); } bool TaskFaceAppearances::reject() { return widget->reject(); } #include "moc_TaskFaceAppearances.cpp"