FreeCAD

Форк
0
/
DlgFilletEdges.cpp 
1157 строк · 43.2 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
# include <algorithm>
26
# include <climits>
27
# include <sstream>
28
# include <QHeaderView>
29
# include <QItemDelegate>
30
# include <QItemSelectionModel>
31
# include <QLocale>
32
# include <QMessageBox>
33
# include <QTimer>
34
# include <QVBoxLayout>
35

36
# include <BRep_Tool.hxx>
37
# include <TopoDS.hxx>
38
# include <TopoDS_Edge.hxx>
39
# include <TopoDS_Shape.hxx>
40
# include <TopExp.hxx>
41
# include <TopTools_ListOfShape.hxx>
42
# include <TopTools_IndexedDataMapOfShapeListOfShape.hxx>
43
# include <TopTools_IndexedMapOfShape.hxx>
44

45
# include <Inventor/actions/SoSearchAction.h>
46
# include <Inventor/details/SoLineDetail.h>
47
#endif
48

49
#include <App/Application.h>
50
#include <App/Document.h>
51
#include <App/DocumentObject.h>
52
#include <Base/UnitsApi.h>
53
#include <Gui/Application.h>
54
#include <Gui/BitmapFactory.h>
55
#include <Gui/Command.h>
56
#include <Gui/QuantitySpinBox.h>
57
#include <Gui/Selection.h>
58
#include <Gui/SelectionFilter.h>
59
#include <Gui/SelectionObject.h>
60
#include <Gui/SoFCUnifiedSelection.h>
61
#include <Gui/ViewProvider.h>
62
#include <Gui/WaitCursor.h>
63
#include <Gui/Window.h>
64
#include <Mod/Part/App/FeatureChamfer.h>
65
#include <Mod/Part/App/FeatureFillet.h>
66

67
#include "DlgFilletEdges.h"
68
#include "ui_DlgFilletEdges.h"
69
#include "SoBrepEdgeSet.h"
70
#include "SoBrepFaceSet.h"
71
#include "SoBrepPointSet.h"
72

73
FC_LOG_LEVEL_INIT("Part", true, true)
74

75
using namespace PartGui;
76
namespace sp = std::placeholders;
77

78
FilletRadiusDelegate::FilletRadiusDelegate(QObject *parent) : QItemDelegate(parent)
79
{
80
}
81

82
QWidget *FilletRadiusDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */,
83
                                            const QModelIndex & index) const
84
{
85
    if (index.column() < 1)
86
        return nullptr;
87

88
    Gui::QuantitySpinBox *editor = new Gui::QuantitySpinBox(parent);
89
    editor->setUnit(Base::Unit::Length);
90
    editor->setMinimum(0.0);
91
    editor->setMaximum(INT_MAX);
92
    editor->setSingleStep(0.1);
93

94
    return editor;
95
}
96

97
void FilletRadiusDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
98
{
99
    Base::Quantity value = index.model()->data(index, Qt::EditRole).value<Base::Quantity>();
100

101
    Gui::QuantitySpinBox *spinBox = static_cast<Gui::QuantitySpinBox*>(editor);
102
    spinBox->setValue(value);
103
}
104

105
void FilletRadiusDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
106
                                        const QModelIndex &index) const
107
{
108
    Gui::QuantitySpinBox *spinBox = static_cast<Gui::QuantitySpinBox*>(editor);
109
    spinBox->interpretText();
110
    //double value = spinBox->value();
111
    //QString value = QString::fromLatin1("%1").arg(spinBox->value(),0,'f',2);
112
    //QString value = QLocale().toString(spinBox->value().getValue(),'f',Base::UnitsApi::getDecimals());
113
    Base::Quantity value = spinBox->value();
114

115
    model->setData(index, QVariant::fromValue<Base::Quantity>(value), Qt::EditRole);
116
}
117

118
void FilletRadiusDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
119
                                                const QModelIndex &/* index */) const
120
{
121
    editor->setGeometry(option.rect);
122
}
123

124
// --------------------------------------------------------------
125

126
FilletRadiusModel::FilletRadiusModel(QObject * parent) : QStandardItemModel(parent)
127
{
128
}
129

130
void FilletRadiusModel::updateCheckStates()
131
{
132
    // See http://www.qtcentre.org/threads/18856-Checkboxes-in-Treeview-do-not-get-refreshed?s=b0fea2bfc66da1098413ae9f2a651a68&p=93201#post93201
133
    Q_EMIT layoutChanged();
134
}
135

136
Qt::ItemFlags FilletRadiusModel::flags (const QModelIndex & index) const
137
{
138
    Qt::ItemFlags fl = QStandardItemModel::flags(index);
139
    if (index.column() == 0)
140
        fl = fl | Qt::ItemIsUserCheckable;
141
    return fl;
142
}
143

144
bool FilletRadiusModel::setData (const QModelIndex & index, const QVariant & value, int role)
145
{
146
    bool ok = QStandardItemModel::setData(index, value, role);
147
    if (role == Qt::CheckStateRole) {
148
        Q_EMIT toggleCheckState(index);
149
    }
150
    return ok;
151
}
152

153
QVariant FilletRadiusModel::data(const QModelIndex& index, int role) const
154
{
155
    QVariant value = QStandardItemModel::data(index, role);
156
    if (role == Qt::DisplayRole && index.column() >= 1) {
157
        Base::Quantity q = value.value<Base::Quantity>();
158
        QString str = q.getUserString();
159
        return str;
160
    }
161
    return value;
162
}
163

164
// --------------------------------------------------------------
165

