Examples

Creating a ccPointCloud from Python

import pycc
import laspy
import numpy as np

# This example shows how to create a ccPointCloud from your custom data
# here, we will load data from a las file using laspy.

# Note that the resulting ccPointCloud's point values and scalar field values
# will be copies.

path_to_las = r"L:\R1_F_0+000_0+050.las"

las = laspy.read(path_to_las)

# Be aware that CloudCompare stores coordinates on 32 bit floats.
# To avoid losing too much precision you should 'shift' your coordinates
# if they are 64 bit floats (which is the default in python land)

xs = (las.x - las.header.x_min).astype(pycc.PointCoordinateType)
ys = (las.y - las.header.y_min).astype(pycc.PointCoordinateType)
zs = (las.z - las.header.z_min).astype(pycc.PointCoordinateType)

point_cloud = pycc.ccPointCloud(xs, ys, zs)
# Add the global shift to CloudCompare so that it can use it,
# for example to display the real coordinates in point picking tool
point_cloud.setGlobalShift(-las.header.x_min, -las.header.y_min, -las.header.z_min)
point_cloud.setName(path_to_las)
print(point_cloud.size())

assert np.all(xs == point_cloud.points()[..., 0])

# Adding scalar field & copying values the manual way
idx = point_cloud.addScalarField("classification")

classification_array = point_cloud.getScalarField(idx).asArray()
classification_array[:] = las.classification[:]
print(classification_array)

# Or give the values directly
idx = point_cloud.addScalarField("intensity", las.intensity)
intensity_array = point_cloud.getScalarField(idx).asArray()
print(intensity_array[:])



try:
    cc = pycc.GetInstance()
except AttributeError:
    exit(0)
# We are in "embedded mode", add the point cloud to the DB
# but first we should compute min and max for the scalarfields
# so that their color ranges displays properly
point_cloud.getScalarField(point_cloud.getScalarFieldIndexByName("intensity")).computeMinAndMax()
point_cloud.getScalarField(point_cloud.getScalarFieldIndexByName("classification")).computeMinAndMax()

cc.addToDB(point_cloud)
cc.updateUI()

Using ccMesh

"""
An example of how to create a mesh or query a mesh
"""
import cccorelib
import pycc
import numpy as np

# Vertices of the cube
VERTICES = np.array([
    [-0.5, -0.5, -0.5],
    [-0.5, -0.5, 0.5],
    [-0.5, 0.5, -0.5],
    [-0.5, 0.5, 0.5],
    [0.5, -0.5, -0.5],
    [0.5, -0.5, 0.5],
    [0.5, 0.5, -0.5],
    [0.5, 0.5, 0.5]
])

# Indices of the cube
INDICES = np.array([
    [0, 1, 3],
    [0, 3, 2],
    [0, 2, 4],
    [2, 6, 4],
    [0, 4, 1],
    [1, 4, 5],
    [2, 3, 6],
    [3, 7, 6],
    [4, 6, 5],
    [5, 6, 7],
    [1, 5, 7],
    [1, 7, 3]
])


def main():
    # Creating mesh
    vertices = pycc.ccPointCloud(VERTICES[:, 0], VERTICES[:, 1], VERTICES[:, 2])
    mesh = pycc.ccMesh(vertices)
    for (i1, i2, i3) in INDICES:
        mesh.addTriangle(i1, i2, i3)

    assert vertices.size() == len(VERTICES)
    assert mesh.size() == len(INDICES)
    assert vertices == mesh.getAssociatedCloud()

    # Query triangles
    x, y, z = cccorelib.CCVector3(), cccorelib.CCVector3(), cccorelib.CCVector3()
    for i in range(mesh.size()):
        vert_indices = mesh.getTriangleVertIndexes(i)
        assert vert_indices.i1 == INDICES[i, 0]
        assert vert_indices.i2 == INDICES[i, 1]
        assert vert_indices.i3 == INDICES[i, 2]

        for j in range(3):
            assert vert_indices[j] == INDICES[i, j]

        vertices.getPoint(vert_indices.i1, x)
        vertices.getPoint(vert_indices.i2, y)
        vertices.getPoint(vert_indices.i3, z)

        # or
        mesh.getTriangleVertices(i, x, y, z)

    CC = pycc.GetInstance()
    CC.addToDB(mesh)
    CC.updateUI()

if __name__ == '__main__':
    main()

Loading a file

import pycc

CC = pycc.GetInstance()

params = pycc.FileIOFilter.LoadParameters()
params.parentWidget = CC.getMainWindow()

path = r"~\Projects\CloudCompare\plugins\private\CloudCompare-PythonPlugin\tests\data\a_cloud.bin"


# This automatically adds the loaded entities
# to the DB on success, and raises an exception if an error occured
obj = CC.loadFile(path, params)


# This does not add to the DB, and returns None if the loading failed
obj = pycc.FileIOFilter.LoadFromFile(path, params)

if obj is not None:
    print("Failed to load the file")
    CC.addToDB(obj)

params = pycc.FileIOFilter.SaveParameters()
result = pycc.FileIOFilter.SaveToFile(obj, r'savedFromPlugin.bin', params)
result = pycc.FileIOFilter.SaveToFile(obj.getChild(0), r'savedFromPlugin.laz', params)

Sub sampling

import cccorelib
import pycc

CC = pycc.GetInstance()


