FreeCAD

Форк
0
/
ViewProviderMeasureDistance.cpp 
541 строка · 20.4 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2023 David Friedli <david[at]friedli-be.ch>             *
3
 *   Copyright (c) 2013 Thomas Anderson <blobfish[at]gmx.com>              *
4
 *   Copyright (c) 2008 Werner Mayer <wmayer[at]users.sourceforge.net>     *
5
 *                                                                         *
6
 *   This file is part of FreeCAD.                                         *
7
 *                                                                         *
8
 *   FreeCAD is free software: you can redistribute it and/or modify it    *
9
 *   under the terms of the GNU Lesser General Public License as           *
10
 *   published by the Free Software Foundation, either version 2.1 of the  *
11
 *   License, or (at your option) any later version.                       *
12
 *                                                                         *
13
 *   FreeCAD is distributed in the hope that it will be useful, but        *
14
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
15
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
16
 *   Lesser General Public License for more details.                       *
17
 *                                                                         *
18
 *   You should have received a copy of the GNU Lesser General Public      *
19
 *   License along with FreeCAD. If not, see                               *
20
 *   <https://www.gnu.org/licenses/>.                                      *
21
 *                                                                         *
22
 **************************************************************************/
23

24
#include "PreCompiled.h"
25

26
#ifndef _PreComp_
27
#include <sstream>
28
#include <QApplication>
29
#include <Inventor/engines/SoCalculator.h>
30
#include <Inventor/engines/SoConcatenate.h>
31
#include <Inventor/engines/SoComposeRotation.h>
32
#include <Inventor/engines/SoComposeRotationFromTo.h>
33
#include <Inventor/engines/SoComposeVec3f.h>
34
#include <Inventor/engines/SoDecomposeVec3f.h>
35
#include <Inventor/nodes/SoAnnotation.h>
36
#include <Inventor/nodes/SoBaseColor.h>
37
#include <Inventor/nodes/SoCoordinate3.h>
38
#include <Inventor/nodes/SoDrawStyle.h>
39
#include <Inventor/nodes/SoFontStyle.h>
40
#include <Inventor/nodes/SoIndexedLineSet.h>
41
#include <Inventor/nodes/SoMarkerSet.h>
42
#include <Inventor/nodes/SoPickStyle.h>
43
#include <Inventor/nodes/SoText2.h>
44
#include <Inventor/nodes/SoTranslation.h>
45
#include <Inventor/nodes/SoMaterial.h>
46
#include <Inventor/nodes/SoSwitch.h>
47
#include <Inventor/nodes/SoCone.h>
48
#include <Inventor/nodes/SoResetTransform.h>
49
#include <Inventor/nodes/SoNodes.h>
50
#endif
51

52
#include <Gui/Inventor/MarkerBitmaps.h>
53

54
#include <App/Document.h>
55
#include <Base/BaseClass.h>
56
#include <Base/Console.h>
57
#include <Base/Quantity.h>
58
#include <Mod/Measure/App/Preferences.h>
59

60
#include "ViewProviderMeasureDistance.h"
61
#include "Gui/Application.h"
62
#include <Gui/Command.h>
63
#include "Gui/Document.h"
64
#include "Gui/ViewParams.h"
65

66

67
using namespace Gui;
68
using namespace MeasureGui;
69
using namespace Measure;
70

71
PROPERTY_SOURCE(MeasureGui::ViewProviderMeasureDistance, MeasureGui::ViewProviderMeasureBase)
72

73
SO_KIT_SOURCE(MeasureGui::DimensionLinear)
74

75
void MeasureGui::DimensionLinear::initClass()
76
{
77
    SO_KIT_INIT_CLASS(DimensionLinear, SoSeparatorKit, "SeparatorKit");
78
}
79

