void SOP_CortexConverter::doConvert( const GU_DetailHandle &handle, const std::string &name, ResultType type, const std::string &attributeFilter, bool convertStandardAttributes )
{
	if ( handle.isNull() )
	{
		addError( SOP_MESSAGE, ( "Could not extract the geometry named " + name ).c_str() );
		return;
	}
	
	FromHoudiniGeometryConverterPtr fromConverter = FromHoudiniGeometryConverter::create( handle );
	if ( !fromConverter )
	{
		addError( SOP_MESSAGE, ( "Could not convert the geometry named " + name ).c_str() );
		return;
	}
	
	IECore::ObjectPtr result = fromConverter->convert();
	if ( !result )
	{
		addError( SOP_MESSAGE, ( "Could not find Cortex Object named " + name + " on input geometry" ).c_str() );
		return;
	}
	
	if ( IECore::ParameterisedProcedural *procedural = IECore::runTimeCast<IECore::ParameterisedProcedural>( result.get() ) )
	{
		IECore::CapturingRendererPtr renderer = new IECore::CapturingRenderer();
		// We are acquiring and releasing the GIL here to ensure that it is released when we render. This has
		// to be done because a procedural might jump between c++ and python a few times (i.e. if it spawns
		// subprocedurals that are implemented in python). In a normal call to cookMySop, this wouldn't be an
		// issue, but if cookMySop was called from HOM, hou.Node.cook appears to be holding onto the GIL.
		IECorePython::ScopedGILLock gilLock;
		{
			IECorePython::ScopedGILRelease gilRelease;
			{
				IECore::WorldBlock worldBlock( renderer );
				procedural->render( renderer.get() );
			}
		}
		
		result = boost::const_pointer_cast<IECore::Object>( IECore::runTimeCast<const IECore::Object>( renderer->world() ) );
	}
	
	ToHoudiniGeometryConverterPtr converter = ( type == Cortex ) ? new ToHoudiniCortexObjectConverter( result.get() ) : ToHoudiniGeometryConverter::create( result.get() );
	converter->nameParameter()->setTypedValue( name );
	converter->attributeFilterParameter()->setTypedValue( attributeFilter );
	converter->convertStandardAttributesParameter()->setTypedValue( convertStandardAttributes );
	
	if ( !converter->convert( myGdpHandle ) )
	{
		addError( SOP_MESSAGE, ( "Could not convert the Cortex Object named " + name + " to Houdini geometry" ).c_str() );
	}
}
MStatus NumericParameterHandler<T>::doUpdate( IECore::ConstParameterPtr parameter, MPlug &plug ) const
{
	typename IECore::NumericParameter<T>::ConstPtr p = IECore::runTimeCast<const IECore::NumericParameter<T> >( parameter );
	if( !p )
	{
		return MS::kFailure;
	}

	MObject attribute = plug.attribute();
	MFnNumericAttribute fnNAttr( attribute );
	if( !fnNAttr.hasObj( attribute ) )
	{
		return MS::kFailure;
	}
	if( fnNAttr.unitType() != NumericTraits<T>::dataType() )
	{
		return MS::kFailure;
	}

	fnNAttr.setDefault( p->numericDefaultValue() );

	if( p->minValue()!=Imath::limits<T>::min() )
	{
		fnNAttr.setMin( p->minValue() );
	}
	else
	{
		// parameter has no min value
		if( fnNAttr.hasMin() )
		{
			// there is no way of unsetting a minimum value
			// in maya.
			return MS::kFailure;
		}
	}

	if( p->maxValue()!=Imath::limits<T>::max() )
	{
		fnNAttr.setMax( p->maxValue() );
	}
	else
	{
		// parameter has no max value
		if( fnNAttr.hasMax() )
		{
			// there is no way of unsetting a maximum value
			// in maya.
			return MS::kFailure;
		}
	}

	T v;
	MStatus result = plug.getValue( v );
	if( result )
	{
		IECore::ObjectPtr d = new IECore::TypedData<T>( v );
		if( !parameter->valueValid( d.get() ) )
		{
			return MS::kFailure;
		}
	}

	bool keyable = true;
	bool channelBox = true;

	const IECore::ConstCompoundObjectPtr userData = parameter->userData();
	assert( userData );

	const IECore::ConstCompoundObjectPtr maya = userData->member<const IECore::CompoundObject>("maya");
	if (maya)
	{
		const IECore::ConstBoolDataPtr keyableData = maya->member<const IECore::BoolData>("keyable");
		if (keyableData)
		{
			keyable = keyableData->readable();
		}

		const IECore::ConstBoolDataPtr channelBoxData = maya->member<const IECore::BoolData>("channelBox");
		if (channelBoxData)
		{
			channelBox = channelBoxData->readable();
		}
	}

	fnNAttr.setKeyable( keyable );

	// Calling setChannelBox(true) disables keying
	if (!keyable)
	{
		fnNAttr.setChannelBox( channelBox );
	}

	return finishUpdating( parameter, plug );
}