FreeCAD

Форк
0
/
PropertyContainerPyImp.cpp 
646 строк · 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(PyExc_AttributeError, "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
    size_t count = 1;
236
    bool isSeq = false;
237
    if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) {
238
        isSeq = true;
239
        count = PySequence_Size(pyValue);
240
    }
241

242
    for(size_t i=0; i<count; ++i) {
243
        Py::Object item;
244
        if (isSeq)
245
            item = Py::Object(PySequence_GetItem(pyValue,i));
246
        else
247
            item = Py::Object(pyValue);
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
                PyErr_Format(PyExc_ValueError, "Unknown property status '%s'", v.c_str());
263
                return nullptr;
264
            }
265
            status.set(it->second,value);
266
        }
267
        else if (item.isNumeric()) {
268
            int v = Py::Int(item);
269
            if(v<0) {
270
                value = false;
271
                v = -v;
272
            }
273
            if(v==0 || v>31)
274
                PyErr_Format(PyExc_ValueError, "Status value out of range '%d'", v);
275
            status.set(v,value);
276
        }
277
        else {
278
            PyErr_SetString(PyExc_TypeError, "Expects status type to be Int or String");
279
            return nullptr;
280
        }
281
    }
282

283
    prop->setStatusValue(status.to_ulong());
284
    Py_Return;
285
}
286

287
PyObject*  PropertyContainerPy::getPropertyStatus(PyObject *args)
288
{
289
    const char* name = "";
290
    if (!PyArg_ParseTuple(args, "|s", &name))
291
        return nullptr;
292

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

306
        auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
307
        if (linkProp && linkProp->testFlag(App::PropertyLinkBase::LinkAllowPartial))
308
            ret.append(Py::String("AllowPartial"));
309

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

328
PyObject*  PropertyContainerPy::getEditorMode(PyObject *args)
329
{
330
    char* name;
331
    if (!PyArg_ParseTuple(args, "s", &name))
332
        return nullptr;
333

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

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

351
PyObject*  PropertyContainerPy::getGroupOfProperty(PyObject *args)
352
{
353
    char *pstr;
354
    if (!PyArg_ParseTuple(args, "s", &pstr))
355
        return nullptr;
356

357
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
358
    if (!prop) {
359
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
360
        return nullptr;
361
    }
362

363
    const char* Group = getPropertyContainerPtr()->getPropertyGroup(prop);
364
    if (Group)
365
        return Py::new_reference_to(Py::String(Group));
366
    else
367
        return Py::new_reference_to(Py::String(""));
368
}
369

370
PyObject*  PropertyContainerPy::setGroupOfProperty(PyObject *args)
371
{
372
    char *pstr;
373
    char *group;
374
    if (!PyArg_ParseTuple(args, "ss", &pstr, &group))
375
        return nullptr;
376

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

389

390
PyObject*  PropertyContainerPy::getDocumentationOfProperty(PyObject *args)
391
{
392
    char *pstr;
393
    if (!PyArg_ParseTuple(args, "s", &pstr))
394
        return nullptr;
395

396
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
397
    if (!prop) {
398
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
399
        return nullptr;
400
    }
401

402
    const char* docstr = getPropertyContainerPtr()->getPropertyDocumentation(prop);
403
    if (docstr)
404
        return Py::new_reference_to(Py::String(docstr));
405
    else
406
        return Py::new_reference_to(Py::String(""));
407
}
408

409
PyObject*  PropertyContainerPy::setDocumentationOfProperty(PyObject *args)
410
{
411
    char *pstr;
412
    char *doc;
413
    if (!PyArg_ParseTuple(args, "ss", &pstr, &doc))
414
        return nullptr;
415

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

428
PyObject*  PropertyContainerPy::getEnumerationsOfProperty(PyObject *args)
429
{
430
    char *pstr;
431
    if (!PyArg_ParseTuple(args, "s", &pstr))
432
        return nullptr;
433

434
    Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
435
    if (!prop) {
436
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
437
        return nullptr;
438
    }
439

440
    PropertyEnumeration *enumProp = dynamic_cast<PropertyEnumeration*>(prop);
441
    if (!enumProp)
442
        Py_Return;
443

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

452
Py::List PropertyContainerPy::getPropertiesList() const
453
{
454
    Py::List ret;
455
    std::map<std::string,Property*> Map;
456

457
    getPropertyContainerPtr()->getPropertyMap(Map);
458

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

462
    return ret;
463
}
464

465

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

476
    Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
477
    if (!prop) {
478
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
479
        return nullptr;
480
    }
481

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

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

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

504
    PyObject* ba = PyByteArray_FromStringAndSize(nullptr, offset);
505

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

522
    return ba;
523
}
524

525
PyObject* PropertyContainerPy::restorePropertyContent(PyObject *args)
526
{
527
    PyObject* buffer;
528
    char* property;
529
    if( !PyArg_ParseTuple(args, "sO", &property, &buffer) )
530
        return nullptr;
531

532
    Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
533
    if (!prop) {
534
        PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
535
        return nullptr;
536
    }
537

538
    //check if it really is a buffer
539
    if( !PyObject_CheckBuffer(buffer) ) {
540
        PyErr_SetString(PyExc_TypeError, "Must be a buffer object");
541
        return nullptr;
542
    }
543

544
    Py_buffer buf;
545
    if(PyObject_GetBuffer(buffer, &buf, PyBUF_SIMPLE) < 0)
546
        return nullptr;
547

548
    if(!PyBuffer_IsContiguous(&buf, 'C')) {
549
        PyErr_SetString(PyExc_TypeError, "Buffer must be contiguous");
550
        return nullptr;
551
    }
552

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

564
    Py_Return;
565
}
566

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

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

625
    return nullptr;
626
}
627

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

640
        FC_TRACE("Set property " << prop->getFullName());
641
        prop->setPyObject(obj);
642
        return 1;
643
    }
644

645
    return 0;
646
}
647

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

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

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

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