166
namespace PartGui {
167
    class EdgeFaceSelection : public Gui::SelectionFilterGate
168
    {
169
        bool allowEdge{true};
170
        App::DocumentObject*& object;
171
    public:
172
        explicit EdgeFaceSelection(App::DocumentObject*& obj)
173
            : Gui::SelectionFilterGate(nullPointer())
174
            , object(obj)
175
        {
176
        }
177
        void selectEdges()
178
        {
179
            allowEdge = true;
180
        }
181
        void selectFaces()
182
        {
183
            allowEdge = false;
184
        }
185
        bool allow(App::Document* /*pDoc*/, App::DocumentObject*pObj, const char*sSubName) override
186
        {
187
            if (pObj != this->object)
188
                return false;
189
            if (!sSubName || sSubName[0] == '\0')
190
                return false;
191
            std::string element(sSubName);
192
            if (allowEdge)
193
                return element.substr(0,4) == "Edge";
194
            else
195
                return element.substr(0,4) == "Face";
196
        }
197
    };
198
    class DlgFilletEdges::Private
199
    {
200
    public:
201
        App::DocumentObject* object;
202
        EdgeFaceSelection* selection;
203
        Part::FilletBase* fillet;
204
        QTimer* highlighttimer;
205
        FilletType filletType;
206
        std::vector<int> edge_ids;
207
        TopTools_IndexedMapOfShape all_edges;
208
        TopTools_IndexedMapOfShape all_faces;
209
        using Connection = boost::signals2::connection;
210
        Connection connectApplicationDeletedObject;
211
        Connection connectApplicationDeletedDocument;
212

213
        class SelectionObjectCompare
214
        {
215
        public:
216
            App::DocumentObject* obj;
217
            explicit SelectionObjectCompare(App::DocumentObject* obj) : obj(obj)
218
            {
219
            }
220
            bool operator()(const Gui::SelectionObject& sel) const
221
            {
222
                return (sel.getObject() == obj);
223
            }
224
        };
225
    };
226
}
227

228
/* TRANSLATOR PartGui::DlgFilletEdges */
229

230
DlgFilletEdges::DlgFilletEdges(FilletType type, Part::FilletBase* fillet, QWidget* parent, Qt::WindowFlags fl)
231
  : QWidget(parent, fl), ui(new Ui_DlgFilletEdges()), d(new Private())
232
{
233
    ui->setupUi(this);
234
    setupConnections();
235

236
    ui->filletStartRadius->setMaximum(INT_MAX);
237
    ui->filletStartRadius->setMinimum(0);
238
    ui->filletStartRadius->setUnit(Base::Unit::Length);
239

240
    ui->filletEndRadius->setMaximum(INT_MAX);
241
    ui->filletEndRadius->setMinimum(0);
242
    ui->filletEndRadius->setUnit(Base::Unit::Length);
243

244
    d->object = nullptr;
245
    d->selection = new EdgeFaceSelection(d->object);
246
    Gui::Selection().addSelectionGate(d->selection);
247

248
    d->fillet = fillet;
249
    //NOLINTBEGIN
250
    d->connectApplicationDeletedObject = App::GetApplication().signalDeletedObject
251
        .connect(std::bind(&DlgFilletEdges::onDeleteObject, this, sp::_1));
252
    d->connectApplicationDeletedDocument = App::GetApplication().signalDeleteDocument
253
        .connect(std::bind(&DlgFilletEdges::onDeleteDocument, this, sp::_1));
254
    //NOLINTEND
255
    // set tree view with three columns
256
    FilletRadiusModel* model = new FilletRadiusModel(this);
257
    connect(model, &FilletRadiusModel::toggleCheckState,
258
            this, &DlgFilletEdges::toggleCheckState);
259
    model->insertColumns(0,3);
260

261
    // timer for highlighting
262
    d->highlighttimer = new QTimer(this);
263
    d->highlighttimer->setSingleShot(true);
264
    connect(d->highlighttimer, &QTimer::timeout,
265
            this, &DlgFilletEdges::onHighlightEdges);
266

267
    d->filletType = type;
268
    if (d->filletType == DlgFilletEdges::CHAMFER) {
269
        ui->parameterName->setTitle(tr("Chamfer Parameters"));
270
        ui->labelfillet->setText(tr("Chamfer type"));
271
        ui->labelRadius->setText(tr("Length:"));
272
        ui->filletType->setItemText(0, tr("Equal distance"));
273
        ui->filletType->setItemText(1, tr("Two distances"));
274

275
        model->setHeaderData(0, Qt::Horizontal, tr("Edges to chamfer"), Qt::DisplayRole);
276
        model->setHeaderData(1, Qt::Horizontal, tr("Size"), Qt::DisplayRole);
277
        model->setHeaderData(2, Qt::Horizontal, tr("Size2"), Qt::DisplayRole);
278
    }
279
    else {
280
        ui->parameterName->setTitle(tr("Fillet Parameter"));
281
        ui->labelfillet->setText(tr("Fillet type"));
282
        model->setHeaderData(0, Qt::Horizontal, tr("Edges to fillet"), Qt::DisplayRole);
283
        model->setHeaderData(1, Qt::Horizontal, tr("Start radius"), Qt::DisplayRole);
284
        model->setHeaderData(2, Qt::Horizontal, tr("End radius"), Qt::DisplayRole);
285
    }
286
    ui->treeView->setRootIsDecorated(false);
287
    ui->treeView->setItemDelegate(new FilletRadiusDelegate(this));
288
    ui->treeView->setModel(model);
289

290
    QHeaderView* header = ui->treeView->header();
291
    header->setSectionResizeMode(0, QHeaderView::Stretch);
292
    header->setDefaultAlignment(Qt::AlignLeft);
293
    header->setSectionsMovable(false);
294
    onFilletTypeActivated(0);
295
    findShapes();
296
}
297

298
/*
299
 *  Destroys the object and frees any allocated resources
300
 */
301
DlgFilletEdges::~DlgFilletEdges()
302
{
303
    // no need to delete child widgets, Qt does it all for us
304
    d->connectApplicationDeletedDocument.disconnect();
305
    d->connectApplicationDeletedObject.disconnect();
306
    Gui::Selection().rmvSelectionGate();
307
}
308

