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"
26
# include <QButtonGroup>
27
# include <QMessageBox>
28
# include <QTextStream>
31
# include <TopTools_IndexedMapOfShape.hxx>
34
#include <App/Application.h>
35
#include <App/Document.h>
36
#include <App/DocumentObject.h>
37
#include <Base/Console.h>
38
#include <Gui/Application.h>
39
#include <Gui/BitmapFactory.h>
40
#include <Gui/Command.h>
41
#include <Gui/Document.h>
42
#include <Gui/Selection.h>
43
#include <Gui/SelectionFilter.h>
44
#include <Gui/SelectionObject.h>
45
#include <Mod/Part/App/PartFeature.h>
47
#include "TaskShapeBuilder.h"
48
#include "ui_TaskShapeBuilder.h"
49
#include "BoxSelection.h"
52
using namespace PartGui;
55
class ShapeSelection : public Gui::SelectionFilterGate
58
enum Type {VERTEX, EDGE, FACE, ALL};
61
: Gui::SelectionFilterGate(nullPointer())
64
void setMode(Type mode)
68
bool allow(App::Document*, App::DocumentObject* obj, const char*sSubName) override
70
if (!obj || !obj->isDerivedFrom(Part::Feature::getClassTypeId()))
72
if (!sSubName || sSubName[0] == '\0')
74
std::string element(sSubName);
77
return element.substr(0,6) == "Vertex";
79
return element.substr(0,4) == "Edge";
81
return element.substr(0,4) == "Face";
89
class ShapeBuilderWidget::Private
92
Ui_TaskShapeBuilder ui;
95
BoxSelection selection;
98
Gui::Command::runCommand(Gui::Command::App, "from FreeCAD import Base");
99
Gui::Command::runCommand(Gui::Command::App, "import Part");
101
~Private() = default;
104
/* TRANSLATOR PartGui::ShapeBuilderWidget */
106
ShapeBuilderWidget::ShapeBuilderWidget(QWidget* parent)
111
d->ui.label->setText(QString());
112
d->bg.addButton(d->ui.radioButtonEdgeFromVertex, 0);
113
d->bg.addButton(d->ui.radioButtonWireFromEdge, 1);
114
d->bg.addButton(d->ui.radioButtonFaceFromVertex, 2);
115
d->bg.addButton(d->ui.radioButtonFaceFromEdge, 3);
116
d->bg.addButton(d->ui.radioButtonShellFromFace, 4);
117
d->bg.addButton(d->ui.radioButtonSolidFromShell, 5);
118
d->bg.setExclusive(true);
120
connect(d->ui.selectButton, &QPushButton::clicked,
121
this, &ShapeBuilderWidget::onSelectButtonClicked);
122
connect(d->ui.createButton, &QPushButton::clicked,
123
this, &ShapeBuilderWidget::onCreateButtonClicked);
124
#if QT_VERSION < QT_VERSION_CHECK(5,15,0)
125
connect(&d->bg, qOverload<int>(&QButtonGroup::buttonClicked),
126
this, &ShapeBuilderWidget::switchMode);
128
connect(&d->bg, &QButtonGroup::idClicked,
129
this, &ShapeBuilderWidget::switchMode);
132
d->gate = new ShapeSelection();
133
Gui::Selection().addSelectionGate(d->gate);
135
d->bg.button(0)->setChecked(true);
139
ShapeBuilderWidget::~ShapeBuilderWidget()
141
Gui::Selection().rmvSelectionGate();
145
void ShapeBuilderWidget::onSelectionChanged(const Gui::SelectionChanges& msg)
147
if (d->ui.checkFaces->isChecked()) {
148
if (msg.Type == Gui::SelectionChanges::AddSelection) {
149
std::string subName(msg.pSubName);
150
if (!subName.empty()) {
151
// From the shape get all faces and add them to the selection
152
bool blocked = blockSelection(true);
153
App::Document* doc = App::GetApplication().getDocument(msg.pDocName);
154
App::DocumentObject* obj = doc->getObject(msg.pObjectName);
155
if (obj->isDerivedFrom<Part::Feature>()) {
156
TopoDS_Shape myShape = static_cast<Part::Feature*>(obj)->Shape.getValue();
157
TopTools_IndexedMapOfShape all_faces;
158
TopExp::MapShapes(myShape, TopAbs_FACE, all_faces);
159
for (int i=1; i<= all_faces.Extent(); i++) {
160
TopoDS_Shape face = all_faces(i);
161
if (!face.IsNull()) {
162
std::stringstream str;
164
Gui::Selection().addSelection(msg.pDocName, msg.pObjectName, str.str().c_str());
169
blockSelection(blocked);
175
void ShapeBuilderWidget::onCreateButtonClicked()
177
int mode = d->bg.checkedId();
178
Gui::Document* doc = Gui::Application::Instance->activeDocument();
184
createEdgeFromVertex();
186
else if (mode == 1) {
187
createWireFromEdge();
189
else if (mode == 2) {
190
createFaceFromVertex();
192
else if (mode == 3) {
193
createFaceFromEdge();
195
else if (mode == 4) {
196
createShellFromFace();
198
else if (mode == 5) {
199
createSolidFromShell();
201
doc->getDocument()->recompute();
202
Gui::Selection().clearSelection();
204
catch (const Base::Exception& e) {
205
Base::Console().Error("%s\n", e.what());
209
void ShapeBuilderWidget::onSelectButtonClicked()
211
int id = d->bg.checkedId();
212
if (id == 0 || id == 2) {
213
d->selection.start(TopAbs_VERTEX);
215
else if (id == 1 || id == 3) {
216
d->selection.start(TopAbs_EDGE);
219
d->selection.start(TopAbs_FACE);
222
QMessageBox::warning(this, tr("Unsupported"), tr("Box selection for shells is not supported"));
226
void ShapeBuilderWidget::createEdgeFromVertex()
228
Gui::SelectionFilter vertexFilter ("SELECT Part::Feature SUBELEMENT Vertex COUNT 2");
229
bool matchVertex = vertexFilter.match();
231
QMessageBox::critical(this, tr("Wrong selection"), tr("Select two vertices"));
235
std::vector<Gui::SelectionObject> sel = vertexFilter.Result[0];
236
std::vector<QString> elements;
237
std::vector<Gui::SelectionObject>::iterator it;
238
std::vector<std::string>::const_iterator jt;
239
for (it=sel.begin();it!=sel.end();++it) {
240
for (jt=it->getSubNames().begin();jt!=it->getSubNames().end();++jt) {
242
QTextStream str(&line);
243
str << "App.ActiveDocument." << it->getFeatName() << ".Shape." << jt->c_str() << ".Point";
244
elements.push_back(line);
248
// should actually never happen
249
if (elements.size() != 2) {
250
QMessageBox::critical(this, tr("Wrong selection"), tr("Select two vertices"));
255
cmd = QString::fromLatin1(
256
"_=Part.makeLine(%1, %2)\n"
257
"if _.isNull(): raise RuntimeError('Failed to create edge')\n"
258
"App.ActiveDocument.addObject('Part::Feature','Edge').Shape=_\n"
260
).arg(elements[0], elements[1]);
263
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Edge"));
264
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
265
Gui::Application::Instance->activeDocument()->commitCommand();
267
catch (const Base::Exception&) {
268
Gui::Application::Instance->activeDocument()->abortCommand();
273
void ShapeBuilderWidget::createWireFromEdge()
275
Gui::SelectionFilter edgeFilter ("SELECT Part::Feature SUBELEMENT Edge COUNT 1..");
276
bool matchEdge = edgeFilter.match();
278
QMessageBox::critical(this, tr("Wrong selection"), tr("Select one or more edges"));
282
std::vector<Gui::SelectionObject> sel = edgeFilter.Result[0];
283
std::vector<Gui::SelectionObject>::iterator it;
284
std::vector<std::string>::const_iterator jt;
287
QTextStream str(&list);
289
for (it=sel.begin();it!=sel.end();++it) {
290
for (jt=it->getSubNames().begin();jt!=it->getSubNames().end();++jt) {
291
str << "App.ActiveDocument." << it->getFeatName() << ".Shape." << jt->c_str() << ", ";
297
cmd = QString::fromLatin1(
298
"_=Part.Wire(Part.__sortEdges__(%1))\n"
299
"if _.isNull(): raise RuntimeError('Failed to create a wire')\n"
300
"App.ActiveDocument.addObject('Part::Feature','Wire').Shape=_\n"
304
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Wire"));
305
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
306
Gui::Application::Instance->activeDocument()->commitCommand();
308
catch (const Base::Exception&) {
309
Gui::Application::Instance->activeDocument()->abortCommand();
314
void ShapeBuilderWidget::createFaceFromVertex()
316
Gui::SelectionFilter vertexFilter ("SELECT Part::Feature SUBELEMENT Vertex COUNT 3..");
317
bool matchVertex = vertexFilter.match();
319
QMessageBox::critical(this, tr("Wrong selection"), tr("Select three or more vertices"));
323
std::vector<Gui::SelectionObject> sel = vertexFilter.Result[0];
324
std::vector<Gui::SelectionObject>::iterator it;
325
std::vector<std::string>::const_iterator jt;
328
QTextStream str(&list);
330
for (it=sel.begin();it!=sel.end();++it) {
331
for (jt=it->getSubNames().begin();jt!=it->getSubNames().end();++jt) {
332
str << "App.ActiveDocument." << it->getFeatName() << ".Shape." << jt->c_str() << ".Point, ";
338
if (d->ui.checkPlanar->isChecked()) {
339
cmd = QString::fromLatin1(
340
"_=Part.Face(Part.makePolygon(%1, True))\n"
341
"if _.isNull(): raise RuntimeError('Failed to create face')\n"
342
"App.ActiveDocument.addObject('Part::Feature','Face').Shape=_\n"
347
cmd = QString::fromLatin1(
348
"_=Part.makeFilledFace(Part.makePolygon(%1, True).Edges)\n"
349
"if _.isNull(): raise RuntimeError('Failed to create face')\n"
350
"App.ActiveDocument.addObject('Part::Feature','Face').Shape=_\n"
356
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Face"));
357
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
358
Gui::Application::Instance->activeDocument()->commitCommand();
360
catch (const Base::Exception&) {
361
Gui::Application::Instance->activeDocument()->abortCommand();
366
void ShapeBuilderWidget::createFaceFromEdge()
368
Gui::SelectionFilter edgeFilter ("SELECT Part::Feature SUBELEMENT Edge COUNT 1..");
369
bool matchEdge = edgeFilter.match();
371
QMessageBox::critical(this, tr("Wrong selection"), tr("Select one or more edges"));
375
std::vector<Gui::SelectionObject> sel = edgeFilter.Result[0];
376
std::vector<Gui::SelectionObject>::iterator it;
377
std::vector<std::string>::const_iterator jt;
380
QTextStream str(&list);
382
for (it=sel.begin();it!=sel.end();++it) {
383
for (jt=it->getSubNames().begin();jt!=it->getSubNames().end();++jt) {
384
str << "App.ActiveDocument." << it->getFeatName() << ".Shape." << jt->c_str() << ", ";
390
if (d->ui.checkPlanar->isChecked()) {
391
cmd = QString::fromLatin1(
392
"_=Part.Face(Part.Wire(Part.__sortEdges__(%1)))\n"
393
"if _.isNull(): raise RuntimeError('Failed to create face')\n"
394
"App.ActiveDocument.addObject('Part::Feature','Face').Shape=_\n"
399
cmd = QString::fromLatin1(
400
"_=Part.makeFilledFace(Part.__sortEdges__(%1))\n"
401
"if _.isNull(): raise RuntimeError('Failed to create face')\n"
402
"App.ActiveDocument.addObject('Part::Feature','Face').Shape=_\n"
408
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Face"));
409
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
410
Gui::Application::Instance->activeDocument()->commitCommand();
412
catch (const Base::Exception&) {
413
Gui::Application::Instance->activeDocument()->abortCommand();
418
void ShapeBuilderWidget::createShellFromFace()
420
Gui::SelectionFilter faceFilter ("SELECT Part::Feature SUBELEMENT Face COUNT 2..");
421
bool matchFace = faceFilter.match();
423
QMessageBox::critical(this, tr("Wrong selection"), tr("Select two or more faces"));
427
std::vector<Gui::SelectionObject> sel = faceFilter.Result[0];
430
QTextStream str(&list);
431
if (d->ui.checkFaces->isChecked()) {
432
std::set<const App::DocumentObject*> obj;
433
for (const auto& it : sel)
434
obj.insert(it.getObject());
436
for (auto it : obj) {
437
str << "+ App.ActiveDocument." << it->getNameInDocument() << ".Shape.Faces";
442
for (const auto& it : sel) {
443
for (const auto& jt : it.getSubNames()) {
444
str << "App.ActiveDocument." << it.getFeatName() << ".Shape." << jt.c_str() << ", ";
451
if (d->ui.checkRefine->isEnabled() && d->ui.checkRefine->isChecked()) {
452
cmd = QString::fromLatin1(
454
"if _.isNull(): raise RuntimeError('Failed to create shell')\n"
455
"App.ActiveDocument.addObject('Part::Feature','Shell').Shape=_.removeSplitter()\n"
460
cmd = QString::fromLatin1(
462
"if _.isNull(): raise RuntimeError('Failed to create shell')\n"
463
"App.ActiveDocument.addObject('Part::Feature','Shell').Shape=_\n"
469
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Shell"));
470
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
471
Gui::Application::Instance->activeDocument()->commitCommand();
473
catch (const Base::Exception&) {
474
Gui::Application::Instance->activeDocument()->abortCommand();
479
void ShapeBuilderWidget::createSolidFromShell()
481
Gui::SelectionFilter partFilter ("SELECT Part::Feature COUNT 1");
482
bool matchPart = partFilter.match();
484
QMessageBox::critical(this, tr("Wrong selection"), tr("Select only one part object"));
489
QTextStream str(&line);
491
std::vector<Gui::SelectionObject> sel = partFilter.Result[0];
492
std::vector<Gui::SelectionObject>::iterator it;
493
for (it=sel.begin();it!=sel.end();++it) {
494
str << "App.ActiveDocument." << it->getFeatName() << ".Shape";
499
if (d->ui.checkRefine->isEnabled() && d->ui.checkRefine->isChecked()) {
500
cmd = QString::fromLatin1(
502
"if shell.ShapeType != 'Shell': raise RuntimeError('Part object is not a shell')\n"
503
"_=Part.Solid(shell)\n"
504
"if _.isNull(): raise RuntimeError('Failed to create solid')\n"
505
"App.ActiveDocument.addObject('Part::Feature','Solid').Shape=_.removeSplitter()\n"
510
cmd = QString::fromLatin1(
512
"if shell.ShapeType != 'Shell': raise RuntimeError('Part object is not a shell')\n"
513
"_=Part.Solid(shell)\n"
514
"if _.isNull(): raise RuntimeError('Failed to create solid')\n"
515
"App.ActiveDocument.addObject('Part::Feature','Solid').Shape=_\n"
521
Gui::Application::Instance->activeDocument()->openCommand(QT_TRANSLATE_NOOP("Command", "Solid"));
522
Gui::Command::runCommand(Gui::Command::App, cmd.toLatin1());
523
Gui::Application::Instance->activeDocument()->commitCommand();
525
catch (const Base::Exception&) {
526
Gui::Application::Instance->activeDocument()->abortCommand();
531
void ShapeBuilderWidget::switchMode(int mode)
533
Gui::Selection().clearSelection();
535
d->gate->setMode(ShapeSelection::VERTEX);
536
d->ui.label->setText(tr("Select two vertices to create an edge"));
537
d->ui.checkPlanar->setEnabled(false);
538
d->ui.checkFaces->setEnabled(false);
539
d->ui.checkRefine->setEnabled(false);
541
else if (mode == 1) {
542
d->gate->setMode(ShapeSelection::EDGE);
543
d->ui.label->setText(tr("Select adjacent edges"));
544
d->ui.checkPlanar->setEnabled(true);
545
d->ui.checkFaces->setEnabled(false);
546
d->ui.checkRefine->setEnabled(false);
548
else if (mode == 2) {
549
d->gate->setMode(ShapeSelection::VERTEX);
550
d->ui.label->setText(tr("Select a list of vertices"));
551
d->ui.checkPlanar->setEnabled(true);
552
d->ui.checkFaces->setEnabled(false);
553
d->ui.checkRefine->setEnabled(false);
555
else if (mode == 3) {
556
d->gate->setMode(ShapeSelection::EDGE);
557
d->ui.label->setText(tr("Select a closed set of edges"));
558
d->ui.checkPlanar->setEnabled(true);
559
d->ui.checkFaces->setEnabled(false);
560
d->ui.checkRefine->setEnabled(false);
562
else if (mode == 4) {
563
d->gate->setMode(ShapeSelection::FACE);
564
d->ui.label->setText(tr("Select adjacent faces"));
565
d->ui.checkPlanar->setEnabled(false);
566
d->ui.checkFaces->setEnabled(true);
567
d->ui.checkRefine->setEnabled(true);
570
d->gate->setMode(ShapeSelection::ALL);
571
d->ui.label->setText(tr("All shape types can be selected"));
572
d->ui.checkPlanar->setEnabled(false);
573
d->ui.checkFaces->setEnabled(false);
574
d->ui.checkRefine->setEnabled(true);
578
bool ShapeBuilderWidget::accept()
583
bool ShapeBuilderWidget::reject()
588
void ShapeBuilderWidget::changeEvent(QEvent *e)
590
QWidget::changeEvent(e);
591
if (e->type() == QEvent::LanguageChange) {
592
d->ui.retranslateUi(this);
597
/* TRANSLATOR PartGui::TaskShapeBuilder */
599
TaskShapeBuilder::TaskShapeBuilder()
601
widget = new ShapeBuilderWidget();
602
addTaskBox(Gui::BitmapFactory().pixmap("Part_Shapebuilder"), widget);
605
TaskShapeBuilder::~TaskShapeBuilder() = default;
607
void TaskShapeBuilder::open()
611
void TaskShapeBuilder::clicked(int)
615
bool TaskShapeBuilder::accept()
617
return widget->accept();
620
bool TaskShapeBuilder::reject()
622
return widget->reject();
625
#include "moc_TaskShapeBuilder.cpp"