FreeCAD中对象属性的Python访问机制

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;
}

从上面的代码可以看出,正是在这个函数完成根据属性名映射到了属性扩展类型。

 

参考资料

  1. PyCXX
  2. PyCXX Python3
  3. FreeCADWeb
上一篇:【六】Android MediaPlayer整体架构源码分析 -【start请求播放处理流程】【Part 12】【01】


下一篇:文件上传的实现