455 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			455 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * ***** BEGIN GPL LICENSE BLOCK *****
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU General Public License
 | |
|  * as published by the Free Software Foundation; either version 2
 | |
|  * of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program; if not, write to the Free Software Foundation,
 | |
|  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | |
|  *
 | |
|  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 | |
|  * All rights reserved.
 | |
|  *
 | |
|  * The Original Code is: all of this file.
 | |
|  *
 | |
|  * Contributor(s): none yet.
 | |
|  *
 | |
|  * ***** END GPL LICENSE BLOCK *****
 | |
|  */
 | |
| 
 | |
| /** \file gameengine/Converter/BL_ArmatureConstraint.cpp
 | |
|  *  \ingroup bgeconv
 | |
|  */
 | |
| 
 | |
| 
 | |
| #include "DNA_constraint_types.h"
 | |
| #include "DNA_action_types.h"
 | |
| #include "BL_ArmatureConstraint.h"
 | |
| #include "BL_ArmatureObject.h"
 | |
| #include "BLI_math.h"
 | |
| #include "BLI_string.h"
 | |
| 
 | |
| #ifdef WITH_PYTHON
 | |
| 
 | |
| PyTypeObject BL_ArmatureConstraint::Type = {
 | |
| 	PyVarObject_HEAD_INIT(NULL, 0)
 | |
| 	"BL_ArmatureConstraint",
 | |
| 	sizeof(PyObjectPlus_Proxy),
 | |
| 	0,
 | |
| 	py_base_dealloc,
 | |
| 	0,
 | |
| 	0,
 | |
| 	0,
 | |
| 	0,
 | |
| 	py_base_repr,
 | |
| 	0,0,0,0,0,0,0,0,0,
 | |
| 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
 | |
| 	0,0,0,0,0,0,0,
 | |
| 	Methods,
 | |
| 	0,
 | |
| 	0,
 | |
| 	&CValue::Type,
 | |
| 	0,0,0,0,0,0,
 | |
| 	py_base_new
 | |
| };
 | |
| 
 | |
| PyObject* BL_ArmatureConstraint::py_repr(void)
 | |
| {
 | |
| 	return PyUnicode_FromString(m_name);
 | |
| }
 | |
| 
 | |
| #endif // WITH_PYTHON
 | |
| 
 | |
| BL_ArmatureConstraint::BL_ArmatureConstraint(
 | |
| 	BL_ArmatureObject *armature, 
 | |
| 	bPoseChannel *posechannel,
 | |
| 	bConstraint *constraint, 
 | |
| 	KX_GameObject* target,
 | |
| 	KX_GameObject* subtarget)
 | |
| 	: PyObjectPlus(), m_constraint(constraint), m_posechannel(posechannel), m_armature(armature)
 | |
| {
 | |
| 	m_target = target;
 | |
| 	m_blendtarget = (target) ? target->GetBlenderObject() : NULL;
 | |
| 	m_subtarget = subtarget;
 | |
| 	m_blendsubtarget = (subtarget) ? subtarget->GetBlenderObject() : NULL;
 | |
| 	m_pose = m_subpose = NULL;
 | |
| 	if (m_blendtarget) {
 | |
| 		copy_m4_m4(m_blendmat, m_blendtarget->obmat);
 | |
| 		if (m_blendtarget->type == OB_ARMATURE)
 | |
| 			m_pose = m_blendtarget->pose;
 | |
| 	}
 | |
| 	if (m_blendsubtarget) {
 | |
| 		copy_m4_m4(m_blendsubmat, m_blendsubtarget->obmat);
 | |
| 		if (m_blendsubtarget->type == OB_ARMATURE)
 | |
| 			m_subpose = m_blendsubtarget->pose;
 | |
| 	}
 | |
| 	if (m_target)
 | |
| 		m_target->RegisterObject(m_armature);
 | |
| 	if (m_subtarget)
 | |
| 		m_subtarget->RegisterObject(m_armature);
 | |
| 	BLI_snprintf(m_name, sizeof(m_name), "%s:%s", m_posechannel->name, m_constraint->name);
 | |
| }
 | |
| 
 | |
| BL_ArmatureConstraint::~BL_ArmatureConstraint()
 | |
