lunedì 15 settembre 2025

Compilare plugin personalizzati in CloudCompare in Debian Trixie

 

Per compilare un plugin per Cloudcompare si deve partire dai sorgenti 

git clone --recursive https://github.com/CloudCompare/CloudCompare

Cloudcompare si puo' compilare solo su Qt5 mentre nelle versioni recenti di Debian il default e' Qt6  (dove non specificato le librerie si possono scaricare dal apt come per esempio geotiff senza dover ricompilare da sorgenti)

sudo apt update
sudo apt install -y \
    git cmake build-essential \
    qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools \
    qttools5-dev qttools5-dev-tools qtscript5-dev \
    libqt5svg5-dev libqt5opengl5-dev \
    libeigen3-dev libboost-all-dev \
    libfftw3-dev libtbb-dev \
    libgdal-dev libproj-dev \
    libpcl-dev \
    libxi-dev libxmu-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev \
    libsqlite3-dev

Uno dei problemi in compilazione e' che Liblas e pdal non sono scaricabili da apt e si devono compilare da sorgenti. LibLas e Pdal dipendono da Boost e sono compatibili con la versione 1.88 pero' Pdal dipende a sua volta da Gdal che su apt e' compilata su Boost 1.83 e cio' ovviamente rompe il sistema

Si deve procedere quindi a compilare Gdal da sorgenti usando Boost 1.88

git clone https://github.com/OSGeo/gdal.git
cd gdal
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release \
    -DBUILD_SHARED_LIBS=ON \
    -DCMAKE_INSTALL_PREFIX=/usr/local
make -j$(nproc)
sudo make install
sudo ldconfig

 

Compilazione di PDAL

git clone  https://github.com/PDAL/PDAL

cd ~/PDAL
mkdir -p build && cd build
rm -rf *

cmake .. \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_INSTALL_PREFIX=/usr/local \
    -DWITH_GDAL=ON \
    -DWITH_LASZIP=ON \
    -DWITH_LAZPERF=ON \
    -DBUILD_SHARED_LIBS=ON \
    -DINSTALL_CMAKE_CONFIG=ON

make -j$(nproc)
sudo make install
sudo ldconfig

 

