FreeCAD

Форк
0
/
PropertyContainerPyImp.cpp 
650 строк · 21.8 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2007 Jürgen Riegel <juergen.riegel@web.de>              *
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

24
#include "PreCompiled.h"
25

26
#ifndef _PreComp_
27
# include <sstream>
28
#endif
29

30
#include "PropertyContainer.h"
31
#include "Property.h"
32
#include "DocumentObject.h"
33
#include <Base/PyWrapParseTupleAndKeywords.h>
34

35
#include <boost/iostreams/device/array.hpp>
36
#include <boost/iostreams/stream.hpp>
37

38
// inclusion of the generated files (generated out of PropertyContainerPy.xml)
39
#include "PropertyContainerPy.h"
40
#include "PropertyContainerPy.cpp"
41

42
FC_LOG_LEVEL_INIT("Property", true, 2)
43

44
using namespace App;
45

46
// returns a string which represent the object e.g. when printed in python
47
std::string PropertyContainerPy::representation() const
48
{
49
    return {"<property container>"};
50
}
51

52
PyObject*  PropertyContainerPy::getPropertyByName(PyObject *args)
53
{
54
    char *pstr;
55
    int checkOwner=0;
56
    if (!PyArg_ParseTuple(args, "s|i", &pstr, &checkOwner))
57
        return nullptr;
58

59
    if (checkOwner < 0 || checkOwner > 2) {
60
        PyErr_SetString(PyExc_ValueError, "'checkOwner' expected in the range [0, 2]");
61
        return nullptr;
62
    }
63

64
    App::Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
65
    if (!prop) {
66
        PyErr_Format(Base::PyExc_FC_PropertyError, "Property container has no property '%s'", pstr);
67
        return nullptr;
68
    }
69

70
    if (!checkOwner || (checkOwner==1 && prop->getContainer()==getPropertyContainerPtr()))
71
        return prop->getPyObject();
72

73
    Py::TupleN res(Py::asObject(prop->getContainer()->getPyObject()), Py::asObject(prop->getPyObject()));
74

75
    return Py::new_reference_to(res);
76
}
77

78
PyObject*  PropertyContainerPy::getPropertyTouchList(PyObject *args)
79
{
80
    char *pstr;
81
    if (!PyArg_ParseTuple(args, "s", &pstr))
82
        return nullptr;
83

84
    App::Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
85
    if (prop && prop->isDerivedFrom(PropertyLists::getClassTypeId())) {
86
        const auto &touched = static_cast<PropertyLists*>(prop)->getTouchList();
87
        Py::Tuple ret(touched.size());
88
        int i=0;
89
        for(int idx : touched)
90
            ret.setItem(i++,Py::Long(idx));
91
        return Py::new_reference_to(ret);
92
    }
93
    else if (!prop) {
94
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
95
        return nullptr;
96
    }
97
    else {
98
        PyErr_Format(PyExc_AttributeError, "Property '%s' is not of list type", pstr);
99
        return nullptr;
100
    }
101
}
102

103
PyObject*  PropertyContainerPy::getTypeOfProperty(PyObject *args)
104
{
105
    Py::List ret;
106
    char *pstr;
107
    if (!PyArg_ParseTuple(args, "s", &pstr))
108
        return nullptr;
109

110
    Property* prop =  getPropertyContainerPtr()->getPropertyByName(pstr);
111
    if (!prop) {
112
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
113
        return nullptr;
114
    }
115

116
    short Type =  prop->getType();
117
    if (Type & Prop_ReadOnly)
118
        ret.append(Py::String("ReadOnly"));
119
    if (Type & Prop_Transient)
120
        ret.append(Py::String("Transient"));
121
    if (Type & Prop_Hidden)
122
        ret.append(Py::String("Hidden"));
123
    if (Type & Prop_Output)
124
        ret.append(Py::String("Output"));
125
    if (Type & Prop_NoRecompute)
126
        ret.append(Py::String("NoRecompute"));
127
    if (Type & Prop_NoPersist)
128
        ret.append(Py::String("NoPersist"));
129

130
    return Py::new_reference_to(ret);
131
}
132

