FreeCAD中对象属性的Python访问机制
济南友泉软件有限公司
在Part模块一节中,对Part模块的主要组件以及原理进行了较为详细的介绍。但有一个遗留问题,即Python脚本中是如何实现对Part::Feature及其子类的属性访问的呢?Part::Face继承自Part::Feature,因此,可以通过Part::Face为例来说明Python中Part::Face相关属性的访问过程。
一、Python C扩展的原理
在剖析Part::Face代码实现实现之前,需要先要了解一下Python C扩展的原理,具体来说是使用CPython API来扩展Python类型。本节以Part::PartFeaturePy为例先说明Python扩展类型的注册方法。
使用CPython扩展Python类型,实质是通过CPython API向CPython注册类型相关的信息,这是PyTypeObject这个结构体来描述的,
/// Type structure of PartFeaturePy
PyTypeObject PartFeaturePy::Type = {
PyVarObject_HEAD_INIT(&PyType_Type,0)
"Part.Feature", /*tp_name*/
sizeof(PartFeaturePy), /*tp_basicsize*/
0, /*tp_itemsize*/
/* methods */
PyDestructor, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
__repr, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
0, /*tp_call */
0, /*tp_str */
__getattro, /*tp_getattro*/
__setattro, /*tp_setattro*/
/* --- Functions to access object as input/output buffer ---------*/
0, /* tp_as_buffer */
/* --- Flags to define presence of optional/expanded features */
#if PY_MAJOR_VERSION >= 3
Py_TPFLAGS_BASETYPE|Py_TPFLAGS_DEFAULT, /*tp_flags */
#else
Py_TPFLAGS_DEFAULT, /*tp_flags */
#endif
"This is the father of all shape object classes", /*tp_doc */
0, /*tp_traverse */
0, /*tp_clear */
0, /*tp_richcompare */
0, /*tp_weaklistoffset */
0, /*tp_iter */
0, /*tp_iternext */
Part::PartFeaturePy::Methods, /*tp_methods */
0, /*tp_members */
Part::PartFeaturePy::GetterSetter, /*tp_getset */
&App::GeoFeaturePy::Type, /*tp_base */
0, /*tp_dict */
0, /*tp_descr_get */
0, /*tp_descr_set */
0, /*tp_dictoffset */
__PyInit, /*tp_init */
0, /*tp_alloc */
Part::PartFeaturePy::PyMake,/*tp_new */
0, /*tp_free Low-level free-memory routine */
0, /*tp_is_gc For PyObject_IS_GC */
0, /*tp_bases */
0, /*tp_mro method resolution order */
0, /*tp_cache */
0, /*tp_subclasses */
0, /*tp_weaklist */
0, /*tp_del */
0 /*tp_version_tag */
#if PY_MAJOR_VERSION >= 3
,0 /*tp_finalize */
#endif
};
从上面的代码可以看出,PartFeaturePy注册了Base::PyObjectBase::__getattro()与Base::PyObjectBase::__setattro()来获取与设置属性。实际上,当在Python脚本中通过”.”运算符访问Python扩展类型时,调用的就是注册的这两个函数。
二、Part::Face
2.1 获取PartFeaturePy
当在Python脚本中创建Part::Feature对象时,
face1 = App.ActiveDocument.addObject('Part::Face', 'Face1')
由于App.ActiveDocument.addObject()实际关联到DocumentPy::addObject(),而DocumentPy::addObject()则是通过调用getPyObject()返回关联的Python扩展类型。
在FreeCAD中,虽然没有提供与Part::Face直接对应的Python扩展类型,但是通过继承自Part::Feature,Part::Face关联到了Part::PartFeaturePy这个Python扩展类型。
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(Part::FeaturePython, Part::Feature)
template<> const char* Part::FeaturePython::getViewProviderName(void) const {
return "PartGui::ViewProviderPython";
}
template<> PyObject* Part::FeaturePython::getPyObject(void) {
if (PythonObject.is(Py::_None())) {
// ref counter is set to 1
PythonObject = Py::Object(new FeaturePythonPyT<Part::PartFeaturePy>(this),true);
}
return Py::new_reference_to(PythonObject);
}
/// @endcond
// explicit template instantiation
template class PartExport FeaturePythonT<Part::Feature>;
}
2.2 访问属性
从前面的分析可知,如果访问Part::Feature的Sources属性,则会出发调用Base::PyObjectBase::__getattro()函数,最终会调用PropertyContainerPy::getCustomAttributes()这一非虚函数,
PyObject *PropertyContainerPy::getCustomAttributes(const char* attr) const
{
// search in PropertyList
if(FC_LOG_INSTANCE.level()>FC_LOGLEVEL_TRACE) {
FC_TRACE("Get property " << attr);
}
Property *prop = getPropertyContainerPtr()->getPropertyByName(attr);
if (prop) {
PyObject* pyobj = prop->getPyObject();
if (!pyobj && PyErr_Occurred()) {
// the Python exception is already set
throw Py::Exception();
}
return pyobj;
}
else if (Base::streq(attr, "__dict__")) {
// get the properties to the C++ PropertyContainer class
std::map<std::string,App::Property*> Map;
getPropertyContainerPtr()->getPropertyMap(Map);
PyObject *dict = PyDict_New();
if (dict) {
for ( std::map<std::string,App::Property*>::iterator it = Map.begin(); it != Map.end(); ++it )
#if PY_MAJOR_VERSION >= 3
PyDict_SetItem(dict, PyUnicode_FromString(it->first.c_str()), PyUnicode_FromString(""));
#else
PyDict_SetItem(dict, PyString_FromString(it->first.c_str()), PyString_FromString(""));
#endif
if (PyErr_Occurred()) {
Py_DECREF(dict);
dict = NULL;
}
}
return dict;
} else if(Base::streq(attr,"Shape")
&& getPropertyContainerPtr()->isDerivedFrom(App::DocumentObject::getClassTypeId()))
{
// Special treatment of Shape property
static PyObject *_getShape = 0;
if(!_getShape) {
_getShape = Py_None;
PyObject *mod = PyImport_ImportModule("Part");
if(!mod) {
PyErr_Clear();
} else {
Py::Object pyMod = Py::asObject(mod);
if(pyMod.hasAttr("getShape"))
_getShape = Py::new_reference_to(pyMod.getAttr("getShape"));
}
}
if(_getShape != Py_None) {
Py::Tuple args(1);
args.setItem(0,Py::Object(const_cast<PropertyContainerPy*>(this)));
auto res = PyObject_CallObject(_getShape, args.ptr());
if(!res)
PyErr_Clear();
else {
Py::Object pyres(res,true);
if(pyres.hasAttr("isNull")) {
Py::Callable func(pyres.getAttr("isNull"));
if(!func.apply().isTrue())
return Py::new_reference_to(res);
}
}
}
}
return 0;
}
从上面的代码可以看出,正是在这个函数完成根据属性名映射到了属性扩展类型。