309
void DlgFilletEdges::setupConnections()
310
{
311
    // clang-format off
312
    connect(ui->shapeObject, qOverload<int>(&QComboBox::activated),
313
            this, &DlgFilletEdges::onShapeObjectActivated);
314
    connect(ui->selectEdges, &QRadioButton::toggled,
315
            this, &DlgFilletEdges::onSelectEdgesToggled);
316
    connect(ui->selectFaces, &QRadioButton::toggled,
317
            this, &DlgFilletEdges::onSelectFacesToggled);
318
    connect(ui->selectAllButton, &QPushButton::clicked,
319
            this, &DlgFilletEdges::onSelectAllButtonClicked);
320
    connect(ui->selectNoneButton, &QPushButton::clicked,
321
            this, &DlgFilletEdges::onSelectNoneButtonClicked);
322
    connect(ui->filletType, qOverload<int>(&QComboBox::activated),
323
            this, &DlgFilletEdges::onFilletTypeActivated);
324
    connect(ui->filletStartRadius,
325
            qOverload<const Base::Quantity&>(&Gui::QuantitySpinBox::valueChanged),
326
            this, &DlgFilletEdges::onFilletStartRadiusValueChanged);
327
    connect(ui->filletEndRadius,
328
            qOverload<const Base::Quantity&>(&Gui::QuantitySpinBox::valueChanged),
329
            this, &DlgFilletEdges::onFilletEndRadiusValueChanged);
330
    // clang-format on
331
}
332

333
void DlgFilletEdges::onSelectionChanged(const Gui::SelectionChanges& msg)
334
{
335
    // no object selected in the combobox or no sub-element was selected
336
    if (!d->object || !msg.pSubName)
337
        return;
338
    if (msg.Type == Gui::SelectionChanges::AddSelection ||
339
        msg.Type == Gui::SelectionChanges::RmvSelection) {
340
        // when adding a sub-element to the selection check
341
        // whether this is the currently handled object
342
        App::Document* doc = d->object->getDocument();
343
        std::string docname = doc->getName();
344
        std::string objname = d->object->getNameInDocument();
345
        if (docname==msg.pDocName && objname==msg.pObjectName) {
346
            QString subelement = QString::fromLatin1(msg.pSubName);
347
            if (subelement.startsWith(QLatin1String("Edge"))) {
348
                onSelectEdge(subelement, msg.Type);
349
            }
350
            else if (subelement.startsWith(QLatin1String("Face"))) {
351
                d->selection->selectEdges();
352
                onSelectEdgesOfFace(subelement, msg.Type);
353
                d->selection->selectFaces();
354
            }
355
        }
356
    }
357

358
    if (msg.Type != Gui::SelectionChanges::SetPreselect &&
359
        msg.Type != Gui::SelectionChanges::RmvPreselect)
360
        d->highlighttimer->start(20);
361
}
362

363
void DlgFilletEdges::onHighlightEdges()
364
{
365
    Gui::ViewProvider* view = Gui::Application::Instance->getViewProvider(d->object);
366
    if (view) {
367
        // deselect all faces
368
        {
369
            SoSearchAction searchAction;
370
            searchAction.setType(PartGui::SoBrepFaceSet::getClassTypeId());
371
            searchAction.setInterest(SoSearchAction::FIRST);
372
            searchAction.apply(view->getRoot());
373
            SoPath* selectionPath = searchAction.getPath();
374
            if (selectionPath) {
375
                Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::None);
376
                action.apply(selectionPath);
377
            }
378
        }
379
        // deselect all points
380
        {
381
            SoSearchAction searchAction;
382
            searchAction.setType(PartGui::SoBrepPointSet::getClassTypeId());
383
            searchAction.setInterest(SoSearchAction::FIRST);
384
            searchAction.apply(view->getRoot());
385
            SoPath* selectionPath = searchAction.getPath();
386
            if (selectionPath) {
387
                Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::None);
388
                action.apply(selectionPath);
389
            }
390
        }
391
        // select the edges
392
        {
393
            SoSearchAction searchAction;
394
            searchAction.setType(PartGui::SoBrepEdgeSet::getClassTypeId());
395
            searchAction.setInterest(SoSearchAction::FIRST);
396
            searchAction.apply(view->getRoot());
397
            SoPath* selectionPath = searchAction.getPath();
398
            if (selectionPath) {
399
                ParameterGrp::handle hGrp = Gui::WindowParameter::getDefaultParameter()->GetGroup("View");
400
                SbColor selectionColor(0.1f, 0.8f, 0.1f);
401
                unsigned long selection = (unsigned long)(selectionColor.getPackedValue());
402
                selection = hGrp->GetUnsigned("SelectionColor", selection);
403
                float transparency;
404
                selectionColor.setPackedValue((uint32_t)selection, transparency);
405

406
                // clear the selection first
407
                Gui::SoSelectionElementAction clear(Gui::SoSelectionElementAction::None);
408
                clear.apply(selectionPath);
409

410
                Gui::SoSelectionElementAction action(Gui::SoSelectionElementAction::Append);
411
                action.setColor(selectionColor);
412
                action.apply(selectionPath);
413

414
                QAbstractItemModel* model = ui->treeView->model();
415
                SoLineDetail detail;
416
                action.setElement(&detail);
417
                for (int i=0; i<model->rowCount(); ++i) {
418
                    QVariant value = model->index(i,0).data(Qt::CheckStateRole);
419
                    Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
420

421
                    // is item checked
422
                    if (checkState & Qt::Checked) {
423
                        // the index value of the edge
424
                        int id = model->index(i,0).data(Qt::UserRole).toInt();
425
                        detail.setLineIndex(id-1);
426
                        action.apply(selectionPath);
427
                    }
428
                }
429
            }
430
        }
431
    }
432
}
433