80
MeasureGui::DimensionLinear::DimensionLinear()
81
{
82
    SO_KIT_CONSTRUCTOR(MeasureGui::DimensionLinear);
83

84
    SO_KIT_ADD_CATALOG_ENTRY(transformation, SoTransform, true, topSeparator, "", true);
85
    SO_KIT_ADD_CATALOG_ENTRY(annotate, SoAnnotation, true, topSeparator, "", true);
86
    SO_KIT_ADD_CATALOG_ENTRY(leftArrow, SoShapeKit, true, topSeparator, "", true);
87
    SO_KIT_ADD_CATALOG_ENTRY(rightArrow, SoShapeKit, true, topSeparator, "", true);
88
    SO_KIT_ADD_CATALOG_ENTRY(line, SoShapeKit, true, annotate, "", true);
89
    SO_KIT_ADD_CATALOG_ENTRY(textSep, SoSeparator, true, annotate, "", true);
90

91
    SO_KIT_INIT_INSTANCE();
92

93
    SO_NODE_ADD_FIELD(rotate, (1.0, 0.0, 0.0, 0.0));  // position orientation of the dimension.
94
    SO_NODE_ADD_FIELD(length, (1.0));                 // turns into dimension length
95
    SO_NODE_ADD_FIELD(origin, (0.0, 0.0, 0.0));       // static
96
    SO_NODE_ADD_FIELD(text, ("test"));                // dimension text
97
    SO_NODE_ADD_FIELD(dColor, (1.0, 0.0, 0.0));       // dimension color.
98
    SO_NODE_ADD_FIELD(backgroundColor, (1.0, 1.0, 1.0));
99
    SO_NODE_ADD_FIELD(showArrows, (false));  // display dimension arrows
100
    SO_NODE_ADD_FIELD(fontSize, (12.0));     // size of the dimension font
101
}
102

103
MeasureGui::DimensionLinear::~DimensionLinear()
104
{}
105

106
SbBool MeasureGui::DimensionLinear::affectsState() const
107
{
108
    return false;
109
}
110