| {
 | |
| 	if (m_target)
 | |
| 		m_target->UnregisterObject(m_armature);
 | |
| 	if (m_subtarget)
 | |
| 		m_subtarget->UnregisterObject(m_armature);
 | |
| }
 | |
| 
 | |
| BL_ArmatureConstraint* BL_ArmatureConstraint::GetReplica() const
 | |
| {
 | |
| 	BL_ArmatureConstraint* replica = new BL_ArmatureConstraint(*this);
 | |
| 	replica->ProcessReplica();
 | |
| 	return replica;
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::ReParent(BL_ArmatureObject* armature)
 | |
| {
 | |
| 	m_armature = armature;
 | |
| 	if (m_target)
 | |
| 		m_target->RegisterObject(armature);
 | |
| 	if (m_subtarget)
 | |
| 		m_subtarget->RegisterObject(armature);
 | |
| 	// find the corresponding constraint in the new armature object
 | |
| 	if (m_constraint) {
 | |
| 		bPose* newpose = armature->GetOrigPose();
 | |
| 		char* constraint = m_constraint->name;
 | |
| 		char* posechannel = m_posechannel->name;
 | |
| 		bPoseChannel* pchan;
 | |
| 		bConstraint* pcon;
 | |
| 		m_constraint = NULL;
 | |
| 		m_posechannel = NULL;
 | |
| 		// and locate the constraint
 | |
| 		for (pchan = (bPoseChannel*)newpose->chanbase.first; pchan; pchan=(bPoseChannel*)pchan->next) {
 | |
| 			if (!strcmp(pchan->name, posechannel)) {
 | |
| 				// now locate the constraint
 | |
| 				for (pcon = (bConstraint*)pchan->constraints.first; pcon; pcon=(bConstraint*)pcon->next) {
 | |
| 					if (!strcmp(pcon->name, constraint)) {
 | |
| 						m_constraint = pcon;
 | |
| 						m_posechannel = pchan;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::Relink(CTR_Map<CTR_HashedPtr, void*> *obj_map)
 | |
| {
 | |
| 	void **h_obj = (*obj_map)[m_target];
 | |
| 	if (h_obj) {
 | |
| 		m_target->UnregisterObject(m_armature);
 | |
| 		m_target = (KX_GameObject*)(*h_obj);
 | |
| 		m_target->RegisterObject(m_armature);
 | |
| 	}
 | |
| 	h_obj = (*obj_map)[m_subtarget];
 | |
| 	if (h_obj) {
 | |
| 		m_subtarget->UnregisterObject(m_armature);
 | |
| 		m_subtarget = (KX_GameObject*)(*h_obj);
 | |
| 		m_subtarget->RegisterObject(m_armature);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool BL_ArmatureConstraint::UnlinkObject(SCA_IObject* clientobj)
 | |
| {
 | |
| 	bool res=false;
 | |
| 	if (clientobj == m_target) {
 | |
| 		m_target = NULL;
 | |
| 		res = true;
 | |
| 	}
 | |
| 	if (clientobj == m_subtarget) {
 | |
| 		m_subtarget = NULL;
 | |
| 		res = true;
 | |
| 	}
 | |
| 	return res;
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::UpdateTarget()
 | |
| {
 | |
| 	if (m_constraint && !(m_constraint->flag&CONSTRAINT_OFF) && (!m_blendtarget || m_target)) {
 | |
| 		if (m_blendtarget) {
 | |
| 			// external target, must be updated
 | |
| 			m_target->UpdateBlenderObjectMatrix(m_blendtarget);
 | |
| 			if (m_pose && m_target->GetGameObjectType() == SCA_IObject::OBJ_ARMATURE)
 | |
| 				// update the pose in case a bone is specified in the constraint target
 | |
| 				m_blendtarget->pose = ((BL_ArmatureObject*)m_target)->GetOrigPose();
 | |
| 		}
 | |
| 		if (m_blendsubtarget && m_subtarget) {
 | |
| 			m_subtarget->UpdateBlenderObjectMatrix(m_blendsubtarget);
 | |
| 			if (m_subpose && m_subtarget->GetGameObjectType() == SCA_IObject::OBJ_ARMATURE)
 | |
| 				m_blendsubtarget->pose = ((BL_ArmatureObject*)m_target)->GetOrigPose();
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::RestoreTarget()
 | |
| {
 | |
| 	if (m_constraint && !(m_constraint->flag&CONSTRAINT_OFF) && (!m_blendtarget || m_target)) {
 | |
| 		if (m_blendtarget) {
 | |
| 			copy_m4_m4(m_blendtarget->obmat, m_blendmat);
 | |
| 			if (m_pose)
 | |
| 				m_blendtarget->pose = m_pose;
 | |
| 		}
 | |
| 		if (m_blendsubtarget && m_subtarget) {
 | |
| 			copy_m4_m4(m_blendsubtarget->obmat, m_blendsubmat);
 | |
| 			if (m_subpose)
 | |
| 				m_blendsubtarget->pose = m_subpose;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool BL_ArmatureConstraint::Match(const char* posechannel, const char* constraint)
 | |
| {
 | |
| 	return (!strcmp(m_posechannel->name, posechannel) && !strcmp(m_constraint->name, constraint));
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::SetTarget(KX_GameObject* target)
 | |
| {
 | |
| 	if (m_blendtarget) {
 | |
| 		if (target != m_target) {
 | |
| 			m_target->UnregisterObject(m_armature);
 | |
| 			m_target = target;
 | |
| 			if (m_target)
 | |
| 				m_target->RegisterObject(m_armature);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| void BL_ArmatureConstraint::SetSubtarget(KX_GameObject* subtarget)
 | |
| {
 | |
| 	if (m_blendsubtarget) {
 | |
| 		if (subtarget != m_subtarget) {
 | |
| 			m_subtarget->UnregisterObject(m_armature);
 | |
| 			m_subtarget = subtarget;
 | |
| 			if (m_subtarget)
 | |
| 				m_subtarget->RegisterObject(m_armature);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| #ifdef WITH_PYTHON
 | |
| 
 | |
| // PYTHON
 | |
| 
 | |
| PyMethodDef BL_ArmatureConstraint::Methods[] = {
 | |
|   {NULL,NULL} //Sentinel
 | |
| };
 | |
| 
 | |
| // order of definition of attributes, must match Attributes[] array
 | |
| #define BCA_TYPE		0
 | |
| #define BCA_NAME		1
 | |
| #define BCA_ENFORCE		2
 | |
| #define BCA_HEADTAIL	3
 | |
| #define BCA_LINERROR	4
 | |
| #define BCA_ROTERROR	5
 | |
| #define BCA_TARGET		6
 | |
| #define BCA_SUBTARGET	7
 | |
| #define BCA_ACTIVE		8
 | |
| #define BCA_IKWEIGHT	9
 | |
| #define BCA_IKTYPE		10
 | |
| #define BCA_IKFLAG		11
 | |
| #define BCA_IKDIST		12
 | |
| #define BCA_IKMODE		13
 | |
| 
 | |
| PyAttributeDef BL_ArmatureConstraint::Attributes[] = {
 | |
| 	// Keep these attributes in order of BCA_ defines!!! used by py_attr_getattr and py_attr_setattr
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("type",BL_ArmatureConstraint,py_attr_getattr),	
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("name",BL_ArmatureConstraint,py_attr_getattr),	
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("enforce",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("headtail",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("lin_error",BL_ArmatureConstraint,py_attr_getattr),
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("rot_error",BL_ArmatureConstraint,py_attr_getattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("target",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("subtarget",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("active",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("ik_weight",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("ik_type",BL_ArmatureConstraint,py_attr_getattr),
 | |
| 	KX_PYATTRIBUTE_RO_FUNCTION("ik_flag",BL_ArmatureConstraint,py_attr_getattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("ik_dist",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	KX_PYATTRIBUTE_RW_FUNCTION("ik_mode",BL_ArmatureConstraint,py_attr_getattr,py_attr_setattr),
 | |
| 	
 | |
| 	{ NULL }	//Sentinel
 | |
| };
 | |
| 
 | |
| 
 | |
| PyObject* BL_ArmatureConstraint::py_attr_getattr(void *self_v, const struct KX_PYATTRIBUTE_DEF *attrdef)
 | |
| {
 | |
| 	BL_ArmatureConstraint* self= static_cast<BL_ArmatureConstraint*>(self_v);
 | |
| 	bConstraint* constraint = self->m_constraint;
 | |
| 	bKinematicConstraint* ikconstraint = (constraint && constraint->type == CONSTRAINT_TYPE_KINEMATIC) ? (bKinematicConstraint*)constraint->data : NULL;
 | |
| 	int attr_order = attrdef-Attributes;
 | |
| 
 | |
| 	if (!constraint) {
 | |
| 		PyErr_SetString(PyExc_AttributeError, "constraint is NULL");
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	switch (attr_order) {
 | |
| 	case BCA_TYPE:
 | |
| 		return PyLong_FromLong(constraint->type);
 | |
| 	case BCA_NAME:
 | |
| 		return PyUnicode_FromString(constraint->name);
 | |
| 	case BCA_ENFORCE:
 | |
| 		return PyFloat_FromDouble(constraint->enforce);
 | |
| 	case BCA_HEADTAIL:
 | |
| 		return PyFloat_FromDouble(constraint->headtail);
 | |
| 	case BCA_LINERROR:
 | |
| 		return PyFloat_FromDouble(constraint->lin_error);
 | |
| 	case BCA_ROTERROR:
 | |
| 		return PyFloat_FromDouble(constraint->rot_error);
 | |
| 	case BCA_TARGET:
 | |
| 		if (!self->m_target)	
 | |
| 			Py_RETURN_NONE;
 | |
| 		else
 | |
| 			return self->m_target->GetProxy();
 | |
| 	case BCA_SUBTARGET:
 | |
| 		if (!self->m_subtarget)	
 | |
| 			Py_RETURN_NONE;
 | |
| 		else
 | |
| 			return self->m_subtarget->GetProxy();
 | |
| 	case BCA_ACTIVE:
 | |
| 		return PyBool_FromLong(constraint->flag & CONSTRAINT_OFF);
 | |
| 	case BCA_IKWEIGHT:
 | |
| 	case BCA_IKTYPE:
 | |
| 	case BCA_IKFLAG:
 | |
| 	case BCA_IKDIST:
 | |
| 	case BCA_IKMODE:
 | |
| 		if (!ikconstraint) {
 | |
| 			PyErr_SetString(PyExc_AttributeError, "constraint is not of IK type");
 | |
| 			return NULL;
 | |
| 		}
 | |
| 		switch (attr_order) {
 | |
| 		case BCA_IKWEIGHT:
 | |
| 			return PyFloat_FromDouble((ikconstraint)?ikconstraint->weight : 0.0f);
 | |
| 		case BCA_IKTYPE:
 | |
| 			return PyLong_FromLong(ikconstraint->type);
 | |
| 		case BCA_IKFLAG:
 | |
| 			return PyLong_FromLong(ikconstraint->flag);
 | |
| 		case BCA_IKDIST:
 | |
| 			return PyFloat_FromDouble(ikconstraint->dist);
 | |
| 		case BCA_IKMODE:
 | |
| 			return PyLong_FromLong(ikconstraint->mode);
 | |
| 		}
 | |
| 		// should not come here
 | |
| 		break;
 | |
| 	}
 | |
| 	PyErr_SetString(PyExc_AttributeError, "constraint unknown attribute");
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| int BL_ArmatureConstraint::py_attr_setattr(void *self_v, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
 | |
| {
 | |
| 	BL_ArmatureConstraint* self= static_cast<BL_ArmatureConstraint*>(self_v);
 | |
| 	bConstraint* constraint = self->m_constraint;
 | |
| 	bKinematicConstraint* ikconstraint = (constraint && constraint->type == CONSTRAINT_TYPE_KINEMATIC) ? (bKinematicConstraint*)constraint->data : NULL;
 | |
| 	int attr_order = attrdef-Attributes;
 | |
| 	int ival;
 | |
| 	double dval;
 | |
| //	char* sval;
 | |
| 	KX_GameObject *oval;
 | |
| 
 | |
| 	if (!constraint) {
 | |
| 		PyErr_SetString(PyExc_AttributeError, "constraint is NULL");
 | |
| 		return PY_SET_ATTR_FAIL;
 | |
| 	}
 | |
| 	
 | |
| 	switch (attr_order) {
 | |
| 	case BCA_ENFORCE:
 | |
| 		dval = PyFloat_AsDouble(value);
 | |
| 		if (dval < 0.0 || dval > 1.0) { /* also accounts for non float */
 | |
| 			PyErr_SetString(PyExc_AttributeError, "constraint.enforce = float: BL_ArmatureConstraint, expected a float between 0 and 1");
 | |
| 			return PY_SET_ATTR_FAIL;
 | |
| 		}
 | |
| 		constraint->enforce = dval;
 | |
| 		return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 	case BCA_HEADTAIL:
 | |
| 		dval = PyFloat_AsDouble(value);
 | |
| 		if (dval < 0.0 || dval > 1.0) { /* also accounts for non float */
 | |
| 			PyErr_SetString(PyExc_AttributeError, "constraint.headtail = float: BL_ArmatureConstraint, expected a float between 0 and 1");
 | |
| 			return PY_SET_ATTR_FAIL;
 | |
| 		}
 | |
| 		constraint->headtail = dval;
 | |
| 		return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 	case BCA_TARGET:
 | |
| 		if (!ConvertPythonToGameObject(value, &oval, true, "constraint.target = value: BL_ArmatureConstraint"))
 | |
| 			return PY_SET_ATTR_FAIL; // ConvertPythonToGameObject sets the error
 | |
| 		self->SetTarget(oval);
 | |
| 		return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 	case BCA_SUBTARGET:
 | |
| 		if (!ConvertPythonToGameObject(value, &oval, true, "constraint.subtarget = value: BL_ArmatureConstraint"))
 | |
| 			return PY_SET_ATTR_FAIL; // ConvertPythonToGameObject sets the error
 | |
| 		self->SetSubtarget(oval);
 | |
| 		return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 	case BCA_ACTIVE:
 | |
| 		ival = PyObject_IsTrue( value );
 | |
| 		if (ival == -1) {
 | |
| 			PyErr_SetString(PyExc_AttributeError, "constraint.active = bool: BL_ArmatureConstraint, expected True or False");
 | |
| 			return PY_SET_ATTR_FAIL;
 | |
| 		}
 | |
| 		self->m_constraint->flag = (self->m_constraint->flag & ~CONSTRAINT_OFF) | ((ival)?0:CONSTRAINT_OFF);
 | |
| 		return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 	case BCA_IKWEIGHT:
 | |
| 	case BCA_IKDIST:
 | |
| 	case BCA_IKMODE:
 | |
| 		if (!ikconstraint) {
 | |
| 			PyErr_SetString(PyExc_AttributeError, "constraint is not of IK type");
 | |
| 			return PY_SET_ATTR_FAIL;
 | |
| 		}
 | |
| 		switch (attr_order) {
 | |
| 		case BCA_IKWEIGHT:
 | |
| 			dval = PyFloat_AsDouble(value);
 | |
| 			if (dval < 0.0 || dval > 1.0) { /* also accounts for non float */
 | |
| 				PyErr_SetString(PyExc_AttributeError, "constraint.weight = float: BL_ArmatureConstraint, expected a float between 0 and 1");
 | |
| 				return PY_SET_ATTR_FAIL;
 | |
| 			}
 | |
| 			ikconstraint->weight = dval;
 | |
| 			return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 		case BCA_IKDIST:
 | |
| 			dval = PyFloat_AsDouble(value);
 | |
| 			if (dval < 0.0) {  /* also accounts for non float */
 | |
| 				PyErr_SetString(PyExc_AttributeError, "constraint.ik_dist = float: BL_ArmatureConstraint, expected a positive float");
 | |
| 				return PY_SET_ATTR_FAIL;
 | |
| 			}
 | |
| 			ikconstraint->dist = dval;
 | |
| 			return PY_SET_ATTR_SUCCESS;
 | |
| 
 | |
| 		case BCA_IKMODE:
 | |
| 			ival = PyLong_AsLong(value);
 | |
| 			if (ival < 0) {
 | |
| 				PyErr_SetString(PyExc_AttributeError, "constraint.ik_mode = integer: BL_ArmatureConstraint, expected a positive integer");
 | |
| 				return PY_SET_ATTR_FAIL;
 | |
| 			}
 | |
| 			ikconstraint->mode = ival;
 | |
| 			return PY_SET_ATTR_SUCCESS;
 | |
| 		}
 | |
| 		// should not come here
 | |
| 		break;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	PyErr_SetString(PyExc_AttributeError, "constraint unknown attribute");
 | |
| 	return PY_SET_ATTR_FAIL;
 | |
| }
 | |
| 
 | |
| #endif // WITH_PYTHON
 |