133
PyObject*  PropertyContainerPy::getTypeIdOfProperty(PyObject *args)
134
{
135
    char *pstr;
136
    if (!PyArg_ParseTuple(args, "s", &pstr))
137
        return nullptr;
138

139
    Property* prop =  getPropertyContainerPtr()->getPropertyByName(pstr);
140
    if (!prop) {
141
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
142
        return nullptr;
143
    }
144

145
    Py::String str(prop->getTypeId().getName());
146
    return Py::new_reference_to(str);
147
}
148

149
PyObject*  PropertyContainerPy::setEditorMode(PyObject *args)
150
{
151
    char* name;
152
    short type;
153
    if (PyArg_ParseTuple(args, "sh", &name, &type)) {
154
        App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
155
        if (!prop) {
156
            PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
157
            return nullptr;
158
        }
159

160
        std::bitset<32> status(prop->getStatus());
161
        status.set(Property::ReadOnly, (type & 1) > 0);
162
        status.set(Property::Hidden, (type & 2) > 0);
163
        prop->setStatusValue(status.to_ulong());
164

165
        Py_Return;
166
    }
167

168
    PyErr_Clear();
169
    PyObject *iter;
170
    if (PyArg_ParseTuple(args, "sO", &name, &iter)) {
171
        if (PyTuple_Check(iter) || PyList_Check(iter)) {
172
            Py::Sequence seq(iter);
173
            App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
174
            if (!prop) {
175
                PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
176
                return nullptr;
177
            }
178

179
            // reset all bits first
180
            std::bitset<32> status(prop->getStatus());
181
            status.reset(Property::ReadOnly);
182
            status.reset(Property::Hidden);
183
            for (Py::Sequence::iterator it = seq.begin();it!=seq.end();++it) {
184
                std::string str = static_cast<std::string>(Py::String(*it));
185
                if (str == "ReadOnly")
186
                    status.set(Property::ReadOnly);
187
                else if (str == "Hidden")
188
                    status.set(Property::Hidden);
189
            }
190
            prop->setStatusValue(status.to_ulong());
191

192
            Py_Return;
193
        }
194
    }
195

196
    PyErr_SetString(PyExc_TypeError, "First argument must be str, second can be int, list or tuple");
197
    return nullptr;
198
}
199

200
static const std::map<std::string, int> &getStatusMap() {
201
    static std::map<std::string,int> statusMap;
202
    if(statusMap.empty()) {
203
        statusMap["Immutable"] = Property::Immutable;
204
        statusMap["ReadOnly"] = Property::ReadOnly;
205
        statusMap["Hidden"] = Property::Hidden;
206
        statusMap["Transient"] = Property::Transient;
207
        statusMap["MaterialEdit"] = Property::MaterialEdit;
208
        statusMap["NoMaterialListEdit"] = Property::NoMaterialListEdit;
209
        statusMap["Output"] = Property::Output;
210
        statusMap["LockDynamic"] = Property::LockDynamic;
211
        statusMap["NoModify"] = Property::NoModify;
212
        statusMap["PartialTrigger"] = Property::PartialTrigger;
213
        statusMap["NoRecompute"] = Property::NoRecompute;
214
        statusMap["CopyOnChange"] = Property::CopyOnChange;
215
        statusMap["UserEdit"] = Property::UserEdit;
216
    }
217
    return statusMap;
218
}
219

220
PyObject* PropertyContainerPy::setPropertyStatus(PyObject *args)
221
{
222
    char* name {};
223
    PyObject* pyValue {};
224
    if (!PyArg_ParseTuple(args, "sO", &name, &pyValue))
225
        return nullptr;
226

227
    App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
228
    if (!prop) {
229
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
230
        return nullptr;
231
    }
232

233
    auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
234
    std::bitset<32> status(prop->getStatus());
235

236
    std::vector<Py::Object> items;
237
    if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) {
238
        Py::Sequence seq(pyValue);
239
        for (const auto& it : seq) {
240
            items.emplace_back(it);
241
        }
242
    }
243
    else {
244
        items.emplace_back(pyValue);
245
    }
246

