1
/***************************************************************************
2
* Copyright (c) 2011 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
27
# include <QFontMetrics>
30
# include <BRep_Tool.hxx>
31
# include <BRepGProp.hxx>
33
# include <GProp_GProps.hxx>
34
# include <TopExp_Explorer.hxx>
36
# include <TopTools_IndexedMapOfShape.hxx>
37
# include <Inventor/SoPickedPoint.h>
38
# include <Inventor/actions/SoRayPickAction.h>
39
# include <Inventor/actions/SoSearchAction.h>
40
# include <Inventor/details/SoFaceDetail.h>
41
# include <Inventor/events/SoMouseButtonEvent.h>
42
# include <Inventor/nodes/SoCamera.h>
43
# include <Inventor/nodes/SoSeparator.h>
46
#include <App/Document.h>
47
#include <Gui/Application.h>
48
#include <Gui/Control.h>
49
#include <Gui/DlgMaterialPropertiesImp.h>
50
#include <Gui/Document.h>
51
#include <Gui/MainWindow.h>
52
#include <Gui/Selection.h>
54
#include <Gui/Utilities.h>
55
#include <Gui/View3DInventor.h>
56
#include <Gui/View3DInventorViewer.h>
58
#include <Mod/Material/Gui/MaterialTreeWidget.h>
60
#include "TaskFaceAppearances.h"
61
#include "ui_TaskFaceAppearances.h"
62
#include "SoBrepFaceSet.h"
63
#include "ViewProviderExt.h"
66
using namespace PartGui;
67
namespace sp = std::placeholders;
70
class FaceSelection : public Gui::SelectionFilterGate
72
const App::DocumentObject* object;
74
explicit FaceSelection(const App::DocumentObject* obj)
75
: Gui::SelectionFilterGate(), object(obj)
78
bool allow(App::Document* /*pDoc*/, App::DocumentObject* pObj, const char* sSubName) override
80
if (pObj != this->object)
82
if (!sSubName || sSubName[0] == '\0')
84
std::string element(sSubName);
85
return element.substr(0, 4) == "Face";
90
class FaceAppearances::Private
93
using Connection = boost::signals2::connection;
94
Ui_TaskFaceAppearances* ui;
95
QPointer<Gui::View3DInventorViewer> view;
96
ViewProviderPartExt* vp;
97
App::DocumentObject* obj;
99
std::vector<App::Material> perface;
102
Connection connectDelDoc;
103
Connection connectDelObj;
104
Connection connectUndoDoc;
106
explicit Private(ViewProviderPartExt* vp) : ui(new Ui_TaskFaceAppearances()), view(nullptr), vp(vp)
108
obj = vp->getObject();
109
doc = Gui::Application::Instance->getDocument(obj->getDocument());
111
// build up map edge->face
112
TopTools_IndexedMapOfShape mapOfShape;
113
TopExp_Explorer xp(static_cast<Part::Feature*>(obj)->Shape.getValue(), TopAbs_FACE);
115
mapOfShape.Add(xp.Current());
119
std::vector<App::Material> current = vp->ShapeAppearance.getValues();
121
perface.resize(mapOfShape.Extent(), perface.front());
123
boxSelection = false;
129
bool isVisibleFace(int faceIndex, const SbVec2f& pos, Gui::View3DInventorViewer* viewer)
131
SoSeparator* root = new SoSeparator;
133
root->addChild(viewer->getSoRenderManager()->getCamera());
134
root->addChild(vp->getRoot());
136
SoSearchAction searchAction;
137
searchAction.setType(PartGui::SoBrepFaceSet::getClassTypeId());
138
searchAction.setInterest(SoSearchAction::FIRST);
139
searchAction.apply(root);
140
SoPath* selectionPath = searchAction.getPath();
142
SoRayPickAction rp(viewer->getSoRenderManager()->getViewportRegion());
143
rp.setNormalizedPoint(pos);
144
rp.apply(selectionPath);
147
SoPickedPoint* pick = rp.getPickedPoint();
149
const SoDetail* detail = pick->getDetail();
150
if (detail && detail->isOfType(SoFaceDetail::getClassTypeId())) {
151
int index = static_cast<const SoFaceDetail*>(detail)->getPartIndex();
152
if (faceIndex != index)
154
SbVec3f dir = viewer->getViewDirection();
155
const SbVec3f& nor = pick->getNormal();
156
if (dir.dot(nor) > 0)
157
return false; // bottom side points to user
164
void addFacesToSelection(Gui::View3DInventorViewer* /*viewer*/,
165
const Gui::ViewVolumeProjection& proj,
166
const Base::Polygon2d& polygon,
167
const TopoDS_Shape& shape)
170
TopTools_IndexedMapOfShape M;
172
TopExp_Explorer xp_face(shape, TopAbs_FACE);
173
while (xp_face.More()) {
174
M.Add(xp_face.Current());
178
App::Document* appdoc = doc->getDocument();
179
for (Standard_Integer k = 1; k <= M.Extent(); k++) {
180
const TopoDS_Shape& face = M(k);
182
TopExp_Explorer xp_vertex(face, TopAbs_VERTEX);
183
while (xp_vertex.More()) {
184
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(xp_vertex.Current()));
186
pt2d = proj(Base::Vector3d(p.X(), p.Y(), p.Z()));
187
if (polygon.Contains(Base::Vector2d(pt2d.x, pt2d.y))) {
188
std::stringstream str;
190
Gui::Selection().addSelection(appdoc->getName(), obj->getNameInDocument(), str.str().c_str());
200
static void selectionCallback(void* ud, SoEventCallback* cb)
202
Gui::View3DInventorViewer* view = static_cast<Gui::View3DInventorViewer*>(cb->getUserData());
203
view->removeEventCallback(SoMouseButtonEvent::getClassTypeId(), selectionCallback, ud);
204
view->setSelectionEnabled(true);
206
std::vector<SbVec2f> picked = view->getGLPolygon();
207
SoCamera* cam = view->getSoRenderManager()->getCamera();
208
SbViewVolume vv = cam->getViewVolume();
209
Gui::ViewVolumeProjection proj(vv);
210
Base::Polygon2d polygon;
211
if (picked.size() == 2) {
212
SbVec2f pt1 = picked[0];
213
SbVec2f pt2 = picked[1];
214
polygon.Add(Base::Vector2d(pt1[0], pt1[1]));
215
polygon.Add(Base::Vector2d(pt1[0], pt2[1]));
216
polygon.Add(Base::Vector2d(pt2[0], pt2[1]));
217
polygon.Add(Base::Vector2d(pt2[0], pt1[1]));
220
for (const auto& it : picked)
221
polygon.Add(Base::Vector2d(it[0], it[1]));
224
FaceAppearances* self = static_cast<FaceAppearances*>(ud);
225
self->d->view = nullptr;
226
if (self->d->obj && self->d->obj->isDerivedFrom<Part::Feature>()) {
228
const TopoDS_Shape& shape = static_cast<Part::Feature*>(self->d->obj)->Shape.getValue();
229
self->d->boxSelection = true;
230
self->d->addFacesToSelection(view, proj, polygon, shape);
231
self->d->boxSelection = false;
232
self->d->ui->boxSelection->setChecked(false);
239
/* TRANSLATOR PartGui::TaskFaceAppearances */
241
FaceAppearances::FaceAppearances(ViewProviderPartExt* vp, QWidget* parent)
245
d->ui->setupUi(this);
248
d->ui->groupBox->setTitle(QString::fromUtf8(vp->getObject()->Label.getValue()));
249
d->ui->buttonCustomAppearance->setDisabled(true);
251
FaceSelection* gate = new FaceSelection(d->vp->getObject());
252
Gui::Selection().addSelectionGate(gate);
255
d->connectDelDoc = Gui::Application::Instance->signalDeleteDocument.connect(std::bind
256
(&FaceAppearances::slotDeleteDocument, this, sp::_1));
257
d->connectDelObj = Gui::Application::Instance->signalDeletedObject.connect(std::bind
258
(&FaceAppearances::slotDeleteObject, this, sp::_1));
259
d->connectUndoDoc = d->doc->signalUndoDocument.connect(std::bind
260
(&FaceAppearances::slotUndoDocument, this, sp::_1));
264
FaceAppearances::~FaceAppearances()
267
d->view->stopSelection();
268
d->view->removeEventCallback(SoMouseButtonEvent::getClassTypeId(),
269
Private::selectionCallback, this);
270
d->view->setSelectionEnabled(true);
272
Gui::Selection().rmvSelectionGate();
273
d->connectDelDoc.disconnect();
274
d->connectDelObj.disconnect();
275
d->connectUndoDoc.disconnect();
279
void FaceAppearances::setupConnections()
282
connect(d->ui->defaultButton, &QPushButton::clicked,
283
this, &FaceAppearances::onDefaultButtonClicked);
284
connect(d->ui->boxSelection, &QPushButton::toggled,
285
this, &FaceAppearances::onBoxSelectionToggled);
286
connect(d->ui->widgetMaterial, &MatGui::MaterialTreeWidget::materialSelected,
287
this, &FaceAppearances::onMaterialSelected);
288
connect(d->ui->buttonCustomAppearance, &QPushButton::clicked,
289
this, &FaceAppearances::onButtonCustomAppearanceClicked);
293
void FaceAppearances::slotUndoDocument(const Gui::Document& Doc)
295
if (d->doc == &Doc) {
297
Gui::Control().closeDialog();
301
void FaceAppearances::slotDeleteDocument(const Gui::Document& Doc)
304
Gui::Control().closeDialog();
307
void FaceAppearances::slotDeleteObject(const Gui::ViewProvider& obj)
310
Gui::Control().closeDialog();
313
void FaceAppearances::onBoxSelectionToggled(bool checked)
315
Gui::View3DInventor* view = qobject_cast<Gui::View3DInventor*>(Gui::getMainWindow()->activeWindow());
316
// toggle the button state and feature
317
d->boxSelection = checked;
319
// end box selection mode
321
view->getViewer()->stopSelection();
324
if (view && checked) {
325
Gui::View3DInventorViewer* viewer = view->getViewer();
326
if (!viewer->isSelecting()) {
327
viewer->startSelection(Gui::View3DInventorViewer::Rubberband);
328
viewer->addEventCallback(SoMouseButtonEvent::getClassTypeId(), Private::selectionCallback, this);
329
// avoid that the selection node handles the event otherwise the callback function won't be
330
// called immediately
331
viewer->setSelectionEnabled(false);
337
void FaceAppearances::onDefaultButtonClicked()
339
std::fill(d->perface.begin(), d->perface.end(), d->vp->ShapeAppearance[0]);
340
d->vp->ShapeAppearance.setValues(d->perface);
343
void FaceAppearances::onMaterialSelected(const std::shared_ptr<Materials::Material>& material)
345
if (!d->index.isEmpty()) {
346
for (int it : d->index) {
347
d->perface[it] = material->getMaterialAppearance();
349
d->vp->ShapeAppearance.setValues(d->perface);
350
// new color has been applied, unselect so that users can see this
351
onSelectionChanged(Gui::SelectionChanges::ClrSelection);
352
Gui::Selection().clearSelection();
356
void FaceAppearances::onSelectionChanged(const Gui::SelectionChanges& msg)
358
// no object selected in the combobox or no sub-element was selected
361
bool selection_changed = false;
362
if (msg.Type == Gui::SelectionChanges::AddSelection) {
363
// when adding a sub-element to the selection check
364
// whether this is the currently handled object
365
App::Document* doc = d->obj->getDocument();
366
std::string docname = doc->getName();
367
std::string objname = d->obj->getNameInDocument();
368
if (docname == msg.pDocName && objname == msg.pObjectName) {
369
int index = std::atoi(msg.pSubName + 4) - 1;
370
d->index.insert(index);
371
const App::Color& faceColor = d->perface[index].diffuseColor;
373
// alpha of App::Color is contrary to the one of QColor
374
color.setRgbF(faceColor.r, faceColor.g, faceColor.b, (1.0 - faceColor.a));
375
selection_changed = true;
378
else if (msg.Type == Gui::SelectionChanges::RmvSelection) {
379
App::Document* doc = d->obj->getDocument();
380
std::string docname = doc->getName();
381
std::string objname = d->obj->getNameInDocument();
382
if (docname == msg.pDocName && objname == msg.pObjectName) {
383
int index = std::atoi(msg.pSubName + 4) - 1;
384
d->index.remove(index);
385
selection_changed = true;
388
else if (msg.Type == Gui::SelectionChanges::ClrSelection) {
390
selection_changed = true;
393
if (selection_changed && !d->boxSelection) {
398
void FaceAppearances::updatePanel()
400
QString faces = QString::fromLatin1("[");
401
int size = d->index.size();
402
for (int it : d->index) {
403
faces += QString::number(it + 1);
405
faces += QString::fromLatin1(",");
407
faces += QString::fromLatin1("]");
409
int maxWidth = d->ui->labelElement->width();
410
QFontMetrics fm(d->ui->labelElement->font());
411
if (Gui::QtTools::horizontalAdvance(fm, faces) > maxWidth) {
412
faces = fm.elidedText(faces, Qt::ElideMiddle, maxWidth);
415
d->ui->labelElement->setText(faces);
416
d->ui->buttonCustomAppearance->setDisabled(d->index.isEmpty());
419
int FaceAppearances::getFirstIndex() const
421
if (!d->index.isEmpty()) {
422
return *(d->index.begin());
429
* Opens a dialog that allows to modify the 'ShapeMaterial' property of all selected view providers.
431
void FaceAppearances::onButtonCustomAppearanceClicked()
433
Gui::Dialog::DlgMaterialPropertiesImp dlg(this);
434
App::Material mat = d->perface[getFirstIndex()];
435
dlg.setCustomMaterial(mat);
436
dlg.setDefaultMaterial(mat);
439
// Set the face appearance
440
if (!d->index.isEmpty()) {
441
for (int it : d->index) {
442
d->perface[it] = dlg.getCustomMaterial();
444
d->vp->ShapeAppearance.setValues(d->perface);
445
// new color has been applied, unselect so that users can see this
446
onSelectionChanged(Gui::SelectionChanges::ClrSelection);
447
Gui::Selection().clearSelection();
451
void FaceAppearances::open()
453
Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument());
454
doc->openCommand(QT_TRANSLATE_NOOP("Command", "Change face colors"));
457
bool FaceAppearances::accept()
459
Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument());
460
doc->commitCommand();
465
bool FaceAppearances::reject()
467
Gui::Document* doc = Gui::Application::Instance->getDocument(d->vp->getObject()->getDocument());
473
void FaceAppearances::changeEvent(QEvent* e)
475
QWidget::changeEvent(e);
476
if (e->type() == QEvent::LanguageChange) {
477
d->ui->retranslateUi(this);
482
/* TRANSLATOR PartGui::TaskFaceAppearances */
484
TaskFaceAppearances::TaskFaceAppearances(ViewProviderPartExt* vp)
486
widget = new FaceAppearances(vp);
490
TaskFaceAppearances::~TaskFaceAppearances() = default;
492
void TaskFaceAppearances::open()
497
void TaskFaceAppearances::clicked(int)
501
bool TaskFaceAppearances::accept()
503
return widget->accept();
506
bool TaskFaceAppearances::reject()
508
return widget->reject();
511
#include "moc_TaskFaceAppearances.cpp"