1
/***************************************************************************
2
* Copyright (c) 2007 Jürgen Riegel <juergen.riegel@web.de> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
30
#include "PropertyContainer.h"
32
#include "DocumentObject.h"
33
#include <Base/PyWrapParseTupleAndKeywords.h>
35
#include <boost/iostreams/device/array.hpp>
36
#include <boost/iostreams/stream.hpp>
38
// inclusion of the generated files (generated out of PropertyContainerPy.xml)
39
#include "PropertyContainerPy.h"
40
#include "PropertyContainerPy.cpp"
42
FC_LOG_LEVEL_INIT("Property", true, 2)
46
// returns a string which represent the object e.g. when printed in python
47
std::string PropertyContainerPy::representation() const
49
return {"<property container>"};
52
PyObject* PropertyContainerPy::getPropertyByName(PyObject *args)
56
if (!PyArg_ParseTuple(args, "s|i", &pstr, &checkOwner))
59
if (checkOwner < 0 || checkOwner > 2) {
60
PyErr_SetString(PyExc_ValueError, "'checkOwner' expected in the range [0, 2]");
64
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
66
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
70
if (!checkOwner || (checkOwner==1 && prop->getContainer()==getPropertyContainerPtr()))
71
return prop->getPyObject();
73
Py::TupleN res(Py::asObject(prop->getContainer()->getPyObject()), Py::asObject(prop->getPyObject()));
75
return Py::new_reference_to(res);
78
PyObject* PropertyContainerPy::getPropertyTouchList(PyObject *args)
81
if (!PyArg_ParseTuple(args, "s", &pstr))
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());
89
for(int idx : touched)
90
ret.setItem(i++,Py::Long(idx));
91
return Py::new_reference_to(ret);
94
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
98
PyErr_Format(PyExc_AttributeError, "Property '%s' is not of list type", pstr);
103
PyObject* PropertyContainerPy::getTypeOfProperty(PyObject *args)
107
if (!PyArg_ParseTuple(args, "s", &pstr))
110
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
112
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
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"));
130
return Py::new_reference_to(ret);
133
PyObject* PropertyContainerPy::getTypeIdOfProperty(PyObject *args)
136
if (!PyArg_ParseTuple(args, "s", &pstr))
139
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
141
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
145
Py::String str(prop->getTypeId().getName());
146
return Py::new_reference_to(str);
149
PyObject* PropertyContainerPy::setEditorMode(PyObject *args)
153
if (PyArg_ParseTuple(args, "sh", &name, &type)) {
154
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
156
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
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());
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);
175
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
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);
190
prop->setStatusValue(status.to_ulong());
196
PyErr_SetString(PyExc_TypeError, "First argument must be str, second can be int, list or tuple");
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;
220
PyObject* PropertyContainerPy::setPropertyStatus(PyObject *args)
224
if (!PyArg_ParseTuple(args, "sO", &name, &pyValue))
227
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
229
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
233
auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
234
std::bitset<32> status(prop->getStatus());
237
if (PyList_Check(pyValue) || PyTuple_Check(pyValue)) {
239
count = PySequence_Size(pyValue);
242
for(size_t i=0; i<count; ++i) {
245
item = Py::Object(PySequence_GetItem(pyValue,i));
247
item = Py::Object(pyValue);
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] == '-') {
256
auto it = statusMap.find(v);
257
if(it == statusMap.end()) {
258
if(linkProp && v == "AllowPartial") {
259
linkProp->setAllowPartial(value);
262
PyErr_Format(PyExc_ValueError, "Unknown property status '%s'", v.c_str());
265
status.set(it->second,value);
267
else if (item.isNumeric()) {
268
int v = Py::Int(item);
274
PyErr_Format(PyExc_ValueError, "Status value out of range '%d'", v);
278
PyErr_SetString(PyExc_TypeError, "Expects status type to be Int or String");
283
prop->setStatusValue(status.to_ulong());
287
PyObject* PropertyContainerPy::getPropertyStatus(PyObject *args)
289
const char* name = "";
290
if (!PyArg_ParseTuple(args, "|s", &name))
294
const auto &statusMap = getStatusMap();
296
for(auto &v : statusMap)
297
ret.append(Py::String(v.first.c_str()));
300
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
302
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
306
auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
307
if (linkProp && linkProp->testFlag(App::PropertyLinkBase::LinkAllowPartial))
308
ret.append(Py::String("AllowPartial"));
310
std::bitset<32> bits(prop->getStatus());
311
for(size_t i=1; i<bits.size(); ++i) {
312
if(!bits[i]) continue;
314
for(auto &v : statusMap) {
315
if(v.second == static_cast<int>(i)) {
316
ret.append(Py::String(v.first.c_str()));
322
ret.append(Py::Int(static_cast<long>(i)));
325
return Py::new_reference_to(ret);
328
PyObject* PropertyContainerPy::getEditorMode(PyObject *args)
331
if (!PyArg_ParseTuple(args, "s", &name))
334
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
336
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
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"));
348
return Py::new_reference_to(ret);
351
PyObject* PropertyContainerPy::getGroupOfProperty(PyObject *args)
354
if (!PyArg_ParseTuple(args, "s", &pstr))
357
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
359
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
363
const char* Group = getPropertyContainerPtr()->getPropertyGroup(prop);
365
return Py::new_reference_to(Py::String(Group));
367
return Py::new_reference_to(Py::String(""));
370
PyObject* PropertyContainerPy::setGroupOfProperty(PyObject *args)
374
if (!PyArg_ParseTuple(args, "ss", &pstr, &group))
378
Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
380
PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
383
prop->getContainer()->changeDynamicProperty(prop,group,nullptr);
390
PyObject* PropertyContainerPy::getDocumentationOfProperty(PyObject *args)
393
if (!PyArg_ParseTuple(args, "s", &pstr))
396
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
398
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
402
const char* docstr = getPropertyContainerPtr()->getPropertyDocumentation(prop);
404
return Py::new_reference_to(Py::String(docstr));
406
return Py::new_reference_to(Py::String(""));
409
PyObject* PropertyContainerPy::setDocumentationOfProperty(PyObject *args)
413
if (!PyArg_ParseTuple(args, "ss", &pstr, &doc))
417
Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
419
PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
422
prop->getContainer()->changeDynamicProperty(prop,nullptr,doc);
428
PyObject* PropertyContainerPy::getEnumerationsOfProperty(PyObject *args)
431
if (!PyArg_ParseTuple(args, "s", &pstr))
434
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
436
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
440
PropertyEnumeration *enumProp = dynamic_cast<PropertyEnumeration*>(prop);
444
std::vector<std::string> enumerations = enumProp->getEnumVector();
446
for (const auto & it : enumerations) {
447
ret.append(Py::String(it));
449
return Py::new_reference_to(ret);
452
Py::List PropertyContainerPy::getPropertiesList() const
455
std::map<std::string,Property*> Map;
457
getPropertyContainerPtr()->getPropertyMap(Map);
459
for (std::map<std::string,Property*>::const_iterator It=Map.begin(); It!=Map.end(); ++It)
460
ret.append(Py::String(It->first));
466
PyObject* PropertyContainerPy::dumpPropertyContent(PyObject *args, PyObject *kwds)
469
const char* property;
470
static const std::array<const char *, 3> kwds_def {"Property", "Compression", nullptr};
472
if (!Base::Wrapped_ParseTupleAndKeywords(args, kwds, "s|i", kwds_def, &property, &compression)) {
476
Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
478
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
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);
485
prop->dumpToStream(stream, compression);
488
PyErr_SetString(PyExc_IOError, "Unable to parse content into binary representation");
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");
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");
504
PyObject* ba = PyByteArray_FromStringAndSize(nullptr, offset);
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);
510
if(!stream.read((char*)buf.buf, offset)) {
511
PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
514
PyBuffer_Release(&buf);
517
PyBuffer_Release(&buf);
518
PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
525
PyObject* PropertyContainerPy::restorePropertyContent(PyObject *args)
529
if( !PyArg_ParseTuple(args, "sO", &property, &buffer) )
532
Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
534
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
538
//check if it really is a buffer
539
if( !PyObject_CheckBuffer(buffer) ) {
540
PyErr_SetString(PyExc_TypeError, "Must be a buffer object");
545
if(PyObject_GetBuffer(buffer, &buf, PyBUF_SIMPLE) < 0)
548
if(!PyBuffer_IsContiguous(&buf, 'C')) {
549
PyErr_SetString(PyExc_TypeError, "Buffer must be contiguous");
553
//check if it really is a buffer
555
using Device = boost::iostreams::basic_array_source<char>;
556
boost::iostreams::stream<Device> stream((char*)buf.buf, buf.len);
557
prop->restoreFromStream(stream);
560
PyErr_SetString(PyExc_IOError, "Unable to restore content");
567
PyObject *PropertyContainerPy::getCustomAttributes(const char* attr) const
569
// search in PropertyList
570
if(FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE) {
571
FC_TRACE("Get property " << attr);
573
Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
575
PyObject* pyobj = prop->getPyObject();
576
if (!pyobj && PyErr_Occurred()) {
577
// the Python exception is already set
578
throw Py::Exception();
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);
588
for (const auto & it : Map) {
589
dict.setItem(it.first, Py::String(""));
591
return Py::new_reference_to(dict);
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;
599
PyObject *mod = PyImport_ImportModule("Part");
603
Py::Object pyMod = Py::asObject(mod);
604
if(pyMod.hasAttr("getShape"))
605
_getShape = Py::new_reference_to(pyMod.getAttr("getShape"));
608
if(_getShape != Py_None) {
610
args.setItem(0,Py::Object(const_cast<PropertyContainerPy*>(this)));
611
auto res = PyObject_CallObject(_getShape, args.ptr());
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);
628
int PropertyContainerPy::setCustomAttributes(const char* attr, PyObject *obj)
630
// search in PropertyList
631
Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
633
// Read-only attributes must not be set over its Python interface
634
if(prop->testStatus(Property::Immutable)) {
636
s << "Object attribute '" << attr << "' is read-only";
637
throw Py::AttributeError(s.str());
640
FC_TRACE("Set property " << prop->getFullName());
641
prop->setPyObject(obj);