434
void DlgFilletEdges::onSelectEdge(const QString& subelement, int type)
435
{
436
    Gui::SelectionChanges::MsgType msgType = Gui::SelectionChanges::MsgType(type);
437
    QAbstractItemModel* model = ui->treeView->model();
438
    for (int i=0; i<model->rowCount(); ++i) {
439
        int id = model->data(model->index(i,0), Qt::UserRole).toInt();
440
        QString name = QString::fromLatin1("Edge%1").arg(id);
441
        if (name == subelement) {
442
            // ok, check the selected sub-element
443
            Qt::CheckState checkState =
444
                (msgType == Gui::SelectionChanges::AddSelection
445
                ? Qt::Checked : Qt::Unchecked);
446
            QVariant value(static_cast<int>(checkState));
447
            QModelIndex index = model->index(i,0);
448
            model->setData(index, value, Qt::CheckStateRole);
449
            // select the item
450
            ui->treeView->selectionModel()->setCurrentIndex(index,QItemSelectionModel::NoUpdate);
451
            QItemSelection selection(index, model->index(i,1));
452
            ui->treeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
453
            ui->treeView->update();
454
            break;
455
        }
456
    }
457
}
458

459
void DlgFilletEdges::onSelectEdgesOfFace(const QString& subelement, int type)
460
{
461
    bool ok;
462
    int index = subelement.mid(4).toInt(&ok);
463
    if (ok) {
464
        try {
465
            const TopoDS_Shape& face = d->all_faces.FindKey(index);
466
            TopTools_IndexedMapOfShape mapOfEdges;
467
            TopExp::MapShapes(face, TopAbs_EDGE, mapOfEdges);
468

469
            for(int j = 1; j <= mapOfEdges.Extent(); ++j) {
470
                TopoDS_Edge edge = TopoDS::Edge(mapOfEdges.FindKey(j));
471
                int id = d->all_edges.FindIndex(edge);
472
                QString name = QString::fromLatin1("Edge%1").arg(id);
473
                onSelectEdge(name, type);
474
                Gui::SelectionChanges::MsgType msgType = Gui::SelectionChanges::MsgType(type);
475
                if (msgType == Gui::SelectionChanges::AddSelection) {
476
                    Gui::Selection().addSelection(d->object->getDocument()->getName(),
477
                        d->object->getNameInDocument(), (const char*)name.toLatin1());
478
                }
479
            }
480
        }
481
        catch (Standard_Failure&) {
482
        }
483
    }
484
}
485

486
void DlgFilletEdges::onDeleteObject(const App::DocumentObject& obj)
487
{
488
    if (d->fillet == &obj) {
489
        d->fillet = nullptr;
490
    }
491
    else if (d->fillet && d->fillet->Base.getValue() == &obj) {
492
        d->fillet = nullptr;
493
        d->object = nullptr;
494
        ui->shapeObject->setCurrentIndex(0);
495
        onShapeObjectActivated(0);
496
    }
497
    else if (d->object == &obj) {
498
        d->object = nullptr;
499
        ui->shapeObject->removeItem(ui->shapeObject->currentIndex());
500
        ui->shapeObject->setCurrentIndex(0);
501
        onShapeObjectActivated(0);
502
    }
503
    else {
504
        QString shape = QString::fromLatin1(obj.getNameInDocument());
505
        // start from the second item
506
        for (int i=1; i<ui->shapeObject->count(); i++) {
507
            if (ui->shapeObject->itemData(i).toString() == shape) {
508
                ui->shapeObject->removeItem(i);
509
                break;
510
            }
511
        }
512
    }
513
}
514

515
void DlgFilletEdges::onDeleteDocument(const App::Document& doc)
516
{
517
    if (d->object) {
518
        if (d->object->getDocument() == &doc) {
519
            ui->shapeObject->setCurrentIndex(0);
520
            onShapeObjectActivated(0);
521
            setEnabled(false);
522
        }
523
    }
524
    else if (App::GetApplication().getActiveDocument() == &doc) {
525
        ui->shapeObject->setCurrentIndex(0);
526
        onShapeObjectActivated(0);
527
        setEnabled(false);
528
    }
529
}
530

531
void DlgFilletEdges::toggleCheckState(const QModelIndex& index)
532
{
533
    if (!d->object)
534
        return;
535
    QVariant check = index.data(Qt::CheckStateRole);
536
    int id = index.data(Qt::UserRole).toInt();
537
    QString name = QString::fromLatin1("Edge%1").arg(id);
538
    Qt::CheckState checkState = static_cast<Qt::CheckState>(check.toInt());
539

540
    bool block = this->blockSelection(true);
541

542
    // is item checked
543
    if (checkState & Qt::Checked) {
544
        App::Document* doc = d->object->getDocument();
545
        Gui::Selection().addSelection(doc->getName(),
546
            d->object->getNameInDocument(),
547
            (const char*)name.toLatin1());
548
    }
549
    else {
550
        App::Document* doc = d->object->getDocument();
551
        Gui::Selection().rmvSelection(doc->getName(),
552
            d->object->getNameInDocument(),
553
            (const char*)name.toLatin1());
554
    }
555

556
    this->blockSelection(block);
557
}
558

559
void DlgFilletEdges::findShapes()
560
{
561
    App::Document* activeDoc = App::GetApplication().getActiveDocument();
562
    if (!activeDoc)
563
        return;
564

565
    std::vector<App::DocumentObject*> objs = activeDoc->getObjectsOfType
566
        (Part::Feature::getClassTypeId());
567
    int index = 1;
568
    int current_index = 0;
569
    for (std::vector<App::DocumentObject*>::iterator it = objs.begin(); it!=objs.end(); ++it, ++index) {
570
        ui->shapeObject->addItem(QString::fromUtf8((*it)->Label.getValue()));
571
        ui->shapeObject->setItemData(index, QString::fromLatin1((*it)->getNameInDocument()));
572
        if (current_index == 0) {
573
            if (Gui::Selection().isSelected(*it)) {
574
                current_index = index;
575
            }
576
        }
577
    }
578

579
    // if only one object is in the document then simply use that
580
    if (objs.size() == 1)
581
        current_index = 1;
582

583
    if (current_index > 0) {
584
        ui->shapeObject->setCurrentIndex(current_index);
585
        onShapeObjectActivated(current_index);
586
    }
587

588
    // if an existing fillet object is given start the edit mode
589
    if (d->fillet) {
590
        setupFillet(objs);
591
    }
592
}
593