111
void MeasureGui::DimensionLinear::setupDimension()
112
{
113
    // make unpickable
114
    SoPickStyle* ps = static_cast<SoPickStyle*>(getPart("pickStyle", true));
115
    if (ps) {
116
        ps->style = SoPickStyle::UNPICKABLE;
117
    }
118

119
    // transformation
120
    SoTransform* trans = static_cast<SoTransform*>(getPart("transformation", true));
121
    trans->translation.connectFrom(&point1);
122
    // build engine for vector subtraction and length.
123
    SoCalculator* hyp = new SoCalculator();
124
    hyp->A.connectFrom(&point1);
125
    hyp->B.connectFrom(&point2);
126
    hyp->expression.set1Value(0, "oA = B-A");
127
    hyp->expression.set1Value(1, "oB = normalize(oA)");
128
    hyp->expression.set1Value(2, "oa = length(oA)");
129
    length.connectFrom(&hyp->oa);
130

131
    // build engine for rotation.
132
    SoComposeRotationFromTo* rotationEngine = new SoComposeRotationFromTo();
133
    rotationEngine->from.setValue(SbVec3f(1.0, 0.0, 0.0));
134
    rotationEngine->to.connectFrom(&hyp->oB);
135
    trans->rotation.connectFrom(&rotationEngine->rotation);
136

137
    // color
138
    SoMaterial* material = new SoMaterial;
139
    material->diffuseColor.connectFrom(&dColor);
140

141
    // dimension arrows
142
    float dimLength = (point2.getValue() - point1.getValue()).length();
143
    float coneHeight = dimLength * 0.06;
144
    float coneRadius = coneHeight * 0.5;
145

146
    SoComposeVec3f* vec = new SoComposeVec3f;
147
    vec->x.connectFrom(&length);
148
    vec->y.setValue(0.0);
149
    vec->z.setValue(0.0);
150

151
    // NOTE: showArrows is only respected at setup stage and cannot be changed later
152
    if (showArrows.getValue()) {
153
        SoCone* cone = new SoCone();
154
        cone->bottomRadius.setValue(coneRadius);
155
        cone->height.setValue(coneHeight);
156

157
        char lStr[100];
158
        char rStr[100];
159
        snprintf(lStr, sizeof(lStr), "translation %.6f 0.0 0.0", coneHeight * 0.5);
160
        snprintf(rStr, sizeof(rStr), "translation 0.0 -%.6f 0.0", coneHeight * 0.5);
161

162
        setPart("leftArrow.shape", cone);
163
        set("leftArrow.transform", "rotation 0.0 0.0 1.0 1.5707963");
164
        set("leftArrow.transform", lStr);
165
        setPart("rightArrow.shape", cone);
166
        set("rightArrow.transform", "rotation 0.0 0.0 -1.0 1.5707963");  // no constant for PI.
167
        // have use local here to do the offset because the main is wired up to length of dimension.
168
        set("rightArrow.localTransform", rStr);
169

170
        SoTransform* transform = static_cast<SoTransform*>(getPart("rightArrow.transform", false));
171
        if (!transform) {
172
            return;  // what to do here?
173
        }
174
        transform->translation.connectFrom(&vec->vector);
175

176
        setPart("leftArrow.material", material);
177
        setPart("rightArrow.material", material);
178
    }
179

180
    // line
181
    SoConcatenate* catEngine = new SoConcatenate(SoMFVec3f::getClassTypeId());
182
    // don't know how to get around having this dummy origin. cat engine wants to connectfrom?
183
    catEngine->input[0]->connectFrom(&origin);
184
    catEngine->input[1]->connectFrom(&vec->vector);
185

186
    SoVertexProperty* lineVerts = new SoVertexProperty;
187
    lineVerts->vertex.connectFrom(catEngine->output);
188

189
    int lineVertexMap[] = {0, 1};
190
    int lineVertexMapSize(sizeof(lineVertexMap) / sizeof(int));
191
    SoIndexedLineSet* line = new SoIndexedLineSet;
192
    line->vertexProperty = lineVerts;
193
    line->coordIndex.setValues(0, lineVertexMapSize, lineVertexMap);
194

195
    setPart("line.shape", line);
196
    setPart("line.material", material);
197

198
    // text
199
    SoSeparator* textSep = static_cast<SoSeparator*>(getPart("textSep", true));
200
    if (!textSep) {
201
        return;
202
    }
203

204
    textSep->addChild(material);
205

206
    SoCalculator* textVecCalc = new SoCalculator();
207
    textVecCalc->A.connectFrom(&vec->vector);
208
    textVecCalc->B.set1Value(0, 0.0, 0.250, 0.0);
209
    textVecCalc->expression.set1Value(0, "oA = (A / 2) + B");
210

211
    SoTransform* textTransform = new SoTransform();
212
    textTransform->translation.connectFrom(&textVecCalc->oA);
213
    textSep->addChild(textTransform);
214

215
    SoFont* fontNode = new SoFont();
216
    fontNode->name.setValue("Helvetica : Bold");
217
    fontNode->size.connectFrom(&fontSize);
218
    textSep->addChild(fontNode);
219

220
    auto textNode = new SoFrameLabel();
221
    textNode->justification = SoText2::CENTER;
222
    textNode->string.connectFrom(&text);
223
    textNode->textColor.connectFrom(&dColor);
224
    textNode->backgroundColor.connectFrom(&backgroundColor);
225
    textSep->addChild(textNode);
226

227
    // this prevents the 2d text from screwing up the bounding box for a viewall
228
    SoResetTransform* rTrans = new SoResetTransform;
229
    rTrans->whatToReset = SoResetTransform::BBOX;
230
    textSep->addChild(rTrans);
231
}
232

233

234
SbMatrix ViewProviderMeasureDistance::getMatrix()
235
{
236
    if (!pcObject) {
237
        return {};
238
    }
239

240
    auto prop1 =
241
        Base::freecad_dynamic_cast<App::PropertyVector>(pcObject->getPropertyByName("Position1"));
242
    auto prop2 =
243
        Base::freecad_dynamic_cast<App::PropertyVector>(pcObject->getPropertyByName("Position2"));
244

245
    if (!prop1 || !prop2) {
246
        return {};
247
    }
248

249
    auto vec1 = prop1->getValue();
250
    auto vec2 = prop2->getValue();
251

252
    const double tolerance(10.0e-6);
253
    SbVec3f origin = toSbVec3f((vec2 + vec1) / 2);
254
    Base::Vector3d localXAxis = (vec2 - vec1).Normalize();
255
    Base::Vector3d localYAxis = getTextDirection(localXAxis, tolerance).Normalize();
256

257
    // X and Y axis have to be 90° to eachother
258
    assert(fabs(localYAxis.Dot(localXAxis)) < tolerance);
259
    Base::Vector3d localZAxis = localYAxis.Cross(localXAxis).Normalize();
260

261
    SbMatrix matrix = SbMatrix(localXAxis.x,
262
                               localXAxis.y,
263
                               localXAxis.z,
264
                               0,
265
                               localYAxis.x,
266
                               localYAxis.y,
267
                               localYAxis.z,
268
                               0,
269
                               localZAxis.x,
270
                               localZAxis.y,
271
                               localZAxis.z,
272
                               0,
273
                               // 0,0,0,1
274
                               origin[0],
275
                               origin[1],
276
                               origin[2],
277
                               1);
278

279
    return matrix;
280
}
281