247
    for (const auto& item : items) {
248
        bool value = true;
249
        if (item.isString()) {
250
            const auto &statusMap = getStatusMap();
251
            auto v = static_cast<std::string>(Py::String(item));
252
            if (v.size() > 1 && v[0] == '-') {
253
                value = false;
254
                v = v.substr(1);
255
            }
256
            auto it = statusMap.find(v);
257
            if (it == statusMap.end()) {
258
                if (linkProp && v == "AllowPartial") {
259
                    linkProp->setAllowPartial(value);
260
                    continue;
261
                }
262

263
                PyErr_Format(PyExc_ValueError, "Unknown property status '%s'", v.c_str());
264
                return nullptr;
265
            }
266

267
            status.set(it->second, value);
268
        }
269
        else if (item.isNumeric()) {
270
            int v = Py::Int(item);
271
            if (v < 0) {
272
                value = false;
273
                v = -v;
274
            }
275
            if (v == 0 || v > 31) {
276
                PyErr_Format(PyExc_ValueError, "Status value out of range '%d'", v);
277
                return nullptr;
278
            }
279
            status.set(v, value);
280
        }
281
        else {
282
            PyErr_SetString(PyExc_TypeError, "Expects status type to be Int or String");
283
            return nullptr;
284
        }
285
    }
286

287
    prop->setStatusValue(status.to_ulong());
288
    Py_Return;
289
}
290

291
PyObject*  PropertyContainerPy::getPropertyStatus(PyObject *args)
292
{
293
    const char* name = "";
294
    if (!PyArg_ParseTuple(args, "|s", &name))
295
        return nullptr;
296

297
    Py::List ret;
298
    const auto &statusMap = getStatusMap();
299
    if (!name[0]) {
300
        for(auto &v : statusMap)
301
            ret.append(Py::String(v.first.c_str()));
302
    }
303
    else {
304
        App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
305
        if (!prop) {
306
            PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
307
            return nullptr;
308
        }
309

310
        auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
311
        if (linkProp && linkProp->testFlag(App::PropertyLinkBase::LinkAllowPartial))
312
            ret.append(Py::String("AllowPartial"));
313

314
        std::bitset<32> bits(prop->getStatus());
315
        for(size_t i=1; i<bits.size(); ++i) {
316
            if(!bits[i]) continue;
317
            bool found = false;
318
            for(auto &v : statusMap) {
319
                if(v.second == static_cast<int>(i)) {
320
                    ret.append(Py::String(v.first.c_str()));
321
                    found = true;
322
                    break;
323
                }
324
            }
325
            if (!found)
326
                ret.append(Py::Int(static_cast<long>(i)));
327
        }
328
    }
329
    return Py::new_reference_to(ret);
330
}
331

332
PyObject*  PropertyContainerPy::getEditorMode(PyObject *args)
333
{
334
    char* name;
335
    if (!PyArg_ParseTuple(args, "s", &name))
336
        return nullptr;
337

338
    App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
339
    if (!prop) {
340
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
341
        return nullptr;
342
    }
343

344
    Py::List ret;
345
    if (prop) {
346
        short Type =  prop->getType();
347
        if ((prop->testStatus(Property::ReadOnly)) || (Type & Prop_ReadOnly))
348
            ret.append(Py::String("ReadOnly"));
349
        if ((prop->testStatus(Property::Hidden)) || (Type & Prop_Hidden))
350
            ret.append(Py::String("Hidden"));
351
    }
352
    return Py::new_reference_to(ret);
353
}
354

355
PyObject*  PropertyContainerPy::getGroupOfProperty(PyObject *args)
356
{
357
    char *pstr;
358
    if (!PyArg_ParseTuple(args, "s", &pstr))
359
        return nullptr;
360

361
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
362
    if (!prop) {
363
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
364
        return nullptr;
365
    }
366

367
    const char* Group = getPropertyContainerPtr()->getPropertyGroup(prop);
368
    if (Group)
369
        return Py::new_reference_to(Py::String(Group));
370
    else
371
        return Py::new_reference_to(Py::String(""));
372
}
373

374
PyObject*  PropertyContainerPy::setGroupOfProperty(PyObject *args)
375
{
376
    char *pstr;
377
    char *group;
378
    if (!PyArg_ParseTuple(args, "ss", &pstr, &group))
379
        return nullptr;
380

381
    PY_TRY {
382
        Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
383
        if (!prop) {
384
            PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
385
            return nullptr;
386
        }
387
        prop->getContainer()->changeDynamicProperty(prop,group,nullptr);
388
        Py_Return;
389
    }
390
    PY_CATCH
391
}
392

393