Compilazione di LibLas (non piu' necessaria nelle nuove versioni)


git clone https://github.com/libLAS/libLAS.git
cd libLAS
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_GDAL=ON -DWITH_GEOTIFF=ON
make -j$(nproc)
sudo make install
sudo ldconfig

Infine per compilare CloudCompare vero e proprio (comprensivo di plugin)

 cmake .. -DCMAKE_BUILD_TYPE=Release -DOPTION_USE_QT5=ON -DPLUGIN_GL_QEDL=ON  -DPLUGIN_GL_QSSAO=ON  -DPLUGIN_IO_QADDITIONAL=ON  -DPLUGIN_IO_QCORE=ON  -DPLUGIN_IO_QPHOTOSCAN=ON  -DPLUGIN_STANDARD_QANIMATION=ON  -DPLUGIN_STANDARD_QBROOM=ON  -DPLUGIN_STANDARD_QCANUPO=ON  -DPLUGIN_STANDARD_QCOMPASS=ON  -DPLUGIN_STANDARD_QFACETS=ON  -DPLUGIN_STANDARD_QHOUGH_NORMALS=ON  -DPLUGIN_STANDARD_QHPR=ON  -DPLUGIN_STANDARD_QM3C2=ON  -DPLUGIN_STANDARD_QPCV=ON  -DPLUGIN_STANDARD_QPOISSON_RECON=ON  -DPLUGIN_STANDARD_QRANSAC_SD=OFF  -DPLUGIN_STANDARD_QSRA=ON  -DEIGEN_ROOT_DIR=/usr/include/eigen


per vedere i plugin attivi si deve fare make install (non usare il binario in build/qCC)
i plugin sono dei file .so che si andranno ad installare in /usr/local/lib/cloudcompare/plugins
 
a questo punto si deve passare a compilare il plugin personalizzato. La via che ho trovato piu' comoda e'copiare il progetto di esempio in CloudCompare/plugins/example/ExamplePlugin in un plugins/core/Standard cambiando il nome del folder

per aggiungere il plugin si deve modificare anche CMakeLists.txt in plugins/core/Standard
Diciamo che il plugin custom si chiamera' qQueryMesh andremo a modificare i vari files del progetto
 
info.json
{
"type" : "Standard",
"name" : "qQueryMesh",
"icon" : ":/CC/plugin/qQueryMesh/images/icon.png",
"description": "Click and get info about the mesh",
"authors" : [
{
"name" : "Luca Innocenti",
"email" : "lucainnoc@gmail.com"
}
],
"maintainers" : [
{
"name" : "Luca Innocenti",
"email" : "lucainnoc@gmail.com"
}
],
"references" : [
{
"text" : "The unsuccessful self-treatment of a case of “writer's block”",
"url" : "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1311997/"
}
]
}


 
 
CMakeLists.txt
# Add an option to CMake to control whether we build this plugin or not
option( PLUGIN_EXAMPLE_STANDARD "Install example plugin" ON )

if ( PLUGIN_EXAMPLE_STANDARD )
project( qQueryMesh )
AddPlugin( NAME ${PROJECT_NAME} )
add_subdirectory( include )
add_subdirectory( src )
# set dependencies to necessary libraries
# target_link_libraries( ${PROJECT_NAME} LIB1 )
endif()
 
 
qQueryMesh.qrc
 
<RCC>
  <qresource prefix="/CC/plugin/qQueryMesh" >
    <file>images/icon.png</file>
    <file>info.json</file>
  </qresource>
</RCC>
 
 
 qQueryMesh.h(deve essere modificato il file CMakeLists.txt in include)
//##########################################################################
//# #
//# CLOUDCOMPARE PLUGIN: ExamplePlugin #
//# #
//# 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; version 2 of the License. #
//# #
//# 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. #
//# #
//# COPYRIGHT: XXX #
//# #
//##########################################################################

#pragma once

#include "ccStdPluginInterface.h"
#include <QObject>
#include <QAction>

class qQueryMesh : public QObject, public ccStdPluginInterface
{
Q_OBJECT
Q_INTERFACES( ccPluginInterface ccStdPluginInterface )
Q_PLUGIN_METADATA( IID "cccorp.cloudcompare.plugin.qQueryMesh" FILE "../info.json" )

public:
explicit qQueryMesh( QObject *parent = nullptr );
~qQueryMesh() override = default;

//explicit qQueryPolygon(QObject* parent = nullptr);
QList<QAction*> getActions() override;
void onNewSelection(const ccHObject::Container& selectedEntities) override;


// Inherited from ccStdPluginInterface
//void onNewSelection( const ccHObject::Container &selectedEntities ) override;
//QList<QAction *> getActions() override;

private:
QAction* m_action;
};


 
 
 qQueryMesh.cpp (deve essere modificato il file CMakeLists.txt in src)
#include <QtGui>

#include "qQueryMesh.h"

#include <ccMesh.h>
#include <ccLog.h>
#include <ccPolyline.h>
#include <ccPointCloud.h>
#include <ccGLWindowInterface.h>

#include <fstream>
#include <cmath>

qQueryMesh::qQueryMesh(QObject *parent)
: QObject(parent)
, ccStdPluginInterface(":/CC/plugin/qQueryMesh/info.json")
, m_action(nullptr)
{
}

void qQueryMesh::onNewSelection(const ccHObject::Container &selectedEntities)
{
if (m_action == nullptr)
{
return;
}

for (ccHObject *entity : selectedEntities)
{
if (!entity)
continue;

// Stampiamo il nome dell'entità
ccLog::Print(QString("Oggetto selezionato: %1").arg(entity->getName()));

// Determiniamo il tipo dell'entità
if (entity->isKindOf(CC_TYPES::MESH))
{
ccLog::Print("Tipo: Mesh");
ccMesh *mesh = static_cast<ccMesh *>(entity);

ccPointCloud *vertices = dynamic_cast<ccPointCloud *>(mesh->getAssociatedCloud());
if (!vertices)
{
ccLog::Print("Errore: impossibile ottenere il PointCloud associato alla mesh");
continue;
}

// Colora tutti i vertici di grigio
ccColor::Rgb color(128, 128, 128);
for (unsigned i = 0; i < vertices->size(); ++i)
vertices->setPointColor(i, color);

// Mostra i colori sulla mesh
mesh->showColors(true);
mesh->prepareDisplayForRefresh();

if (mesh->hasNormals())
{
if (vertices->size() < 3)
continue;

CCVector3 avgNormal(0, 0, 0);
CCVector3 centroid(0, 0, 0);

double A = 0.0;
double B = 0.0;
double C = 0.0;
double D = 0.0;

unsigned triCount = mesh->size();
for (unsigned i = 0; i < triCount; ++i)
{
CCCoreLib::VerticesIndexes *tri = mesh->getTriangleVertIndexes(i);
if (!tri)
continue;

const CCVector3 *pA = vertices->getPoint(tri->i1);
const CCVector3 *pB = vertices->getPoint(tri->i2);
const CCVector3 *pC = vertices->getPoint(tri->i3);

// centroide per il calcolo di D
centroid += (*pA + *pB + *pC) / 3.0f;

// normale triangolo
CCVector3 AB = *pB - *pA;
CCVector3 AC = *pC - *pA;
CCVector3 N = AB.cross(AC);
N.normalize();

avgNormal += N;
}

if (triCount > 0)
{
avgNormal.normalize();
centroid /= static_cast<PointCoordinateType>(triCount);

// piano: Ax + By + Cz + D = 0
A = avgNormal.x;
B = avgNormal.y;
C = avgNormal.z;
D = -(A * centroid.x + B * centroid.y + C * centroid.z);

ccLog::Print(QString("Piano mesh: normale=(%1,%2,%3), D=%4")
.arg(A)
.arg(B)
.arg(C)
.arg(D));
}

// Calcolo della normale media
CCVector3 normalSum(0, 0, 0);
unsigned count = 0;

for (unsigned i = 0; i < mesh->size(); ++i) // numero di triangoli
{
const CCCoreLib::VerticesIndexes *tri = mesh->getTriangleVertIndexes(i);
if (!tri)
continue;

const CCVector3 *p0 = vertices->getPoint(tri->i1);
const CCVector3 *p1 = vertices->getPoint(tri->i2);
const CCVector3 *p2 = vertices->getPoint(tri->i3);

CCVector3 v1 = *p1 - *p0;
CCVector3 v2 = *p2 - *p0;
CCVector3 N = v1.cross(v2);

if (N.norm2() > 0)
{
N.normalize();
normalSum += N;
++count;
}
}

if (count > 0)
{
normalSum /= static_cast<PointCoordinateType>(count);
normalSum.normalize();
ccLog::Print(QString("Normale media: (%1, %2, %3)")
.arg(normalSum.x)
.arg(normalSum.y)
.arg(normalSum.z));
}

// Scrittura in CSV in append
// coseni direttori
double norm = sqrt(A * A + B * B + C * C);
double l = A / norm;
double m = B / norm;
double n = C / norm;

// angoli in radianti → gradi
double alpha = acos(l) * 180.0 / M_PI;
double beta = acos(m) * 180.0 / M_PI;
double gamma = acos(n) * 180.0 / M_PI;

const char *filename = "/home/luca/dati.csv";
bool writeHeader = false;

{
std::ifstream checkFile(filename);
if (!checkFile.good() || checkFile.peek() == std::ifstream::traits_type::eof())
writeHeader = true;
}

std::ofstream file(filename, std::ios::app);
if (file.is_open())
{
if (writeHeader)
{
file << "A,B,C,D,alpha,beta,gamma\n";
}
file << A << "," << B << "," << C << "," << D << ","
<< alpha << "," << beta << "," << gamma << "\n";
file.close();
}
}
else
{
ccLog::Print("Mesh senza normali");
}
}
else if (entity->isKindOf(CC_TYPES::POLY_LINE))
{
ccLog::Print("Tipo: Polilinea");
}
else if (entity->isKindOf(CC_TYPES::POINT_CLOUD))
{
ccLog::Print("Tipo: Point Cloud");
}
else
{
ccLog::Print("Tipo: Altro");
}
}

// abilita se è selezionato un tema poligonale
bool hasPoly = false;
for (ccHObject *obj : selectedEntities)
{
if (obj && obj->isKindOf(CC_TYPES::POLY_LINE))
{
hasPoly = true;
break;
}
}

m_action->setEnabled(hasPoly);
}

QList<QAction *> qQueryMesh::getActions()
{
if (!m_action)
{
m_action = new QAction(getName(), this);
m_action->setToolTip(getDescription());
m_action->setIcon(getIcon());

connect(m_action, &QAction::triggered, this, [this]()
{
if (!m_app)
return;

const ccHObject::Container& selected = m_app->getSelectedEntities();
if (selected.empty())
{
ccLog::Warning("[qQueryPolygon] No polygon selected!");
return;
}

for (ccHObject* obj : selected)
{
if (!obj || !obj->isKindOf(CC_TYPES::POLY_LINE))
continue;

ccPolyline* poly = static_cast<ccPolyline*>(obj);
if (!poly)
continue;

ccPointCloud* vertices = static_cast<ccPointCloud*>(poly->getAssociatedCloud());
if (!vertices)
continue;

// ---- INFO ----
QString info;
info += QString("Polygon: %1\n").arg(poly->getName());
info += QString("Vertices: %1\n").arg(vertices->size());
info += QString("Closed: %1\n").arg(poly->isClosed() ? "Yes" : "No");

PointCoordinateType length = poly->computeLength();
info += QString("\nPerimeter length: %1").arg(length);

ccLog::Print(info);

// ---- HIGHLIGHT ----
poly->setColor(ccColor::red); // change line color
poly->setWidth(4); // make it thicker
poly->showColors(true);

if (m_app->getActiveGLWindow())
m_app->getActiveGLWindow()->redraw();
} });
}

return {m_action};
}


 
 Si controlla che il plugin e' attivo in Help/About Plugins. Poi si aggiunge il seguente parametro a cmake ..
-DPLUGIN_STANDARD_QQUERYMESH=ON
 
infine make e make install. Se tutto e' corretto make, sudo make install avremo il file  libqQueryMesh.so


 

Montagne frattali

 Negli anni 90 quando i miei amici matematici leggevano (Ri)creazioni al calcolatore di Dewdney (vedi anche pag. 107 del libro The Magic Mac...