Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

For any question about plugins!
Post Reply
the_grateful_dawg
Posts: 3
Joined: Sun Jul 19, 2015 10:06 pm

Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by the_grateful_dawg »

Good day,

I am trying to learn how to create Cloud Compare plugins. Currently, I have written simple plug-ins that make use of the computeCloud2CloudDistance(...) and computeCloud2MeshDistance(...) functions from the DistanceComputationTools class. When they execute, the functions return 0, and a new scalar field is generated in the "compared cloud" (in the cloud to cloud case) and the "point cloud" (in the cloud to mesh case). The issue is that the scalar field that is generated is set to 0 for all points in the cloud. The goal of these plugins is to compute the distance between 2 datasets, identical to the pre-built functions in Cloud Compare (without prompting the user with a GUI). Currently, using the cloud-to-cloud and the cloud-to-mesh distance functions in Cloud Compare produces the expected distance scalar field for one of the point clouds. I want to replicate this behaviour in my own plugins. Any help would be appreciated.
daniel
Site Admin
Posts: 7709
Joined: Wed Oct 13, 2010 7:34 am
Location: Grenoble, France
Contact:

Re: Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by daniel »

I'll need to see some code to help you ;)

And I guess you have already looked at the original code? Do you know also that you can perform the same tasks in command line mode?
Daniel, CloudCompare admin
the_grateful_dawg
Posts: 3
Joined: Sun Jul 19, 2015 10:06 pm

Re: Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by the_grateful_dawg »

Hi Daniel,

thanks for the reply. I have played with the command line conversion tool a bit, but I am looking to develop more extensive plugins down the road, and this is my means of learning the CC API and C++. I am going to attach the code for the cloud2cloud distance computation doAction() method.

Code: Select all