594
void DlgFilletEdges::setupFillet(const std::vector<App::DocumentObject*>& objs)
595
{
596
    App::DocumentObject* base = d->fillet->Base.getValue();
597
    const std::vector<Part::FilletElement>& e = d->fillet->Edges.getValues();
598
    const auto &subs = d->fillet->EdgeLinks.getShadowSubs();
599
    if(subs.size()!=e.size()) {
600
        FC_ERR("edge link size mismatch");
601
        return;
602
    }
603
    std::set<std::string> subSet;
604
    for(auto &sub : subs)
605
        subSet.insert(sub.newName.empty()?sub.oldName:sub.newName);
606

607
    std::string tmp;
608
    std::vector<App::DocumentObject*>::const_iterator it = std::find(objs.begin(), objs.end(), base);
609
    if (it != objs.end()) {
610
        // toggle visibility
611
        Gui::ViewProvider* vp;
612
        vp = Gui::Application::Instance->getViewProvider(d->fillet);
613
        if (vp) vp->hide();
614
        vp = Gui::Application::Instance->getViewProvider(base);
615
        if (vp) vp->show();
616

617
        int current_index = (it - objs.begin()) + 1;
618
        ui->shapeObject->setCurrentIndex(current_index);
619
        onShapeObjectActivated(current_index);
620
        ui->shapeObject->setEnabled(false);
621

622
        double startRadius = 1;
623
        double endRadius = 1;
624
        bool twoRadii = false;
625

626
        std::vector<std::string> subElements;
627
        QStandardItemModel *model = qobject_cast<QStandardItemModel*>(ui->treeView->model());
628
        bool block = model->blockSignals(true); // do not call toggleCheckState
629
        auto baseShape = Part::Feature::getTopoShape(base);
630
        std::set<Part::FilletElement> elements;
631
        for(size_t i=0;i<e.size();++i) {
632
            auto &sub = subs[i];
633
            if(sub.newName.empty()) {
634
                int idx = 0;
635
                sscanf(sub.oldName.c_str(),"Edge%d",&idx);
636
                if(idx==0)
637
                    FC_WARN("missing element reference: " << sub.oldName);
638
                else
639
                    elements.insert(e[i]);
640
                continue;
641
            }
642
            auto &ref = sub.newName;
643
            Part::TopoShape edge;
644
            try {
645
                edge = baseShape.getSubShape(ref.c_str());
646
            }catch(...) {}
647
            if(!edge.isNull())  {
648
                elements.insert(e[i]);
649
                continue;
650
            }
651
            FC_WARN("missing element reference: " << base->getFullName() << "." << ref);
652

653
            for(auto &mapped : Part::Feature::getRelatedElements(base,ref.c_str())) {
654
                tmp.clear();
655
                if(!subSet.insert(mapped.index.appendToStringBuffer(tmp)).second
656
                    || !subSet.insert(mapped.name.toString(0)).second)
657
                    continue;
658
                FC_WARN("guess element reference: " << ref << " -> " << mapped.index);
659
                elements.emplace(mapped.index.getIndex(),e[i].radius1,e[i].radius2);
660
            }
661
        }
662

663
        for (const auto & et : e) {
664
            std::vector<int>::iterator it = std::find(d->edge_ids.begin(), d->edge_ids.end(), et.edgeid);
665
            if (it != d->edge_ids.end()) {
666
                int index = it - d->edge_ids.begin();
667
                model->setData(model->index(index, 0), Qt::Checked, Qt::CheckStateRole);
668
                //model->setData(model->index(index, 1), QVariant(QLocale().toString(et->radius1,'f',Base::UnitsApi::getDecimals())));
669
                //model->setData(model->index(index, 2), QVariant(QLocale().toString(et->radius2,'f',Base::UnitsApi::getDecimals())));
670
                model->setData(model->index(index, 1), QVariant::fromValue<Base::Quantity>(Base::Quantity(et.radius1, Base::Unit::Length)));
671
                model->setData(model->index(index, 2), QVariant::fromValue<Base::Quantity>(Base::Quantity(et.radius2, Base::Unit::Length)));
672

673
                startRadius = et.radius1;
674
                endRadius = et.radius2;
675
                if (startRadius != endRadius)
676
                    twoRadii = true;
677

678
                int id = model->index(index, 0).data(Qt::UserRole).toInt();
679
                std::stringstream str;
680
                str << "Edge" << id;
681
                subElements.push_back(str.str());
682
            }
683
        }
684
        model->blockSignals(block);
685

686
        // #0002273
687
        if (twoRadii) {
688
            ui->filletType->setCurrentIndex(1);
689
            onFilletTypeActivated(1);
690
        }
691

692
        // #0001746
693
        ui->filletStartRadius->blockSignals(true);
694
        ui->filletStartRadius->setValue(startRadius);
695
        ui->filletStartRadius->blockSignals(false);
696
        ui->filletEndRadius->blockSignals(true);
697
        ui->filletEndRadius->setValue(endRadius);
698
        ui->filletEndRadius->blockSignals(false);
699

700
        App::Document* doc = d->object->getDocument();
701
        // get current selection and their sub-elements
702
        //std::vector<Gui::SelectionObject> selObj = Gui::Selection().getSelectionEx(doc->getName());
703
        //std::vector<Gui::SelectionObject>::iterator selIt = std::find_if(selObj.begin(), selObj.end(),
704
        //    Private::SelectionObjectCompare(d->object));
705

706

707
         /*
708
          * Edit: the following check is no longer necessary, as Gui::Selection
709
          * will do the check
710
          *
711
        // If sub-objects are already selected then only add the un-selected parts.
712
        // This is important to avoid recursive calls of rmvSelection() which
713
        // invalidates the internal iterator (#0002200).
714
        if (selIt != selObj.end()) {
715
            std::vector<std::string> selElements = selIt->getSubNames();
716
            std::sort(selElements.begin(), selElements.end());
717
            std::sort(subElements.begin(), subElements.end());
718

719
            std::vector<std::string> complementary;
720
            std::back_insert_iterator<std::vector<std::string> > biit(complementary);
721
            std::set_difference(subElements.begin(), subElements.end(), selElements.begin(), selElements.end(), biit);
722
            subElements = complementary;
723
        }
724
        */
725

726
        Gui::Selection().clearSelection(doc->getName());
727

728
        if (!subElements.empty()) {
729
            Gui::Selection().addSelections(doc->getName(),
730
                d->object->getNameInDocument(),
731
                subElements);
732
        }
733
    }
734
}
735

