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)
Scalar Field
"""Example that shows how to add a scalar field
as well as how to make it properly be displayed in the UI
"""
import pycc
import numpy as np
xs = np.arange(0.0, 5.0, 0.1, pycc.PointCoordinateType)
ys = np.ones(len(xs), pycc.PointCoordinateType) * 18.5
zs = np.ones(len(xs), pycc.PointCoordinateType) * 17.33
pc = pycc.ccPointCloud(xs, ys, zs)
# Create and add a new scalar field
idx = pc.addScalarField("Intensity")
scalar_field = pc.getScalarField(idx)
# Change the values
sfArray = pc.getScalarField(idx).asArray()
num_chunk = 4
chunk_size = len(xs) // num_chunk
for i in range(num_chunk):
sfArray[i*chunk_size:(i+1)*chunk_size] = i
# Make it so the scalar field is displayed
scalar_field.computeMinAndMax()
pc.setCurrentDisplayedScalarField(idx)
pc.showSF(True)
# Optional: choose the color scale
scale = pycc.ccColorScalesManager.GetDefaultScale(
pycc.ccColorScalesManager.YELLOW_BROWN
)
scalar_field.setColorScale(scale)
pc.setName("Scalar Field Example")
# Change the point size so that the cloud is more
# visible by default (only needed here as the point cloud
# is very small)
pc.setPointSize(7)
pycc.GetInstance().addToDB(pc)
PointCloud Colors
"""
An example on how to create a point cloud and add colors
And how to get colors of a point clouds
"""
import pycc
import numpy as np
CC = pycc.GetInstance()
points = np.array([
[0, 0, 0],
[1, 1, 0],
[2, 2, 0]
])
cloud = pycc.ccPointCloud(points[:, 0], points[:, 1], points[:, 2])
cloud.setName("Colored Cloud 1")
cloud.setPointSize(9) # So that the point are visible
# This point cloud does not yet have colors:
assert cloud.hasColors() == False
assert cloud.colors() is None
red = pycc.Rgb(255, 0, 0)
green = pycc.Rgb(0, 255, 0)
blue = pycc.Rgb(0, 0, 255)
# This needs to be called after points have been added
cloud.resizeTheRGBTable()
cloud.setPointColor(0, red)
cloud.setPointColor(1, green)
cloud.setPointColor(2, blue)
# So that the colors are show by default
# without having to select in the UI
cloud.showColors(True)
print(f"First point color: {cloud.getPointColor(0)}")
color = cloud.getPointColor(1)
print(f"Second point r: {color.r}, g: {color.g}, b: {color.b}, a: {color.a}")
# Get the colors as a numpy array
colors = cloud.colors()
print(colors)
# This array is a view so modifications made on it will reflect on
# the point cloud
colors[0][:] = [255, 0, 255, 255]
CC.addToDB(cloud)
# Another way is to use the setColors function
points = np.array([
[3, 3, 0],
[4, 4, 0],
[5, 5, 0]
])
cloud = pycc.ccPointCloud(points[:, 0], points[:, 1], points[:, 2])
cloud.setName("Colored Cloud 2")
cloud.setPointSize(9) # So that the point are visible
pointColors = np.array([[255, 0, 0], [0, 255, 0], [0, 0, 255]])
cloud.setColors(pointColors)
cloud.showColors(True)
# We can then also get colors as an array
colors = cloud.colors()
colors[0][:] = [255, 0, 255, 255]
CC.addToDB(cloud)
PointCloud Normals
"""
An example of creating a ploint cloud and adding normals, and getting
normals stored in it
"""
import pycc
import cccorelib
import numpy as np
points = np.array([
[0, 0, 0],
[1, 1, 0],
[2, 2, 0]
])
cloud = pycc.ccPointCloud(points[:, 0], points[:, 1], points[:, 2])
# This point cloud does not yet have colors:
assert cloud.hasNormals() == False
assert cloud.normals() is None
norm1 = cccorelib.CCVector3(1, 1, 1)
norm2 = cccorelib.CCVector3(2, 2, 2)
norm3 = cccorelib.CCVector3(3, 3, 3)
print("Some vectors:", norm1, norm2, norm3)
# Explicitely normalize (if not done, setPointNormal will)
norm1.normalize()
norm2.normalize()
norm3.normalize()
print("Normalized normals", norm1, norm2, norm3)
cloud.resizeTheNormsTable()
cloud.setPointNormal(0, norm1)
cloud.setPointNormal(1, norm2)
cloud.setPointNormal(2, norm3)
print("Normals using getPointNormal:",
cloud.getPointNormal(0),
cloud.getPointNormal(1),
cloud.getPointNormal(2)
)
normals = cloud.normals()
print("Normals as array:", normals)
normals[0][:] = [0, 0, 0]
# normals is a copy, so changing it did not reflect of the point cloud
print("Normals:", cloud.normals())
# Another way is to use setNormals
points = np.array([
[0, 0, 0],
[1, 1, 0],
[2, 2, 0]
])
cloud = pycc.ccPointCloud(points[:, 0], points[:, 1], points[:, 2])
# This point cloud does not yet have colors:
assert cloud.hasNormals() == False
assert cloud.normals() is None
normals = np.array([
[0.57735026, 0.57735026, 0.57735026],
[0.57735026, 0.57735026, 0.57735026],
[0.57735026, 0.57735026, 0.57735026],
])
# Note that this takes care of calling resizeTheNormsTable
cloud.setNormals(normals)
print(cloud.normals())
Polyline
import pycc
import cccorelib
import numpy as np
CC = pycc.GetInstance()
VERTICES = np.array([
[-0.5, -0.5, 0],
[1, 1, 0],
[2, 2, 0]
])
vertices = pycc.ccPointCloud(VERTICES[:, 0], VERTICES[:, 1], VERTICES[:, 2])
polyline = pycc.ccPolyline(vertices)
polyline.setColor(pycc.Rgb(255, 0, 0)) # set the color to red
polyline.showColors(True)
polyline.setClosed(False)
# This is important, otherwise the polyline would have a size of 0
polyline.addPointIndex(0, 3)
CC.addToDB(polyline)
CC.updateUI()
polyline.getDisplay().display3DLabel("Hello, world", cccorelib.CCVector3(1, 1,0))
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(polyline)
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()
Picking
This is shown as a plugin as it is required to be able to have a start/stop mechanism
import pycc
MAIN_ICON_BYTES = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00rIDATX\x85\xed\xd5\xb1\r\x80 \x10\x85\xe1\xdf\x0b\x05\x03XX\xb8\xae\xab\xb8\x8e\xa5k\x18\x060\x94Z\x90\xd0x\x9e\x89\xefU\x84\xe6>\xc8#\x0c\xeb\xbel\xc0\x04$\xc0\xb8r_\xb7r>\xb0W\x120\x03cg\x98Wr\xef\x94\xee1\xfaW\xed\x0e\x08\x8d\x00\xea\x80\x00\x02\x18\xed\xcf\xe2U@h\x04\x10@\xaf@\x80O\x00TB\x01\xc2\x01\xff.a\x02\n\x90\x83\xe6\x1f\x15\x88\xd1\x0e=Zl\x86\xd1\x00\x00\x00\x00IEND\xaeB`\x82'
ICON_BYTES = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\x00\x00\x00\tpHYs\x00\x00\x0e\xc4\x00\x00\x0e\xc4\x01\x95+\x0e\x1b\x00\x00\x00nIDATX\x85\xed\xd51\x0e\x80 \x10D\xd1/\xd9\x82\x03XXx\xff\xebx\x15\xc3\x01\xc4R\x0b\x92m\\\xd7\xc4\x99\x8a\xd0\xec\x83\x0ca\xea\x9d\rX\x00\x03\nW\xee\xebQ\x8e\x07\xf6\x9a\x01+0;\xc3\xa2R\xbdS\x86\xa7\xe0_u8 5\x02\xa8\x03\x02\x08P\x18\x7f\x16\xaf\x02R#\x80\x00z\x05\x02|\x02\xa0\x12\n\x90\x0e\xf8w\t\rh@M\x9a\xbf\x9fI7\x0e9\x19U)\xd1\x00\x00\x00\x00IEND\xaeB`\x82'
class PickingListener(pycc.ccPickingListener):
def __init__(self):
super().__init__()
def onItemPicked(self, item):
# Use ccLog instead of Python's print to display messages
# in CC's console
pycc.ccLog.Print("onItemPicked called")
pycc.ccLog.Print(f"clickPoint: {item.clickPoint.x(), item.clickPoint.y()}")
pycc.ccLog.Print(f"entity: {item.entity}")
pycc.ccLog.Print(f"itemIndex: {item.itemIndex}")
pycc.ccLog.Print(f"P3D: {item.P3D}")
pycc.ccLog.Print(f"uvw: {item.uvw}")
pycc.ccLog.Print(f"entityCenter: {item.entityCenter}")
class PickerPlugin(pycc.PythonPluginInterface):
def __init__(self):
pycc.PythonPluginInterface.__init__(self)
self.app = pycc.GetInstance()
self.listener = PickingListener()
self.isListening = False
def startPicking(self):
if self.isListening:
return
self.app.pickingHub().addListener(self.listener)
self.isListening = True
def stopPicking(self):
if not self.isListening:
return
self.app.pickingHub().removeListener(self.listener)
self.isListening = False
def getIcon(self):
return MAIN_ICON_BYTES
def getActions(self):
return [
pycc.Action(
name="Start Picking",
target=self.startPicking,
icon=(ICON_BYTES, "PNG")
),
pycc.Action(
name="Stop Picking",
target=self.stopPicking,
icon=(ICON_BYTES, "PNG")
)
]