394
PyObject*  PropertyContainerPy::getDocumentationOfProperty(PyObject *args)
395
{
396
    char *pstr;
397
    if (!PyArg_ParseTuple(args, "s", &pstr))
398
        return nullptr;
399

400
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
401
    if (!prop) {
402
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
403
        return nullptr;
404
    }
405

406
    const char* docstr = getPropertyContainerPtr()->getPropertyDocumentation(prop);
407
    if (docstr)
408
        return Py::new_reference_to(Py::String(docstr));
409
    else
410
        return Py::new_reference_to(Py::String(""));
411
}
412

413
PyObject*  PropertyContainerPy::setDocumentationOfProperty(PyObject *args)
414
{
415
    char *pstr;
416
    char *doc;
417
    if (!PyArg_ParseTuple(args, "ss", &pstr, &doc))
418
        return nullptr;
419

420
    PY_TRY {
421
        Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
422
        if (!prop) {
423
            PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
424
            return nullptr;
425
        }
426
        prop->getContainer()->changeDynamicProperty(prop,nullptr,doc);
427
        Py_Return;
428
    }
429
    PY_CATCH
430
}
431

432
PyObject*  PropertyContainerPy::getEnumerationsOfProperty(PyObject *args)
433
{
434
    char *pstr;
435
    if (!PyArg_ParseTuple(args, "s", &pstr))
436
        return nullptr;
437

438
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
439
    if (!prop) {
440
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
441
        return nullptr;
442
    }
443

444
    PropertyEnumeration *enumProp = dynamic_cast<PropertyEnumeration*>(prop);
445
    if (!enumProp)
446
        Py_Return;
447

448
    std::vector<std::string> enumerations = enumProp->getEnumVector();
449
    Py::List ret;
450
    for (const auto & it : enumerations) {
451
        ret.append(Py::String(it));
452
    }
453
    return Py::new_reference_to(ret);
454
}
455

456
Py::List PropertyContainerPy::getPropertiesList() const
457
{
458
    Py::List ret;
459
    std::map<std::string,Property*> Map;
460

461
    getPropertyContainerPtr()->getPropertyMap(Map);
462

463
    for (std::map<std::string,Property*>::const_iterator It=Map.begin(); It!=Map.end(); ++It)
464
        ret.append(Py::String(It->first));
465

466
    return ret;
467
}
468

469

470
PyObject* PropertyContainerPy::dumpPropertyContent(PyObject *args, PyObject *kwds)
471
{
472
    int compression = 3;
473
    const char* property;
474
    static const std::array<const char *, 3> kwds_def {"Property", "Compression", nullptr};
475
    PyErr_Clear();
476
    if (!Base::Wrapped_ParseTupleAndKeywords(args, kwds, "s|i", kwds_def, &property, &compression)) {
477
        return nullptr;
478
    }
479

480
    Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
481
    if (!prop) {
482
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
483
        return nullptr;
484
    }
485

486
    //setup the stream. the in flag is needed to make "read" work
487
    std::stringstream stream(std::stringstream::out | std::stringstream::in | std::stringstream::binary);
488
    try {
489
        prop->dumpToStream(stream, compression);
490
    }
491
    catch (...) {
492
       PyErr_SetString(PyExc_IOError, "Unable to parse content into binary representation");
493
       return nullptr;
494
    }
495

496
    //build the byte array with correct size
497
    if (!stream.seekp(0, stream.end)) {
498
        PyErr_SetString(PyExc_IOError, "Unable to find end of stream");
499
        return nullptr;
500
    }
501

502
    std::stringstream::pos_type offset = stream.tellp();
503
    if (!stream.seekg(0, stream.beg)) {
504
        PyErr_SetString(PyExc_IOError, "Unable to find begin of stream");
505
        return nullptr;
506
    }
507

508
    PyObject* ba = PyByteArray_FromStringAndSize(nullptr, offset);
509

510
    //use the buffer protocol to access the underlying array and write into it
511
    Py_buffer buf = Py_buffer();
512
    PyObject_GetBuffer(ba, &buf, PyBUF_WRITABLE);
513
    try {
514
        if(!stream.read((char*)buf.buf, offset)) {
515
            PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
516
            return nullptr;
517
        }
518
        PyBuffer_Release(&buf);
519
    }
520
    catch (...) {
521
        PyBuffer_Release(&buf);
522
        PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
523
        return nullptr;
524
    }
525

526
    return ba;
527
}
528