282

283
//! calculate a good direction from the elements being measured to the annotation text based on the
284
//! layout of the elements and its relationship with the cardinal axes and the view direction.
285
//! elementDirection is expected to be a normalized vector. an example of an elementDirection would
286
//! be the vector from the start of a line to the end.
287
Base::Vector3d ViewProviderMeasureDistance::getTextDirection(Base::Vector3d elementDirection,
288
                                                             double tolerance)
289
{
290
    const Base::Vector3d stdX(1.0, 0.0, 0.0);
291
    const Base::Vector3d stdY(0.0, 1.0, 0.0);
292
    const Base::Vector3d stdZ(0.0, 0.0, 1.0);
293

294
    Base::Vector3d textDirection = elementDirection.Cross(stdX);
295
    if (textDirection.Length() < tolerance) {
296
        textDirection = elementDirection.Cross(stdY);
297
    }
298
    if (textDirection.Length() < tolerance) {
299
        textDirection = elementDirection.Cross(stdZ);
300
    }
301
    textDirection.Normalize();
302
    if (textDirection.Dot(stdZ) < 0.0) {
303
        textDirection = textDirection * -1.0;
304
    }
305

306
    return textDirection.Normalize();
307
}
308

309

310
ViewProviderMeasureDistance::ViewProviderMeasureDistance()
311
{
312
    sPixmap = "Measurement-Distance";
313

314
    ADD_PROPERTY_TYPE(ShowDelta,
315
                      (false),
316
                      "Appearance",
317
                      App::Prop_None,
318
                      "Display the X, Y and Z components of the distance");
319

320
    // vert indexes used to create the annotation lines
321
    const size_t lineCount(3);
322
    static const int32_t lines[lineCount] = {
323
        2,
324
        3,
325
        -1  // dimension line
326
    };
327

328
    const size_t lineCountSecondary(9);
329
    static const int32_t linesSecondary[lineCountSecondary] = {
330
        0,
331
        2,
332
        -1,  // extension line 1
333
        1,
334
        3,
335
        -1,  // extension line 2
336
        2,
337
        4,
338
        -1  // label helper line
339
    };
340

341
    // Line Coordinates
342
    // 0-1 points on shape (dimension points)
343
    // 2-3 ends of extension lines/dimension line
344
    // 4 label position
345
    pCoords = new SoCoordinate3();
346
    pCoords->ref();
347

348
    auto engineCoords = new SoCalculator();
349
    engineCoords->a.connectFrom(&fieldDistance);
350
    engineCoords->A.connectFrom(&pLabelTranslation->translation);
351
    engineCoords->expression.setValue("ta=a/2; tb=A[1]; oA=vec3f(ta, 0, 0); oB=vec3f(-ta, 0, 0); "
352
                                      "oC=vec3f(ta, tb, 0); oD=vec3f(-ta, tb, 0)");
353

354
    auto engineCat = new SoConcatenate(SoMFVec3f::getClassTypeId());
355
    engineCat->input[0]->connectFrom(&engineCoords->oA);
356
    engineCat->input[1]->connectFrom(&engineCoords->oB);
357
    engineCat->input[2]->connectFrom(&engineCoords->oC);
358
    engineCat->input[3]->connectFrom(&engineCoords->oD);
359
    engineCat->input[4]->connectFrom(&pLabelTranslation->translation);
360

361
    pCoords->point.connectFrom(engineCat->output);
362
    pCoords->point.setNum(engineCat->output->getNumConnections());
363

364
    pLines = new SoIndexedLineSet();
365
    pLines->ref();
366
    pLines->coordIndex.setNum(lineCount);
367
    pLines->coordIndex.setValues(0, lineCount, lines);
368

369
    pLineSeparator->addChild(pCoords);
370
    pLineSeparator->addChild(pLines);
371

372

373
    // Secondary Lines
374
    auto lineSetSecondary = new SoIndexedLineSet();
375
    lineSetSecondary->coordIndex.setNum(lineCountSecondary);
376
    lineSetSecondary->coordIndex.setValues(0, lineCountSecondary, linesSecondary);
377

378
    pLineSeparatorSecondary->addChild(pCoords);
379
    pLineSeparatorSecondary->addChild(lineSetSecondary);
380

381
    auto points = new SoMarkerSet();
382
    points->markerIndex =
383
        Gui::Inventor::MarkerBitmaps::getMarkerIndex("CROSS",
384
                                                     ViewParams::instance()->getMarkerSize());
385
    points->numPoints = 2;
386
    pLineSeparator->addChild(points);
387

388

389
    // Delta Dimensions
390
    auto decomposedPosition1 = new SoDecomposeVec3f();
391
    decomposedPosition1->vector.connectFrom(&fieldPosition1);
392
    auto decomposedPosition2 = new SoDecomposeVec3f();
393
    decomposedPosition2->vector.connectFrom(&fieldPosition2);
394

395
    // Create intermediate points
396
    auto composeVecDelta1 = new SoComposeVec3f();
397
    composeVecDelta1->x.connectFrom(&decomposedPosition2->x);
398
    composeVecDelta1->y.connectFrom(&decomposedPosition1->y);
399
    composeVecDelta1->z.connectFrom(&decomposedPosition1->z);
400

401
    auto composeVecDelta2 = new SoComposeVec3f();
402
    composeVecDelta2->x.connectFrom(&decomposedPosition2->x);
403
    composeVecDelta2->y.connectFrom(&decomposedPosition2->y);
404
    composeVecDelta2->z.connectFrom(&decomposedPosition1->z);
405

406
    // Set axis colors
407
    SbColor colorX;
408
    SbColor colorY;
409
    SbColor colorZ;
410

411
    float t = 0.0f;
412
    colorX.setPackedValue(ViewParams::instance()->getAxisXColor(), t);
413
    colorY.setPackedValue(ViewParams::instance()->getAxisYColor(), t);
414
    colorZ.setPackedValue(ViewParams::instance()->getAxisZColor(), t);
415

416
    auto dimDeltaX = new MeasureGui::DimensionLinear();
417
    dimDeltaX->point1.connectFrom(&fieldPosition1);
418
    dimDeltaX->point2.connectFrom(&composeVecDelta1->vector);
419
    dimDeltaX->setupDimension();
420
    dimDeltaX->dColor.setValue(colorX);
421

422
    auto dimDeltaY = new MeasureGui::DimensionLinear();
423
    dimDeltaY->point1.connectFrom(&composeVecDelta1->vector);
424
    dimDeltaY->point2.connectFrom(&composeVecDelta2->vector);
425
    dimDeltaY->setupDimension();
426
    dimDeltaY->dColor.setValue(colorY);
427

428
    auto dimDeltaZ = new MeasureGui::DimensionLinear();
429
    dimDeltaZ->point2.connectFrom(&composeVecDelta2->vector);
430
    dimDeltaZ->point1.connectFrom(&fieldPosition2);
431
    dimDeltaZ->setupDimension();
432
    dimDeltaZ->dColor.setValue(colorZ);
433

434
    pDeltaDimensionSwitch = new SoSwitch();
435
    pDeltaDimensionSwitch->ref();
436
    pGlobalSeparator->addChild(pDeltaDimensionSwitch);
437

438
    pDeltaDimensionSwitch->addChild(dimDeltaX);
439
    pDeltaDimensionSwitch->addChild(dimDeltaY);
440
    pDeltaDimensionSwitch->addChild(dimDeltaZ);
441

442
    // This should already be touched in ViewProviderMeasureBase
443
    FontSize.touch();
444
}
445