def doSubSampling(pc):
    refcloud = cccorelib.CloudSamplingTools.subsampleCloudRandomly(pc, pc.size() // 2)
    randomPc = pc.partialClone(refcloud)
    randomPc.setName("Randomly subsampled")
    CC.addToDB(randomPc)

    refcloud = cccorelib.CloudSamplingTools.subsampleCloudWithOctree(pc, pc.size() // 4,
                                                                     cccorelib.CloudSamplingTools.RANDOM_POINT)
    randomPc = pc.partialClone(refcloud)
    randomPc.setName("Subsampled using octree (RANDOM_POINT)")
    CC.addToDB(randomPc)

    refcloud = cccorelib.CloudSamplingTools.subsampleCloudWithOctree(pc, pc.size() // 4,
                                                                     cccorelib.CloudSamplingTools.NEAREST_POINT_TO_CELL_CENTER)
    randomPc = pc.partialClone(refcloud)
    randomPc.setName("Subsampled using octree (NEAREST_POINT_TO_CELL_CENTER)")
    CC.addToDB(randomPc)


def main():
    entities = CC.getSelectedEntities()
    print(f"Selected entities: {entities}")

    if not entities:
        raise RuntimeError("No entities selected")

    pointCloud = entities[0]
    print(pointCloud)

    pycc.RunInThread(doSubSampling, pointCloud)

    CC.updateUI()


if __name__ == '__main__':
    main()

Merge

import pycc


def merge(clouds):
    total_num_points = sum(cloud.size() for cloud in clouds)

    merge_result = pycc.ccPointCloud("MergeResult")
    merge_result.reserve(total_num_points)

    for cloud_idx, cloud in enumerate(clouds):
        for point_idx in range(cloud.size()):
            merge_result.addPoint(cloud.getPoint(point_idx))

    pos = 0
    for cloud in clouds:
        for i in range(cloud.getNumberOfScalarFields()):
            scalarFieldName = cloud.getScalarFieldName(i)
            idx = merge_result.getScalarFieldIndexByName(scalarFieldName)

            if idx == -1:
                idx = merge_result.addScalarField(scalarFieldName)
                if idx == -1:
                    raise RuntimeError("Failed to add ScalarField")

            scalarField = cloud.getScalarField(i)
            sf = merge_result.getScalarField(idx)
            sf.asArray()[pos: pos + scalarField.size()] = scalarField.asArray()[:]
            sf.computeMinAndMax()

        pos += cloud.size()

    return merge_result


def main():
    CC = pycc.GetInstance()
    clouds = CC.getSelectedEntities()

    merged_cloud = merge(clouds)
    CC.addToDB(merged_cloud)


if __name__ == '__main__':
    main()

Crop2D

import pycc
import cccorelib

CC = pycc.GetInstance()
pc_to_crop = CC.getSelectedEntities()[0]
bbMin, bbMax = cccorelib.CCVector3(), cccorelib.CCVector3()
pc_to_crop.getBoundingBox(bbMin, bbMax)
print(f"Min {bbMin}, Max: {bbMax}")
diag = bbMax - bbMin

bbMin = bbMin + cccorelib.CCVector3(diag.x / 2, 0, 0)

vertices = pycc.ccPointCloud()
vertices.setName("polyline.vertices")
vertices.addPoint(bbMin)
vertices.addPoint(bbMin + cccorelib.CCVector3(0, diag.y / 2, 0))
vertices.addPoint(bbMin + cccorelib.CCVector3(diag.x / 2, diag.y / 2, 0))
vertices.addPoint(bbMin + cccorelib.CCVector3(diag.x / 2, 0, 0))

polyline = pycc.ccPolyline(vertices)
polyline.setClosed(True)
# This is important, otherwise the polyline would have a size of 0
polyline.addPointIndex(0, vertices.size())

# To see the crop area
# CC.addToDB(vertices)

cropped_ref = pc_to_crop.crop2D(polyline, 2, True)
if cropped_ref is None:
    raise RuntimeError("Failed to crop")

if cropped_ref.size() != 0:
    cropped = pc_to_crop.partialClone(cropped_ref)
    CC.addToDB(cropped)
else:
    print("No points fall in crop area")

Using OpenGL Transformations

Translation

"""
This scripts applies a translation of the first selected entity.
It's an OpenGL translation, so coordinates are not changed, its just visual
"""
import pycc
import cccorelib
import math


entity = pycc.GetInstance().getSelectedEntities()[0]

# Translating the entity

glMat = entity.getGLTransformation()
translation = glMat.getTranslationAsVec3D()
translation.x += 10.0
glMat.setTranslation(translation)

entity.setGLTransformation(glMat)
entity.applyGLTransformation_recursive()
pycc.GetInstance().redrawAll()

Rotation

"""
This scripts applies a rotation of the first selected entity.
It's an OpenGL rotation, so coordinates are not changed, its just visual
"""

import pycc
import cccorelib
import math


entity = pycc.GetInstance().getSelectedEntities()[0]

# Rotating the entity around its center
# Rotation always happen in OpenGL`s origin which is probably not
# the center of our object
#
# So we have to:
# 1 - Translate the matrix by -center of the object
# 2 - Rotate the matrix by the desired amount
# 3 - Translate the matrix by centre of the object

center = entity.getDisplayBB_recursive(True).getCenter()

# 1
glTrans = entity.getGLTransformation()
translation = glTrans.getTranslationAsVec3D()
translation = translation - center
glTrans.setTranslation(translation)

entity.setGLTransformation(glTrans)
entity.applyGLTransformation_recursive()

# 2 & 3
glRot = pycc.ccGLMatrix()
glRot.initFromParameters(
    math.radians(45),
    cccorelib.CCVector3(0.0, 0.0, 1.0),
    cccorelib.CCVector3(0.0, 0.0, 0.0)
)

glMat = entity.getGLTransformation()
glMat = glMat * glRot;

translation = glMat.getTranslationAsVec3D()
translation = translation + center
glMat.setTranslation(translation)

entity.setGLTransformation(glMat)
entity.applyGLTransformation_recursive()

pycc.GetInstance().redrawAll()