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(Base::PyExc_FC_PropertyError, "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)
223
PyObject* pyValue {};
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());
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);
244
items.emplace_back(pyValue);
247
for (const auto& item : items) {
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);
263
PyErr_Format(PyExc_ValueError, "Unknown property status '%s'", v.c_str());
267
status.set(it->second, value);
269
else if (item.isNumeric()) {
270
int v = Py::Int(item);
275
if (v == 0 || v > 31) {
276
PyErr_Format(PyExc_ValueError, "Status value out of range '%d'", v);
279
status.set(v, value);
282
PyErr_SetString(PyExc_TypeError, "Expects status type to be Int or String");
287
prop->setStatusValue(status.to_ulong());
291
PyObject* PropertyContainerPy::getPropertyStatus(PyObject *args)
293
const char* name = "";
294
if (!PyArg_ParseTuple(args, "|s", &name))
298
const auto &statusMap = getStatusMap();
300
for(auto &v : statusMap)
301
ret.append(Py::String(v.first.c_str()));
304
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
306
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
310
auto linkProp = Base::freecad_dynamic_cast<App::PropertyLinkBase>(prop);
311
if (linkProp && linkProp->testFlag(App::PropertyLinkBase::LinkAllowPartial))
312
ret.append(Py::String("AllowPartial"));
314
std::bitset<32> bits(prop->getStatus());
315
for(size_t i=1; i<bits.size(); ++i) {
316
if(!bits[i]) continue;
318
for(auto &v : statusMap) {
319
if(v.second == static_cast<int>(i)) {
320
ret.append(Py::String(v.first.c_str()));
326
ret.append(Py::Int(static_cast<long>(i)));
329
return Py::new_reference_to(ret);
332
PyObject* PropertyContainerPy::getEditorMode(PyObject *args)
335
if (!PyArg_ParseTuple(args, "s", &name))
338
App::Property* prop = getPropertyContainerPtr()->getPropertyByName(name);
340
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", name);
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"));
352
return Py::new_reference_to(ret);
355
PyObject* PropertyContainerPy::getGroupOfProperty(PyObject *args)
358
if (!PyArg_ParseTuple(args, "s", &pstr))
361
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
363
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
367
const char* Group = getPropertyContainerPtr()->getPropertyGroup(prop);
369
return Py::new_reference_to(Py::String(Group));
371
return Py::new_reference_to(Py::String(""));
374
PyObject* PropertyContainerPy::setGroupOfProperty(PyObject *args)
378
if (!PyArg_ParseTuple(args, "ss", &pstr, &group))
382
Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
384
PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
387
prop->getContainer()->changeDynamicProperty(prop,group,nullptr);
394
PyObject* PropertyContainerPy::getDocumentationOfProperty(PyObject *args)
397
if (!PyArg_ParseTuple(args, "s", &pstr))
400
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
402
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
406
const char* docstr = getPropertyContainerPtr()->getPropertyDocumentation(prop);
408
return Py::new_reference_to(Py::String(docstr));
410
return Py::new_reference_to(Py::String(""));
413
PyObject* PropertyContainerPy::setDocumentationOfProperty(PyObject *args)
417
if (!PyArg_ParseTuple(args, "ss", &pstr, &doc))
421
Property* prop = getPropertyContainerPtr()->getDynamicPropertyByName(pstr);
423
PyErr_Format(PyExc_AttributeError, "Property container has no dynamic property '%s'", pstr);
426
prop->getContainer()->changeDynamicProperty(prop,nullptr,doc);
432
PyObject* PropertyContainerPy::getEnumerationsOfProperty(PyObject *args)
435
if (!PyArg_ParseTuple(args, "s", &pstr))
438
Property* prop = getPropertyContainerPtr()->getPropertyByName(pstr);
440
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", pstr);
444
PropertyEnumeration *enumProp = dynamic_cast<PropertyEnumeration*>(prop);
448
std::vector<std::string> enumerations = enumProp->getEnumVector();
450
for (const auto & it : enumerations) {
451
ret.append(Py::String(it));
453
return Py::new_reference_to(ret);
456
Py::List PropertyContainerPy::getPropertiesList() const
459
std::map<std::string,Property*> Map;
461
getPropertyContainerPtr()->getPropertyMap(Map);
463
for (std::map<std::string,Property*>::const_iterator It=Map.begin(); It!=Map.end(); ++It)
464
ret.append(Py::String(It->first));
470
PyObject* PropertyContainerPy::dumpPropertyContent(PyObject *args, PyObject *kwds)
473
const char* property;
474
static const std::array<const char *, 3> kwds_def {"Property", "Compression", nullptr};
476
if (!Base::Wrapped_ParseTupleAndKeywords(args, kwds, "s|i", kwds_def, &property, &compression)) {
480
Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
482
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
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);
489
prop->dumpToStream(stream, compression);
492
PyErr_SetString(PyExc_IOError, "Unable to parse content into binary representation");
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");
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");
508
PyObject* ba = PyByteArray_FromStringAndSize(nullptr, offset);
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);
514
if(!stream.read((char*)buf.buf, offset)) {
515
PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
518
PyBuffer_Release(&buf);
521
PyBuffer_Release(&buf);
522
PyErr_SetString(PyExc_IOError, "Error copying data into byte array");
529
PyObject* PropertyContainerPy::restorePropertyContent(PyObject *args)
533
if( !PyArg_ParseTuple(args, "sO", &property, &buffer) )
536
Property* prop = getPropertyContainerPtr()->getPropertyByName(property);
538
PyErr_Format(PyExc_AttributeError, "Property container has no property '%s'", property);
542
//check if it really is a buffer
543
if( !PyObject_CheckBuffer(buffer) ) {
544
PyErr_SetString(PyExc_TypeError, "Must be a buffer object");
549
if(PyObject_GetBuffer(buffer, &buf, PyBUF_SIMPLE) < 0)
552
if(!PyBuffer_IsContiguous(&buf, 'C')) {
553
PyErr_SetString(PyExc_TypeError, "Buffer must be contiguous");
557
//check if it really is a buffer
559
using Device = boost::iostreams::basic_array_source<char>;
560
boost::iostreams::stream<Device> stream((char*)buf.buf, buf.len);
561
prop->restoreFromStream(stream);
564
PyErr_SetString(PyExc_IOError, "Unable to restore content");
571
PyObject *PropertyContainerPy::getCustomAttributes(const char* attr) const
573
// search in PropertyList
574
if(FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE) {
575
FC_TRACE("Get property " << attr);
577
Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
579
PyObject* pyobj = prop->getPyObject();
580
if (!pyobj && PyErr_Occurred()) {
581
// the Python exception is already set
582
throw Py::Exception();
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);
592
for (const auto & it : Map) {
593
dict.setItem(it.first, Py::String(""));
595
return Py::new_reference_to(dict);
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;
603
PyObject *mod = PyImport_ImportModule("Part");
607
Py::Object pyMod = Py::asObject(mod);
608
if(pyMod.hasAttr("getShape"))
609
_getShape = Py::new_reference_to(pyMod.getAttr("getShape"));
612
if(_getShape != Py_None) {
614
args.setItem(0,Py::Object(const_cast<PropertyContainerPy*>(this)));
615
auto res = PyObject_CallObject(_getShape, args.ptr());
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);
632
int PropertyContainerPy::setCustomAttributes(const char* attr, PyObject *obj)
634
// search in PropertyList
635
Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
637
// Read-only attributes must not be set over its Python interface
638
if(prop->testStatus(Property::Immutable)) {
640
s << "Object attribute '" << attr << "' is read-only";
641
throw Py::AttributeError(s.str());
644
FC_TRACE("Set property " << prop->getFullName());
645
prop->setPyObject(obj);