446
ViewProviderMeasureDistance::~ViewProviderMeasureDistance()
447
{
448
    pCoords->unref();
449
    pLines->unref();
450
    pDeltaDimensionSwitch->unref();
451
}
452

453

454
//! repaint the annotation
455
void ViewProviderMeasureDistance::redrawAnnotation()
456
{
457
    if (!pcObject) {
458
        return;
459
    }
460

461
    auto prop1 =
462
        Base::freecad_dynamic_cast<App::PropertyVector>(pcObject->getPropertyByName("Position1"));
463
    auto prop2 =
464
        Base::freecad_dynamic_cast<App::PropertyVector>(pcObject->getPropertyByName("Position2"));
465

466
    if (!prop1 || !prop2) {
467
        return;
468
    }
469

470
    auto vec1 = prop1->getValue();
471
    auto vec2 = prop2->getValue();
472

473
    fieldPosition1.setValue(SbVec3f(vec1.x, vec1.y, vec1.z));
474
    fieldPosition2.setValue(SbVec3f(vec2.x, vec2.y, vec2.z));
475

476
    // Set the distance
477
    fieldDistance = (vec2 - vec1).Length();
478

479
    auto propDistance =
480
        dynamic_cast<App::PropertyDistance*>(pcObject->getPropertyByName("Distance"));
481
    setLabelValue(propDistance->getQuantityValue().getUserString());
482

483
    // Set delta distance
484
    auto propDistanceX =
485
        static_cast<App::PropertyDistance*>(getMeasureObject()->getPropertyByName("DistanceX"));
486
    static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(0))
