1
/***************************************************************************
2
* Copyright (c) 2017 Werner Mayer <wmayer[at]users.sourceforge.net> *
3
* Copyright (c) 2017 Christophe Grellier <cg[at]grellier.fr> *
5
* This file is part of the FreeCAD CAx development system. *
7
* This library is free software; you can redistribute it and/or *
8
* modify it under the terms of the GNU Library General Public *
9
* License as published by the Free Software Foundation; either *
10
* version 2 of the License, or (at your option) any later version. *
12
* This library is distributed in the hope that it will be useful, *
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
* GNU Library General Public License for more details. *
17
* You should have received a copy of the GNU Library General Public *
18
* License along with this library; see the file COPYING.LIB. If not, *
19
* write to the Free Software Foundation, Inc., 59 Temple Place, *
20
* Suite 330, Boston, MA 02111-1307, USA *
22
***************************************************************************/
24
#include "PreCompiled.h"
30
#include <GeomAbs_Shape.hxx>
32
#include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
33
#include <TopTools_IndexedMapOfShape.hxx>
34
#include <TopTools_ListIteratorOfListOfShape.hxx>
37
#include <App/Document.h>
38
#include <Gui/Application.h>
39
#include <Gui/Command.h>
40
#include <Gui/Document.h>
41
#include <Gui/SelectionObject.h>
42
#include <Gui/Widgets.h>
43
#include <Mod/Part/Gui/ViewProvider.h>
45
#include "TaskFilling.h"
46
#include "TaskFillingEdge.h"
47
#include "ui_TaskFillingEdge.h"
50
using namespace SurfaceGui;
55
class FillingEdgePanel::ShapeSelection: public Gui::SelectionFilterGate
58
ShapeSelection(FillingEdgePanel::SelectionMode& mode, Surface::Filling* editedObject)
59
: Gui::SelectionFilterGate(nullPointer())
61
, editedObject(editedObject)
63
~ShapeSelection() override
65
mode = FillingEdgePanel::None;
68
* Allow the user to pick only edges.
70
bool allow(App::Document*, App::DocumentObject* pObj, const char* sSubName) override
72
// don't allow references to itself
73
if (pObj == editedObject) {
76
if (!pObj->isDerivedFrom(Part::Feature::getClassTypeId())) {
80
if (!sSubName || sSubName[0] == '\0') {
85
case FillingEdgePanel::AppendEdge:
86
return allowEdge(true, pObj, sSubName);
87
case FillingEdgePanel::RemoveEdge:
88
return allowEdge(false, pObj, sSubName);
95
bool allowEdge(bool appendEdges, App::DocumentObject* pObj, const char* sSubName)
97
std::string element(sSubName);
98
if (element.substr(0, 4) != "Edge") {
102
auto links = editedObject->UnboundEdges.getSubListValues();
103
for (const auto& it : links) {
104
if (it.first == pObj) {
105
for (const auto& jt : it.second) {
106
if (jt == sSubName) {
117
FillingEdgePanel::SelectionMode& mode;
118
Surface::Filling* editedObject;
121
// ----------------------------------------------------------------------------
123
FillingEdgePanel::FillingEdgePanel(ViewProviderFilling* vp, Surface::Filling* obj)
125
ui = new Ui_TaskFillingEdge();
129
selectionMode = None;
132
setEditedObject(obj);
134
// Create context menu
135
QAction* action = new QAction(tr("Remove"), this);
136
action->setShortcut(QString::fromLatin1("Del"));
137
action->setShortcutContext(Qt::WidgetShortcut);
138
ui->listUnbound->addAction(action);
139
connect(action, &QAction::triggered, this, &FillingEdgePanel::onDeleteUnboundEdge);
140
ui->listUnbound->setContextMenuPolicy(Qt::ActionsContextMenu);
144
* Destroys the object and frees any allocated resources
146
FillingEdgePanel::~FillingEdgePanel()
148
// no need to delete child widgets, Qt does it all for us
150
Gui::Selection().rmvSelectionGate();
153
void FillingEdgePanel::setupConnections()
155
connect(ui->buttonUnboundEdgeAdd,
156
&QToolButton::toggled,
158
&FillingEdgePanel::onButtonUnboundEdgeAddToggled);
159
connect(ui->buttonUnboundEdgeRemove,
160
&QToolButton::toggled,
162
&FillingEdgePanel::onButtonUnboundEdgeRemoveToggled);
163
connect(ui->listUnbound,
164
&QListWidget::itemDoubleClicked,
166
&FillingEdgePanel::onListUnboundItemDoubleClicked);
167
connect(ui->buttonUnboundAccept,
168
&QPushButton::clicked,
170
&FillingEdgePanel::onButtonUnboundAcceptClicked);
171
connect(ui->buttonUnboundIgnore,
172
&QPushButton::clicked,
174
&FillingEdgePanel::onButtonUnboundIgnoreClicked);
177
void FillingEdgePanel::appendButtons(Gui::ButtonGroup* buttonGroup)
179
buttonGroup->addButton(ui->buttonUnboundEdgeAdd, int(SelectionMode::AppendEdge));
180
buttonGroup->addButton(ui->buttonUnboundEdgeRemove, int(SelectionMode::RemoveEdge));
183
// stores object pointer, its old fill type and adjusts radio buttons according to it.
184
void FillingEdgePanel::setEditedObject(Surface::Filling* fea)
188
// get the free edges, if set their adjacent faces and continuities
189
auto objects = editedObject->UnboundEdges.getValues();
190
auto edges = editedObject->UnboundEdges.getSubValues();
191
auto count = objects.size();
193
// fill up faces if wrong size
194
auto faces = editedObject->UnboundFaces.getValues();
195
if (faces.size() != edges.size()) {
196
faces.resize(edges.size());
197
std::fill(faces.begin(), faces.end(), std::string());
200
// fill up continuities if wrong size
201
auto conts = editedObject->UnboundOrder.getValues();
202
if (edges.size() != conts.size()) {
203
conts.resize(edges.size());
204
std::fill(conts.begin(), conts.end(), static_cast<long>(GeomAbs_C0));
207
App::Document* doc = editedObject->getDocument();
208
for (std::size_t i = 0; i < count; i++) {
209
App::DocumentObject* obj = objects[i];
210
std::string edge = edges[i];
211
std::string face = faces[i];
213
QListWidgetItem* item = new QListWidgetItem(ui->listUnbound);
214
ui->listUnbound->addItem(item);
216
QString text = QString::fromLatin1("%1.%2").arg(QString::fromUtf8(obj->Label.getValue()),
217
QString::fromStdString(edge));
220
// The user data field of a list widget item
221
// is a list of five elementa:
224
// 3. sub-element name of the edge
225
// 4. sub-element of an adjacent face or empty string
226
// 5. the continuity as int
227
QList<QVariant> data;
228
data << QByteArray(doc->getName());
229
data << QByteArray(obj->getNameInDocument());
230
data << QByteArray(edge.c_str());
231
data << QByteArray(face.c_str());
232
data << static_cast<int>(conts[i]);
233
item->setData(Qt::UserRole, data);
236
// attach this document observer
237
attachDocument(Gui::Application::Instance->getDocument(doc));
240
void FillingEdgePanel::changeEvent(QEvent* e)
242
if (e->type() == QEvent::LanguageChange) {
243
ui->retranslateUi(this);
246
QWidget::changeEvent(e);
250
void FillingEdgePanel::open()
254
// highlight the boundary edges
255
this->vp->highlightReferences(ViewProviderFilling::Edge,
256
editedObject->UnboundEdges.getSubListValues(),
259
Gui::Selection().clearSelection();
262
void FillingEdgePanel::clearSelection()
264
Gui::Selection().clearSelection();
267
void FillingEdgePanel::checkOpenCommand()
269
if (checkCommand && !Gui::Command::hasPendingCommand()) {
270
std::string Msg("Edit ");
271
Msg += editedObject->Label.getValue();
272
Gui::Command::openCommand(Msg.c_str());
273
checkCommand = false;
277
void FillingEdgePanel::slotUndoDocument(const Gui::Document&)
282
void FillingEdgePanel::slotRedoDocument(const Gui::Document&)
287
void FillingEdgePanel::slotDeletedObject(const Gui::ViewProviderDocumentObject& Obj)
289
// If this view provider is being deleted then reset the colors of
290
// referenced part objects. The dialog will be deleted later.
291
if (this->vp == &Obj) {
292
this->vp->highlightReferences(ViewProviderFilling::Edge,
293
editedObject->UnboundEdges.getSubListValues(),
298
bool FillingEdgePanel::accept()
300
selectionMode = None;
301
Gui::Selection().rmvSelectionGate();
303
if (editedObject->mustExecute()) {
304
editedObject->recomputeFeature();
306
if (!editedObject->isValid()) {
307
QMessageBox::warning(this,
308
tr("Invalid object"),
309
QString::fromLatin1(editedObject->getStatusString()));
313
this->vp->highlightReferences(ViewProviderFilling::Edge,
314
editedObject->UnboundEdges.getSubListValues(),
319
bool FillingEdgePanel::reject()
321
this->vp->highlightReferences(ViewProviderFilling::Edge,
322
editedObject->UnboundEdges.getSubListValues(),
325
selectionMode = None;
326
Gui::Selection().rmvSelectionGate();
331
void FillingEdgePanel::onButtonUnboundEdgeAddToggled(bool checked)
334
// 'selectionMode' is passed by reference and changed when the filter is deleted
335
Gui::Selection().addSelectionGate(new ShapeSelection(selectionMode, editedObject));
336
selectionMode = AppendEdge;
338
else if (selectionMode == AppendEdge) {
343
void FillingEdgePanel::onButtonUnboundEdgeRemoveToggled(bool checked)
346
// 'selectionMode' is passed by reference and changed when the filter is deleted
347
Gui::Selection().addSelectionGate(new ShapeSelection(selectionMode, editedObject));
348
selectionMode = RemoveEdge;
350
else if (selectionMode == RemoveEdge) {
355
void FillingEdgePanel::onListUnboundItemDoubleClicked(QListWidgetItem* item)
357
Gui::Selection().clearSelection();
358
Gui::Selection().rmvSelectionGate();
359
selectionMode = None;
361
ui->comboBoxUnboundFaces->clear();
362
ui->comboBoxUnboundCont->clear();
365
QList<QVariant> data;
366
data = item->data(Qt::UserRole).toList();
369
App::Document* doc = App::GetApplication().getDocument(data[0].toByteArray());
370
App::DocumentObject* obj = doc ? doc->getObject(data[1].toByteArray()) : nullptr;
371
if (obj && obj->isDerivedFrom<Part::Feature>()) {
372
const Part::TopoShape& shape = static_cast<Part::Feature*>(obj)->Shape.getShape();
373
TopoDS_Shape edge = shape.getSubShape(data[2].toByteArray());
375
// build up map edge->face
376
TopTools_IndexedMapOfShape faces;
377
TopExp::MapShapes(shape.getShape(), TopAbs_FACE, faces);
378
TopTools_IndexedDataMapOfShapeListOfShape edge2Face;
379
TopExp::MapShapesAndAncestors(shape.getShape(),
383
const TopTools_ListOfShape& adj_faces = edge2Face.FindFromKey(edge);
384
if (adj_faces.Extent() > 0) {
385
int n = adj_faces.Extent();
386
ui->statusLabel->setText(tr("Edge has %n adjacent face(s)", nullptr, n));
388
// fill up the combo boxes
389
modifyBoundary(true);
390
ui->comboBoxUnboundFaces->addItem(tr("None"), QByteArray(""));
391
ui->comboBoxUnboundCont->addItem(QString::fromLatin1("C0"),
392
static_cast<int>(GeomAbs_C0));
393
ui->comboBoxUnboundCont->addItem(QString::fromLatin1("G1"),
394
static_cast<int>(GeomAbs_G1));
395
ui->comboBoxUnboundCont->addItem(QString::fromLatin1("G2"),
396
static_cast<int>(GeomAbs_G2));
397
TopTools_ListIteratorOfListOfShape it(adj_faces);
398
for (; it.More(); it.Next()) {
399
const TopoDS_Shape& F = it.Value();
400
int index = faces.FindIndex(F);
401
QString text = QString::fromLatin1("Face%1").arg(index);
402
ui->comboBoxUnboundFaces->addItem(text, text.toLatin1());
405
// activate face and continuity
406
if (data.size() == 5) {
407
int index = ui->comboBoxUnboundFaces->findData(data[3]);
408
ui->comboBoxUnboundFaces->setCurrentIndex(index);
409
index = ui->comboBoxUnboundCont->findData(data[4]);
410
ui->comboBoxUnboundCont->setCurrentIndex(index);
414
ui->statusLabel->setText(tr("Edge has no adjacent faces"));
418
Gui::Selection().addSelection(data[0].toByteArray(),
419
data[1].toByteArray(),
420
data[2].toByteArray());
427
void FillingEdgePanel::onSelectionChanged(const Gui::SelectionChanges& msg)
429
if (selectionMode == None) {
433
if (msg.Type == Gui::SelectionChanges::AddSelection) {
435
if (selectionMode == AppendEdge) {
436
QListWidgetItem* item = new QListWidgetItem(ui->listUnbound);
437
ui->listUnbound->addItem(item);
439
Gui::SelectionObject sel(msg);
440
QString text = QString::fromLatin1("%1.%2").arg(
441
QString::fromUtf8(sel.getObject()->Label.getValue()),
442
QString::fromLatin1(msg.pSubName));
445
QList<QVariant> data;
446
data << QByteArray(msg.pDocName);
447
data << QByteArray(msg.pObjectName);
448
data << QByteArray(msg.pSubName);
449
data << QByteArray("");
450
data << static_cast<int>(GeomAbs_C0);
451
item->setData(Qt::UserRole, data);
453
auto objects = editedObject->UnboundEdges.getValues();
454
std::size_t count = objects.size();
455
objects.push_back(sel.getObject());
456
auto element = editedObject->UnboundEdges.getSubValues();
457
element.emplace_back(msg.pSubName);
458
editedObject->UnboundEdges.setValues(objects, element);
460
// extend faces and continuities lists if needed
461
auto faces = editedObject->UnboundFaces.getValues();
462
if (count == faces.size()) {
463
faces.emplace_back();
464
editedObject->UnboundFaces.setValues(faces);
466
auto conts = editedObject->UnboundOrder.getValues();
467
if (count == conts.size()) {
468
conts.push_back(static_cast<long>(GeomAbs_C0));
469
editedObject->UnboundOrder.setValues(conts);
472
this->vp->highlightReferences(ViewProviderFilling::Edge,
473
editedObject->UnboundEdges.getSubListValues(),
476
else if (selectionMode == RemoveEdge) {
477
Gui::SelectionObject sel(msg);
478
QList<QVariant> data;
479
data << QByteArray(msg.pDocName);
480
data << QByteArray(msg.pObjectName);
481
data << QByteArray(msg.pSubName);
483
// only the three first elements must match
484
for (int i = 0; i < ui->listUnbound->count(); i++) {
485
QListWidgetItem* item = ui->listUnbound->item(i);
486
QList<QVariant> userdata = item->data(Qt::UserRole).toList();
487
if (userdata.mid(0, 3) == data) {
488
ui->listUnbound->takeItem(i);
494
this->vp->highlightReferences(ViewProviderFilling::Edge,
495
editedObject->UnboundEdges.getSubListValues(),
497
App::DocumentObject* obj = sel.getObject();
498
std::string sub = msg.pSubName;
499
auto objects = editedObject->UnboundEdges.getValues();
500
auto element = editedObject->UnboundEdges.getSubValues();
501
auto it = objects.begin();
502
auto jt = element.begin();
504
for (; it != objects.end() && jt != element.end(); ++it, ++jt) {
505
if (*it == obj && *jt == sub) {
506
std::size_t index = std::distance(objects.begin(), it);
510
editedObject->UnboundEdges.setValues(objects, element);
512
// try to remove the item also from the faces
513
auto faces = editedObject->UnboundFaces.getValues();
514
if (index < faces.size()) {
515
faces.erase(faces.begin() + index);
516
editedObject->UnboundFaces.setValues(faces);
519
// try to remove the item also from the orders
520
auto order = editedObject->UnboundOrder.getValues();
521
if (index < order.size()) {
522
order.erase(order.begin() + index);
523
editedObject->UnboundOrder.setValues(order);
528
this->vp->highlightReferences(ViewProviderFilling::Edge,
529
editedObject->UnboundEdges.getSubListValues(),
533
editedObject->recomputeFeature();
534
QTimer::singleShot(50, this, &FillingEdgePanel::clearSelection);
538
void FillingEdgePanel::onDeleteUnboundEdge()
540
int row = ui->listUnbound->currentRow();
541
QListWidgetItem* item = ui->listUnbound->item(row);
544
QList<QVariant> data;
545
data = item->data(Qt::UserRole).toList();
546
ui->listUnbound->takeItem(row);
549
App::Document* doc = App::GetApplication().getDocument(data[0].toByteArray());
550
App::DocumentObject* obj = doc ? doc->getObject(data[1].toByteArray()) : nullptr;
551
std::string sub = data[2].toByteArray().constData();
552
auto objects = editedObject->UnboundEdges.getValues();
553
auto element = editedObject->UnboundEdges.getSubValues();
554
auto it = objects.begin();
555
auto jt = element.begin();
556
this->vp->highlightReferences(ViewProviderFilling::Edge,
557
editedObject->UnboundEdges.getSubListValues(),
559
for (; it != objects.end() && jt != element.end(); ++it, ++jt) {
560
if (*it == obj && *jt == sub) {
561
std::size_t index = std::distance(objects.begin(), it);
565
editedObject->UnboundEdges.setValues(objects, element);
567
// try to remove the item also from the faces
568
auto faces = editedObject->UnboundFaces.getValues();
569
if (index < faces.size()) {
570
faces.erase(faces.begin() + index);
571
editedObject->UnboundFaces.setValues(faces);
574
// try to remove the item also from the orders
575
auto order = editedObject->UnboundOrder.getValues();
576
if (index < order.size()) {
577
order.erase(order.begin() + index);
578
editedObject->UnboundOrder.setValues(order);
583
this->vp->highlightReferences(ViewProviderFilling::Edge,
584
editedObject->UnboundEdges.getSubListValues(),
587
editedObject->recomputeFeature();
591
void FillingEdgePanel::onButtonUnboundAcceptClicked()
593
QListWidgetItem* item = ui->listUnbound->currentItem();
595
QList<QVariant> data;
596
data = item->data(Qt::UserRole).toList();
599
ui->comboBoxUnboundFaces->itemData(ui->comboBoxUnboundFaces->currentIndex());
600
QVariant cont = ui->comboBoxUnboundCont->itemData(ui->comboBoxUnboundCont->currentIndex());
601
if (data.size() == 5) {
610
item->setData(Qt::UserRole, data);
612
std::size_t index = ui->listUnbound->row(item);
614
// try to set the item of the faces
615
auto faces = editedObject->UnboundFaces.getValues();
616
if (index < faces.size()) {
617
faces[index] = face.toByteArray().data();
618
editedObject->UnboundFaces.setValues(faces);
621
// try to set the item of the orders
622
auto order = editedObject->UnboundOrder.getValues();
623
if (index < order.size()) {
624
order[index] = cont.toInt();
625
editedObject->UnboundOrder.setValues(order);
629
modifyBoundary(false);
630
ui->comboBoxUnboundFaces->clear();
631
ui->comboBoxUnboundCont->clear();
632
ui->statusLabel->clear();
634
editedObject->recomputeFeature();
637
void FillingEdgePanel::onButtonUnboundIgnoreClicked()
639
modifyBoundary(false);
640
ui->comboBoxUnboundFaces->clear();
641
ui->comboBoxUnboundCont->clear();
642
ui->statusLabel->clear();
645
void FillingEdgePanel::modifyBoundary(bool on)
647
ui->buttonUnboundEdgeAdd->setDisabled(on);
648
ui->buttonUnboundEdgeRemove->setDisabled(on);
649
ui->listUnbound->setDisabled(on);
651
ui->comboBoxUnboundFaces->setEnabled(on);
652
ui->comboBoxUnboundCont->setEnabled(on);
653
ui->buttonUnboundAccept->setEnabled(on);
654
ui->buttonUnboundIgnore->setEnabled(on);
656
} // namespace SurfaceGui
658
void FillingEdgePanel::exitSelectionMode()
660
// 'selectionMode' is passed by reference to the filter and changed when the filter is deleted
661
Gui::Selection().clearSelection();
662
Gui::Selection().rmvSelectionGate();
665
#include "moc_TaskFillingEdge.cpp"