Files
create/src/Mod/Part/Gui/TaskFaceAppearances.cpp
Chris Hennes 1a2070c36f Merge pull request #19636 from kadet1090/color-in-base
Base: Move App::Color to Base
2025-03-02 16:36:40 -06:00

513 lines
18 KiB
C++

/***************************************************************************
* Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <sstream>
# include <QFontMetrics>
# include <QPointer>
# include <QSet>
# include <BRep_Tool.hxx>
# include <BRepGProp.hxx>
# include <gp_Pnt.hxx>
# include <GProp_GProps.hxx>
# include <TopExp_Explorer.hxx>
# include <TopoDS.hxx>
# include <TopTools_IndexedMapOfShape.hxx>
# include <Inventor/SoPickedPoint.h>
# include <Inventor/actions/SoRayPickAction.h>
# include <Inventor/actions/SoSearchAction.h>
# include <Inventor/details/SoFaceDetail.h>
# include <Inventor/events/SoMouseButtonEvent.h>
# include <Inventor/nodes/SoCamera.h>
# include <Inventor/nodes/SoSeparator.h>
#endif
#include <App/Document.h>
#include <Base/Tools.h>
#include <Gui/Application.h>
#include <Gui/Control.h>
#include <Gui/Dialogs/DlgMaterialPropertiesImp.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/Selection/Selection.h>
#include <Gui/Tools.h>
#include <Gui/Utilities.h>
#include <Gui/View3DInventor.h>
#include <Gui/View3DInventorViewer.h>
#include <Mod/Material/Gui/MaterialTreeWidget.h>
#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 (Base::Tools::isNullOrEmpty(sSubName))
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<Gui::View3DInventorViewer> view;
ViewProviderPartExt* vp;
App::DocumentObject* obj;
Gui::Document* doc;
std::vector<App::Material> perface;
QSet<int> 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<Part::Feature*>(obj)->Shape.getValue(), TopAbs_FACE);
while (xp.More()) {
mapOfShape.Add(xp.Current());
xp.Next();
}
std::vector<App::Material> 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<const SoFaceDetail*>(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<Gui::View3DInventorViewer*>(cb->getUserData());
view->removeEventCallback(SoMouseButtonEvent::getClassTypeId(), selectionCallback, ud);
view->setSelectionEnabled(true);
std::vector<SbVec2f> 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<FaceAppearances*>(ud);
self->d->view = nullptr;
if (self->d->obj && self->d->obj->isDerivedFrom<Part::Feature>()) {
cb->setHandled();
const TopoDS_Shape& shape = static_cast<Part::Feature*>(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()
{
// clang-format off
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);
// clang-format on
}
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::View3DInventor*>(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<Materials::Material>& 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 Base::Color& faceColor = d->perface[index].diffuseColor;
QColor color;
// alpha of Base::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 = QStringLiteral("[");
int size = d->index.size();
for (int it : d->index) {
faces += QString::number(it + 1);
if (--size > 0)
faces += QStringLiteral(",");
}
faces += QStringLiteral("]");
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());
}
int FaceAppearances::getFirstIndex() const
{
if (!d->index.isEmpty()) {
return *(d->index.begin());
}
return 0;
}
/**
* Opens a dialog that allows one to modify the 'ShapeMaterial' property of all selected view providers.
*/
void FaceAppearances::onButtonCustomAppearanceClicked()
{
Gui::Dialog::DlgMaterialPropertiesImp dlg(this);
App::Material mat = d->perface[getFirstIndex()];
dlg.setCustomMaterial(mat);
dlg.setDefaultMaterial(mat);
dlg.exec();
// Set the face appearance
if (!d->index.isEmpty()) {
for (int it : d->index) {
d->perface[it] = dlg.getCustomMaterial();
}
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"