void qCloudToCloud::doAction()
{
	//m_app should have already been initialized by CC when plugin is loaded!
	//(--> pure internal check)
	assert(m_app);
	if (!m_app)
		return;

	//Retrieve selected data sets from the DB Tree
	const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
    	size_t selNum = selectedEntities.size();

    	//Create an instance of the DistanceComputationParams
    	CCLib::DistanceComputationTools::Cloud2CloudDistanceComputationParams params;
    	int placeholder = 0;
    	
    	//Create a pointer to store the input cloud
    	CCLib::GenericIndexedCloudPersist* refCloud = 0;
    	
    	//Find the first selected point cloud in the DBTree
	for (int i = 0; i<selNum; ++i)
	{
		ccHObject* ent = selectedEntities[i];
		if (ent->isKindOf(CC_TYPES::POINT_CLOUD))
		{
			m_app->dispToConsole("[qCloudToCloud] POINT CLOUD",ccMainAppInterface::STD_CONSOLE_MESSAGE);
			refCloud = ccHObjectCaster::ToGenericPointCloud(ent);
			placeholder = i;
			break;
		}
	}

	//Find the next instance of a point cloud and compute the cloud to cloud distance
	for (int i = 0; i < selNum; ++i)
	{
		ccHObject* ent = selectedEntities[i];
		if (ent->isKindOf(CC_TYPES::POINT_CLOUD) && i!=placeholder)
		{
			CCLib::GenericIndexedCloudPersist* inputCloud = 0;
			inputCloud = ccHObjectCaster::ToGenericPointCloud(ent);
			int success = CCLib::DistanceComputationTools::computeCloud2CloudDistance(inputCloud, refCloud, params, 0, 0, 0);
			
			//Print out the size of the two selected clouds (sanity check) and the output of the distance computation
			m_app->dispToConsole(QString::number(BIMcloud->size()),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(inputCloud->size()),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(success),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			
		}
	}
}
daniel
Site Admin
Posts: 7709
Joined: Wed Oct 13, 2010 7:34 am
Location: Grenoble, France
Contact:

Re: Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by daniel »

Hard to tell... Using the default parameters should be ok. Maybe it's an issue with the default scalar field. Can you try to explicitely init the SF before calling the computeCloud2CloudDistance method maybe? (with enableScalarField). And afterwards you should call 'updateMinAndMax' on the output scalar field.

Otherwise can you share the full code maybe? (I mean the full plugin - it will be easier for me to debug).
Daniel, CloudCompare admin
the_grateful_dawg
Posts: 3
Joined: Sun Jul 19, 2015 10:06 pm

Re: Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by the_grateful_dawg »

Hi,

here is the full code (which is just the dummy plugin with a modified doAction()):

Code: Select all

//Default constructor: should mainly be used to initialize
//actions (pointers) and other members
qCloudToCloud::qCloudToCloud(QObject* parent/*=0*/)
	: QObject(parent)
	, m_action(0)
{
}

//This method should enable or disable each plugin action
//depending on the currently selected entities ('selectedEntities').
//For example: if none of the selected entities is a cloud, and your
//plugin deals only with clouds, call 'm_action->setEnabled(false)'
void qCloudToCloud::onNewSelection(const ccHObject::Container& selectedEntities)
{
	//if (m_action)
	//	m_action->setEnabled(!selectedEntities.empty());
}

//This method returns all 'actions' of your plugin.
//It will be called only once, when plugin is loaded.
void qCloudToCloud::getActions(QActionGroup& group)
{
	//default action (if it has not been already created, it's the moment to do it)
	if (!m_action)
	{
		//here we use the default plugin name, description and icon,
		//but each action can have its own!
		m_action = new QAction(getName(),this);
		m_action->setToolTip(getDescription());
		m_action->setIcon(getIcon());
		//connect appropriate signal
		connect(m_action, SIGNAL(triggered()), this, SLOT(doAction()));
	}

	group.addAction(m_action);
}

//This is an example of an action's slot called when the corresponding action
//is triggered (i.e. the corresponding icon or menu entry is clicked in CC
//main's interface). You can access to most of CC components (database,
//3D views, console, etc.) via the 'm_app' attribute (ccMainAppInterface
//object).
void qCloudToCloud::doAction()
{
	//m_app should have already been initialized by CC when plugin is loaded!
	//(--> pure internal check)
	assert(m_app);
	if (!m_app)
		return;

	//Retrieve selected data sets from the DB Tree
	const ccHObject::Container& selectedEntities = m_app->getSelectedEntities();
    	size_t selNum = selectedEntities.size();

    	//Create an instance of the DistanceComputationParams
    	CCLib::DistanceComputationTools::Cloud2CloudDistanceComputationParams params;
    	
    	//Create a placeholder for the index of the reference cloud in the DBTree
    	int placeholder = 0;
    	
    	//Create a pointer to store the input cloud
    	CCLib::GenericIndexedCloudPersist* refCloud = 0;
    	
   	//Find the first selected point cloud in the DBTree
	for (int i = 0; i<selNum; ++i)
	{
		ccHObject* ent = selectedEntities[i];
		if (ent->isKindOf(CC_TYPES::POINT_CLOUD))
		{
			m_app->dispToConsole("[qCloudToCloud] POINT CLOUD",ccMainAppInterface::STD_CONSOLE_MESSAGE);
			refCloud = ccHObjectCaster::ToGenericPointCloud(ent);
			placeholder = i;
			break;
		}
	}

	//Find the next instance of a point cloud and compute the cloud to cloud distance
	for (int i = 0; i < selNum; ++i)
	{
		ccHObject* ent = selectedEntities[i];
		if (ent->isKindOf(CC_TYPES::POINT_CLOUD) && i!=placeholder)
		{
			CCLib::GenericIndexedCloudPersist* inputCloud = 0;
			inputCloud = ccHObjectCaster::ToGenericPointCloud(ent);
			inputCloud->enableScalarField();
			int success = CCLib::DistanceComputationTools::computeCloud2CloudDistance(inputCloud, refCloud, params, 0, 0, 0);
			
			//Print out the size of the two selected clouds (sanity check) and the output of the distance computation
			m_app->dispToConsole(QString::number(refCloud->size()),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(inputCloud->size()),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(success),ccMainAppInterface::STD_CONSOLE_MESSAGE);
			
		}
	}
}

//This method should return the plugin icon (it will be used mainly
//if your plugin as several actions in which case CC will create a
//dedicated sub-menu entry with this icon.
QIcon qCloudToCloud::getIcon() const
{
	//open qCloudToCloud.qrc (text file), update the "prefix" and the
	//icon(s) filename(s). Then save it with the right name (yourPlugin.qrc).
	//(eventually, remove the original qCloudToCloud.qrc file!)
	return QIcon(":/CC/plugin/qCloudToCloud/icon.png");
}

#ifndef CC_QT5
//Don't forget to replace 'qCloudToCloud' by your own plugin class name here also!
Q_EXPORT_PLUGIN2(qCloudToCloud,qCloudToCloud);
#endif
As you can see I have called enableScalarField on the point cloud, but I was unable to figure out where to call the updateMinAndMax from/on.
daniel
Site Admin
Posts: 7709
Joined: Wed Oct 13, 2010 7:34 am
Location: Grenoble, France
Contact:

Re: Cloud2CloudDistance and Cloud2MeshDistance: empty scalar field

Post by daniel »

Actually I was expecting the plugin files (I had to create the new plugin structure, update the header file, include the right headers in the cpp file, etc.).

What your plugin is missing here is proper SF management. CCLib performs the distances computation but don't care about the display and other cloud structure specifics. Notably the 'ccPointCloud' structure can handle multiple scalar fields (so you have to explicitly define which SF is 'active' and will be used to store the distances (ccPointCloud::setCurrentScalarField). You also have to call 'computeMinAndMax' on the scalar field (so that all the SF display parameters can be properly setup).

If you rely on the default 'enableScalarField' method you can't easily update and set the displayed scalar field (you have to look afterwards at the cloud SFs and guess which one has been automatically enabled and used for distances computation).

Therefore, to properly handle scalar fields it's better to create them yourself:

Code: Select all

	//Find the next instance of a point cloud and compute the cloud to cloud distance
	for (int i = 1; i < selNum; ++i)
	{
		ccHObject* ent = selectedEntities[i];
		if (ent->isA(CC_TYPES::POINT_CLOUD) && i != placeholder)
		{
			ccPointCloud* inputCloud = ccHObjectCaster::ToPointCloud(ent);
			//create a new SF to store the distances
			//(warning: the name should be unique - normally you should look for existing SFs with
			// the same name before creating a new one - search for 'addScalarField' in CC's code
			// to see an example)
			ccScalarField* sf = new ccScalarField("distances");
			if (!sf->resize(inputCloud->size()))
			{
				m_app->dispToConsole("Not enough memory", ccMainAppInterface::ERR_CONSOLE_MESSAGE);
				sf->release();
				return;
			}
			int sfIdx = inputCloud->addScalarField(sf);
			//make this SF 'active'
			inputCloud->setCurrentScalarField(sfIdx);
			
			int errorCode = CCLib::DistanceComputationTools::computeCloud2CloudDistance(inputCloud, refCloud, params, 0, 0, 0);

			if (errorCode == 0)
			{
				sf->computeMinAndMax();
				//make the SF the visible
				inputCloud->setCurrentDisplayedScalarField(sfIdx);
				inputCloud->showSF(true);
			}
			else
			{
				//if an error occurred, this SF is useless
				inputCloud->deleteScalarField(sfIdx);
			}
			//redraw the GL window!
			inputCloud->redrawDisplay();

			//Print out the size of the two selected clouds (sanity check) and the output of the distance computation
			m_app->dispToConsole(QString::number(refCloud->size()), ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(inputCloud->size()), ccMainAppInterface::STD_CONSOLE_MESSAGE);
			m_app->dispToConsole(QString::number(errorCode), ccMainAppInterface::STD_CONSOLE_MESSAGE);
		}
	}

Daniel, CloudCompare admin
Post Reply