487
        ->text.setValue("Δx: " + propDistanceX->getQuantityValue().getUserString().toUtf8());
488

489
    auto propDistanceY =
490
        static_cast<App::PropertyDistance*>(getMeasureObject()->getPropertyByName("DistanceY"));
491
    static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(1))
492
        ->text.setValue("Δy: " + propDistanceY->getQuantityValue().getUserString().toUtf8());
493

494
    auto propDistanceZ =
495
        static_cast<App::PropertyDistance*>(getMeasureObject()->getPropertyByName("DistanceZ"));
496
    static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(2))
497
        ->text.setValue("Δz: " + propDistanceZ->getQuantityValue().getUserString().toUtf8());
498

499
    // Set matrix
500
    SbMatrix matrix = getMatrix();
501
    pcTransform->setMatrix(matrix);
502

503
    ViewProviderMeasureBase::redrawAnnotation();
504
    updateView();
505
}
506

507
void ViewProviderMeasureDistance::onChanged(const App::Property* prop)
508
{
509

510
    if (prop == &ShowDelta) {
511
        pDeltaDimensionSwitch->whichChild.setValue(ShowDelta.getValue() ? SO_SWITCH_ALL
512
                                                                        : SO_SWITCH_NONE);
513
    }
514
    else if (prop == &FontSize) {
515
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(0))
516
            ->fontSize.setValue(FontSize.getValue());
517
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(1))
518
            ->fontSize.setValue(FontSize.getValue());
519
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(2))
520
            ->fontSize.setValue(FontSize.getValue());
521
    }
522
    else if (prop == &TextBackgroundColor) {
523
        auto bColor = TextBackgroundColor.getValue();
524
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(0))
525
            ->backgroundColor.setValue(bColor.r, bColor.g, bColor.g);
526
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(1))
527
            ->backgroundColor.setValue(bColor.r, bColor.g, bColor.g);
528
        static_cast<DimensionLinear*>(pDeltaDimensionSwitch->getChild(2))
529
            ->backgroundColor.setValue(bColor.r, bColor.g, bColor.g);
530
    }
531

532

533
    ViewProviderMeasureBase::onChanged(prop);
534
}
535

536

537
void ViewProviderMeasureDistance::positionAnno(const Measure::MeasureBase* measureObject)
538
{
539
    (void)measureObject;
540
    setLabelTranslation(SbVec3f(0, 0.1 * getViewScale(), 0));
541
}
542

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

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

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

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