736
/**
737
 * Sets the strings of the subwidgets using the current language.
738
 */
739
void DlgFilletEdges::changeEvent(QEvent *e)
740
{
741
    if (e->type() == QEvent::LanguageChange) {
742
        int index = ui->shapeObject->currentIndex();
743
        // only get the items from index 1 on since the first one will be added automatically
744
        int count = ui->shapeObject->count() - 1;
745
        QStringList text;
746
        QList<QVariant> data;
747
        for (int i=0; i<count; i++) {
748
            text << ui->shapeObject->itemText(i+1);
749
            data << ui->shapeObject->itemData(i+1);
750
        }
751

752
        ui->retranslateUi(this);
753
        for (int i=0; i<count; i++) {
754
            ui->shapeObject->addItem(text.at(i));
755
            ui->shapeObject->setItemData(i+1, data.at(i));
756
        }
757

758
        ui->shapeObject->setCurrentIndex(index);
759
        QStandardItemModel *model = qobject_cast<QStandardItemModel*>(ui->treeView->model());
760
        count = model->rowCount();
761
        for (int i=0; i<count; i++) {
762
            int id = model->data(model->index(i, 0), Qt::UserRole).toInt();
763
            model->setData(model->index(i, 0), QVariant(tr("Edge%1").arg(id)));
764
        }
765
    }
766
    else {
767
        QWidget::changeEvent(e);
768
    }
769
}
770

771
void DlgFilletEdges::onShapeObjectActivated(int itemPos)
772
{
773
    d->object = nullptr;
774
    QStandardItemModel *model = qobject_cast<QStandardItemModel*>(ui->treeView->model());
775
    model->removeRows(0, model->rowCount());
776

777
    QByteArray name = ui->shapeObject->itemData(itemPos).toByteArray();
778
    App::Document* doc = App::GetApplication().getActiveDocument();
779
    if (!doc)
780
        return;
781
    App::DocumentObject* part = doc->getObject((const char*)name);
782
    if (part && part->isDerivedFrom<Part::Feature>()) {
783
        d->object = part;
784
        TopoDS_Shape myShape = static_cast<Part::Feature*>(part)->Shape.getValue();
785

786
        d->all_edges.Clear();
787
        TopExp::MapShapes(myShape, TopAbs_EDGE, d->all_edges);
788

789
        d->all_faces.Clear();
790
        TopExp::MapShapes(myShape, TopAbs_FACE, d->all_faces);
791

792
        // build up map edge->face
793
        TopTools_IndexedDataMapOfShapeListOfShape edge2Face;
794
        TopExp::MapShapesAndAncestors(myShape, TopAbs_EDGE, TopAbs_FACE, edge2Face);
795
        TopTools_IndexedMapOfShape mapOfShape;
796
        TopExp::MapShapes(myShape, TopAbs_EDGE, mapOfShape);
797

798
        // populate the model
799
        d->edge_ids.clear();
800
        for (int i=1; i<= edge2Face.Extent(); ++i) {
801
            // set the index value as user data to use it in accept()
802
            const TopTools_ListOfShape& los = edge2Face.FindFromIndex(i);
803
            if (los.Extent() == 2) {
804
                // set the index value as user data to use it in accept()
805
                const TopoDS_Shape& edge = edge2Face.FindKey(i);
806
                // Now check also the continuity to only allow C0-continious
807
                // faces
808
                const TopoDS_Shape& face1 = los.First();
809
                const TopoDS_Shape& face2 = los.Last();
810
                GeomAbs_Shape cont = BRep_Tool::Continuity(TopoDS::Edge(edge),
811
                                                           TopoDS::Face(face1),
812
                                                           TopoDS::Face(face2));
813
                if (cont == GeomAbs_C0) {
814
                    int id = mapOfShape.FindIndex(edge);
815
                    d->edge_ids.push_back(id);
816
                }
817
            }
818
        }
819

820
        model->insertRows(0, d->edge_ids.size());
821
        int index = 0;
822
        for (int id : d->edge_ids) {
823
            model->setData(model->index(index, 0), QVariant(tr("Edge%1").arg(id)));
824
            model->setData(model->index(index, 0), QVariant(id), Qt::UserRole);
825
          //model->setData(model->index(index, 1), QVariant(QLocale().toString(1.0,'f',Base::UnitsApi::getDecimals())));
826
          //model->setData(model->index(index, 2), QVariant(QLocale().toString(1.0,'f',Base::UnitsApi::getDecimals())));
827
            model->setData(model->index(index, 1), QVariant::fromValue<Base::Quantity>(Base::Quantity(1.0,Base::Unit::Length)));
828
            model->setData(model->index(index, 2), QVariant::fromValue<Base::Quantity>(Base::Quantity(1.0,Base::Unit::Length)));
829
            std::stringstream element;
830
            element << "Edge" << id;
831
            if (Gui::Selection().isSelected(part, element.str().c_str()))
832
                model->setData(model->index(index, 0), Qt::Checked, Qt::CheckStateRole);
833
            else
834
                model->setData(model->index(index, 0), Qt::Unchecked, Qt::CheckStateRole);
835
            index++;
836
        }
837
    }
838
}
839

840
void DlgFilletEdges::onSelectEdgesToggled(bool on)
841
{
842
    if (on) d->selection->selectEdges();
843
}
844

845
void DlgFilletEdges::onSelectFacesToggled(bool on)
846
{
847
    if (on) d->selection->selectFaces();
848
}
849