529
PyObject* PropertyContainerPy::restorePropertyContent(PyObject *args)
530
{
531
    PyObject* buffer;
532
    char* property;
533
    if( !PyArg_ParseTuple(args, "sO", &property, &buffer) )
534
        return nullptr;
535

536
    Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
537
    if (!prop) {
538
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
539
        return nullptr;
540
    }
541

542
    //check if it really is a buffer
543
    if( !PyObject_CheckBuffer(buffer) ) {
544
        PyErr_SetString(PyExc_TypeError, "Must be a buffer object");
545
        return nullptr;
546
    }
547

548
    Py_buffer buf;
549
    if(PyObject_GetBuffer(buffer, &buf, PyBUF_SIMPLE) < 0)
550
        return nullptr;
551

552
    if(!PyBuffer_IsContiguous(&buf, 'C')) {
553
        PyErr_SetString(PyExc_TypeError, "Buffer must be contiguous");
554
        return nullptr;
555
    }
556

557
    //check if it really is a buffer
558
    try {
559
        using Device = boost::iostreams::basic_array_source<char>;
560
        boost::iostreams::stream<Device> stream((char*)buf.buf, buf.len);
561
        prop->restoreFromStream(stream);
562
    }
563
    catch(...) {
564
        PyErr_SetString(PyExc_IOError, "Unable to restore content");
565
        return nullptr;
566
    }
567

568
    Py_Return;
569
}
570

571
PyObject *PropertyContainerPy::getCustomAttributes(const char* attr) const
572
{
573
    // search in PropertyList
574
    if(FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE) {
575
        FC_TRACE("Get property " << attr);
576
    }
577
    Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
578
    if (prop) {
579
        PyObject* pyobj = prop->getPyObject();
580
        if (!pyobj && PyErr_Occurred()) {
581
            // the Python exception is already set
582
            throw Py::Exception();
583
        }
584
        return pyobj;
585
    }
586
    else if (Base::streq(attr, "__dict__")) {
587
        // get the properties to the C++ PropertyContainer class
588
        std::map<std::string,App::Property*> Map;
589
        getPropertyContainerPtr()->getPropertyMap(Map);
590

591
        Py::Dict dict;
592
        for (const auto & it : Map) {
593
            dict.setItem(it.first, Py::String(""));
594
        }
595
        return Py::new_reference_to(dict);
596
    }
597
    ///FIXME: For v0.20: Do not use stuff from Part module here!
598
    else if(Base::streq(attr,"Shape") && getPropertyContainerPtr()->isDerivedFrom(App::DocumentObject::getClassTypeId())) {
599
        // Special treatment of Shape property
600
        static PyObject *_getShape = nullptr;
601
        if(!_getShape) {
602
            _getShape = Py_None;
603
            PyObject *mod = PyImport_ImportModule("Part");
604
            if(!mod) {
605
                PyErr_Clear();
606
            } else {
607
                Py::Object pyMod = Py::asObject(mod);
608
                if(pyMod.hasAttr("getShape"))
609
                    _getShape = Py::new_reference_to(pyMod.getAttr("getShape"));
610
            }
611
        }
612
        if(_getShape != Py_None) {
613
            Py::Tuple args(1);
614
            args.setItem(0,Py::Object(const_cast<PropertyContainerPy*>(this)));
615
            auto res = PyObject_CallObject(_getShape, args.ptr());
616
            if(!res)
617
                PyErr_Clear();
618
            else {
619
                Py::Object pyres(res,true);
620
                if(pyres.hasAttr("isNull")) {
621
                    Py::Callable func(pyres.getAttr("isNull"));
622
                    if(!func.apply().isTrue())
623
                        return Py::new_reference_to(res);
624
                }
625
            }
626
        }
627
    }
628

629
    return nullptr;
630
}
631

632
int PropertyContainerPy::setCustomAttributes(const char* attr, PyObject *obj)
633
{
634
    // search in PropertyList
635
    Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
636
    if (prop) {
637
        // Read-only attributes must not be set over its Python interface
638
        if(prop->testStatus(Property::Immutable)) {
639
            std::stringstream s;
640
            s << "Object attribute '" << attr << "' is read-only";
641
            throw Py::AttributeError(s.str());
642
        }
643

644
        FC_TRACE("Set property " << prop->getFullName());
645
        prop->setPyObject(obj);
646
        return 1;
647
    }
648

649
    return 0;
650
}
651

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

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

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

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