850
void DlgFilletEdges::onSelectAllButtonClicked()
851
{
852
    std::vector<std::string> subElements;
853
    FilletRadiusModel* model = static_cast<FilletRadiusModel*>(ui->treeView->model());
854
    bool block = model->blockSignals(true); // do not call toggleCheckState
855
    for (int i=0; i<model->rowCount(); ++i) {
856
        QModelIndex index = model->index(i,0);
857

858
        // is not yet checked?
859
        QVariant check = index.data(Qt::CheckStateRole);
860
        Qt::CheckState state = static_cast<Qt::CheckState>(check.toInt());
861
        if (state == Qt::Unchecked) {
862
            int id = index.data(Qt::UserRole).toInt();
863
            std::stringstream str;
864
            str << "Edge" << id;
865
            subElements.push_back(str.str());
866
        }
867

868
        Qt::CheckState checkState = Qt::Checked;
869
        QVariant value(static_cast<int>(checkState));
870
        model->setData(index, value, Qt::CheckStateRole);
871
    }
872
    model->blockSignals(block);
873
    model->updateCheckStates();
874

875
    if (d->object) {
876
        App::Document* doc = d->object->getDocument();
877
        Gui::Selection().addSelections(doc->getName(),
878
            d->object->getNameInDocument(),
879
            subElements);
880
    }
881
}
882

883
void DlgFilletEdges::onSelectNoneButtonClicked()
884
{
885
    FilletRadiusModel* model = static_cast<FilletRadiusModel*>(ui->treeView->model());
886
    bool block = model->blockSignals(true); // do not call toggleCheckState
887
    for (int i=0; i<model->rowCount(); ++i) {
888
        Qt::CheckState checkState = Qt::Unchecked;
889
        QVariant value(static_cast<int>(checkState));
890
        model->setData(model->index(i,0), value, Qt::CheckStateRole);
891
    }
892
    model->blockSignals(block);
893
    model->updateCheckStates();
894

895
    if (d->object) {
896
        App::Document* doc = d->object->getDocument();
897
        Gui::Selection().clearSelection(doc->getName());
898
    }
899
}
900

901
void DlgFilletEdges::onFilletTypeActivated(int index)
902
{
903
    QStandardItemModel *model = qobject_cast<QStandardItemModel*>(ui->treeView->model());
904
    if (index == 0) {
905
        if (d->filletType == DlgFilletEdges::CHAMFER)
906
            model->setHeaderData(1, Qt::Horizontal, tr("Length"), Qt::DisplayRole);
907
        else
908
            model->setHeaderData(1, Qt::Horizontal, tr("Radius"), Qt::DisplayRole);
909
        ui->treeView->hideColumn(2);
910
        ui->filletEndRadius->hide();
911
    }
912
    else {
913
        if (d->filletType == DlgFilletEdges::CHAMFER)
914
            model->setHeaderData(1, Qt::Horizontal, tr("Start length"), Qt::DisplayRole);
915
        else
916
            model->setHeaderData(1, Qt::Horizontal, tr("Start radius"), Qt::DisplayRole);
917
        ui->treeView->showColumn(2);
918
        ui->filletEndRadius->show();
919
    }
920

921
    ui->treeView->resizeColumnToContents(0);
922
    ui->treeView->resizeColumnToContents(1);
923
    ui->treeView->resizeColumnToContents(2);
924
}
925

926
void DlgFilletEdges::onFilletStartRadiusValueChanged(const Base::Quantity& radius)
927
{
928
    QAbstractItemModel* model = ui->treeView->model();
929
    for (int i=0; i<model->rowCount(); ++i) {
930
        QVariant value = model->index(i,0).data(Qt::CheckStateRole);
931
        Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
932

933
        // is item checked
934
        if (checkState & Qt::Checked) {
935
            model->setData(model->index(i, 1), QVariant::fromValue<Base::Quantity>(radius));
936
        }
937
    }
938
}
939

940
void DlgFilletEdges::onFilletEndRadiusValueChanged(const Base::Quantity& radius)
941
{
942
    QAbstractItemModel* model = ui->treeView->model();
943
    for (int i=0; i<model->rowCount(); ++i) {
944
        QVariant value = model->index(i,0).data(Qt::CheckStateRole);
945
        Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
946

947
        // is item checked
948
        if (checkState & Qt::Checked) {
949
            model->setData(model->index(i, 2), QVariant::fromValue<Base::Quantity>(radius));
950
        }
951
    }
952
}
953

954
const char* DlgFilletEdges::getFilletType() const
955
{
956
    return "Fillet";
957
}
958

959
bool DlgFilletEdges::accept()
960
{
961
    if (!d->object) {
962
        QMessageBox::warning(this, tr("No shape selected"),
963
            tr("No valid shape is selected.\n"
964
               "Please select a valid shape in the drop-down box first."));
965
        return false;
966
    }
967
    App::Document* activeDoc = App::GetApplication().getActiveDocument();
968
    QAbstractItemModel* model = ui->treeView->model();
969
    bool end_radius = !ui->treeView->isColumnHidden(2);
970
    bool todo = false;
971

972
    QString shape, type, name;
973
    std::string fillet = getFilletType();
974
    int index = ui->shapeObject->currentIndex();
975
    shape = ui->shapeObject->itemData(index).toString();
976
    type = QString::fromLatin1("Part::%1").arg(QString::fromLatin1(fillet.c_str()));
977

978
    if (d->fillet)
979
        name = QString::fromLatin1(d->fillet->getNameInDocument());
980
    else
981
        name = QString::fromLatin1(activeDoc->getUniqueObjectName(fillet.c_str()).c_str());
982

983
    activeDoc->openTransaction(fillet.c_str());
984
    QString code;
985
    if (!d->fillet) {
986
        code = QString::fromLatin1(
987
        "FreeCAD.ActiveDocument.addObject(\"%1\",\"%2\")\n"
988
        "FreeCAD.ActiveDocument.%2.Base = FreeCAD.ActiveDocument.%3\n")
989
        .arg(type, name, shape);
990
    }
991
    code += QString::fromLatin1("__fillets__ = []\n");
992
    for (int i=0; i<model->rowCount(); ++i) {
993
        QVariant value = model->index(i,0).data(Qt::CheckStateRole);
994
        Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());
995

996
        // is item checked
997
        if (checkState & Qt::Checked) {
998
            // the index value of the edge
999
            int id = model->index(i,0).data(Qt::UserRole).toInt();
1000
            Base::Quantity r1 = model->index(i,1).data(Qt::EditRole).value<Base::Quantity>();
1001
            Base::Quantity r2 = r1;
1002
            if (end_radius)
1003
                r2 = model->index(i,2).data(Qt::EditRole).value<Base::Quantity>();
1004
            code += QString::fromLatin1(
1005
                "__fillets__.append((%1,%2,%3))\n")
1006
                .arg(id)
1007
                .arg(r1.getValue(),0,'f',Base::UnitsApi::getDecimals())
1008
                .arg(r2.getValue(),0,'f',Base::UnitsApi::getDecimals());
1009
            todo = true;
1010
        }
1011
    }
1012

1013
    if (!todo) {
1014
        QMessageBox::warning(this, tr("No edge selected"),
1015
            tr("No edge entity is checked to fillet.\n"
1016
               "Please check one or more edge entities first."));
1017
        return false;
1018
    }
1019

1020
    Gui::WaitCursor wc;
1021
    code += QString::fromLatin1(
1022
        "FreeCAD.ActiveDocument.%1.Edges = __fillets__\n"
1023
        "del __fillets__\n"
1024
        "FreeCADGui.ActiveDocument.%2.Visibility = False\n")
1025
        .arg(name, shape);
1026
    Gui::Command::runCommand(Gui::Command::App, code.toLatin1());
1027
    activeDoc->commitTransaction();
1028
    activeDoc->recompute();
1029
    if (d->fillet) {
1030
        Gui::ViewProvider* vp;
1031
        vp = Gui::Application::Instance->getViewProvider(d->fillet);
1032
        if (vp) vp->show();
1033
    }
1034

1035
    QByteArray to = name.toLatin1();
1036
    QByteArray from = shape.toLatin1();
1037
    Gui::Command::copyVisual(to, "LineColor", from);
1038
    Gui::Command::copyVisual(to, "PointColor", from);
1039
    return true;
1040
}
1041

1042
// ---------------------------------------
1043

1044
FilletEdgesDialog::FilletEdgesDialog(DlgFilletEdges::FilletType type, Part::FilletBase* fillet, QWidget* parent, Qt::WindowFlags fl)
1045
  : QDialog(parent, fl)
1046
{
1047
    widget = new DlgFilletEdges(type, fillet, this);
1048
    this->setWindowTitle(widget->windowTitle());
1049

1050
    QVBoxLayout* hboxLayout = new QVBoxLayout(this);
1051
    QDialogButtonBox* buttonBox = new QDialogButtonBox(this);
1052
    buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
1053

1054
    QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &FilletEdgesDialog::accept);
1055
    QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &FilletEdgesDialog::reject);
1056

1057
    hboxLayout->addWidget(widget);
1058
    hboxLayout->addWidget(buttonBox);
1059
}
1060

1061
FilletEdgesDialog::~FilletEdgesDialog() = default;
1062

1063
void FilletEdgesDialog::accept()
1064
{
1065
    if (widget->accept())
1066
        QDialog::accept();
1067
}
1068

1069
// ---------------------------------------
1070

1071
TaskFilletEdges::TaskFilletEdges(Part::Fillet* fillet)
1072
{
1073
    widget = new DlgFilletEdges(DlgFilletEdges::FILLET, fillet);
1074
    addTaskBox(Gui::BitmapFactory().pixmap("Part_Fillet"), widget);
1075
}
1076

1077
TaskFilletEdges::~TaskFilletEdges()
1078
{
1079
    // automatically deleted in the sub-class
1080
}
1081

1082
void TaskFilletEdges::open()
1083
{
1084
}
1085

1086
void TaskFilletEdges::clicked(int)
1087
{
1088
}
1089

1090
bool TaskFilletEdges::accept()
1091
{
1092
    bool ok = widget->accept();
1093
    if (ok)
1094
        Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()");
1095
    return ok;
1096
}
1097

1098
bool TaskFilletEdges::reject()
1099
{
1100
    Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()");
1101
    return true;
1102
}
1103

1104
// --------------------------------------------------------------
1105

1106
/* TRANSLATOR PartGui::DlgChamferEdges */
1107

1108
DlgChamferEdges::DlgChamferEdges(Part::FilletBase* chamfer, QWidget* parent, Qt::WindowFlags fl)
1109
  : DlgFilletEdges(DlgFilletEdges::CHAMFER, chamfer, parent, fl)
1110
{
1111
    this->setWindowTitle(tr("Chamfer Edges"));
1112
}
1113

1114
/*
1115
 *  Destroys the object and frees any allocated resources
1116
 */
1117
DlgChamferEdges::~DlgChamferEdges() = default;
1118

1119
const char* DlgChamferEdges::getFilletType() const
1120
{
1121
    return "Chamfer";
1122
}
1123

1124
TaskChamferEdges::TaskChamferEdges(Part::Chamfer* chamfer)
1125
{
1126
    widget = new DlgChamferEdges(chamfer);
1127
    addTaskBox(Gui::BitmapFactory().pixmap("Part_Chamfer"), widget);
1128
}
1129

1130
TaskChamferEdges::~TaskChamferEdges()
1131
{
1132
    // automatically deleted in the sub-class
1133
}
1134

1135
void TaskChamferEdges::open()
1136
{
1137
}
1138

1139
void TaskChamferEdges::clicked(int)
1140
{
1141
}
1142

1143
bool TaskChamferEdges::accept()
1144
{
1145
    bool ok = widget->accept();
1146
    if (ok)
1147
        Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()");
1148
    return ok;
1149
}
1150

1151
bool TaskChamferEdges::reject()
1152
{
1153
    Gui::Command::doCommand(Gui::Command::Gui,"Gui.activeDocument().resetEdit()");
1154
    return true;
1155
}
1156

1157
#include "moc_DlgFilletEdges.cpp"
1158

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.