A Blender 5.1+ extension for importing and exporting point cloud files. Built around Blender's native PointCloud object type.
| Format | Import | Export |
|---|---|---|
E57 (.e57) |
✅ | ✅ (no normals) |
PLY (.ply) |
✅ | ✅ (ASCII or binary) |
LAS / LAZ (.las, .laz) |
✅ | ✅ |
PCD (.pcd) |
✅ (ASCII / binary) | ✅ (ASCII or binary) |
XYZ (.xyz, .txt, .csv) |
✅ | ✅ |
PTS (.pts) |
✅ | ✅ |
ASTM E57 is a compact, vendor-neutral container for point clouds, images, and metadata produced by 3D imaging systems — LiDAR, terrestrial laser scanners, structured-light rigs. A single .e57 file can carry multiple scans, each with its own pose, color, intensity, and (via extensions) normals.
- libe57.org — official format home and specification
- Sample E57 files — public test datasets (Stanford bunny, scan stations, etc.)
- Coordinate system conventions — how scanners encode pose and orientation; useful when imported clouds appear flipped or rotated
ASPRS LAS is the dominant format for airborne and terrestrial LiDAR data. Each point stores a position (quantized to int32 with a header-declared scale and offset), 16-bit intensity, return-number metadata for multi-return LiDAR, and an ASPRS classification code (ground, building, vegetation, water, etc.). LAS is uncompressed; LAZ is the same content compressed via LASzip — typically 10–20 % of the original size with no loss.
- ASPRS LAS specification — official format home, including the current LAS 1.4 PDF
- USGS Lidar Explorer — free public LiDAR coverage of the United States; download
.laztiles by drawing a polygon on the map - OpenTopography — curated LiDAR datasets from research and government sources around the world
Small samples for quick testing:
laspytest data — handful of tiny LAS/LAZ files (≤ a few hundred KB) covering different point-data record formats. Good for verifying your import path works.- PDAL test data — slightly larger variety with georeferenced examples.
- Autzen Stadium (
autzen.laz) — ~50 MB, classic LiDAR demo dataset (~10M points, real-world UTM coords). Great for stress-testing.
Heads up: most "real" LAS files use georeferenced coordinates (UTM, State Plane), so the cloud lands millions of metres from origin. The importer's Center on Origin option (on by default) subtracts the data minimum so you can actually see the result. The original offset is stored as a
las_origin_offsetcustom property on the object and added back on export.Aerial LiDAR clouds also tend to be kilometres across, which makes a 5 cm point radius sub-pixel on the viewport. The importer's Auto Point Radius option (also on by default) picks a sensible radius from the cloud's extent and density — typically tens of metres for landscape-scale data, sub-millimetre for room-scale scans.
- Blender 5.1 or newer (Python 3.13 or 3.14)
The required Python wheels (pye57, pyquaternion, laspy, lazrs) are bundled with the extension — no internet needed at install time. Both Python 3.13 and 3.14 builds are included, so distribution-packaged Blender linked against a newer system Python installs cleanly too.
Supported platforms are macOS (Apple Silicon and Intel), Linux x86_64, and Windows x64. E57 is the one format with a gap: upstream pye57 publishes no macOS x86_64 wheel, so on Intel Mac every other format works but E57 does not.
- In Blender:
Edit > Preferences > Get Extensions. - Search for Point Cloud I/O.
- Click Install.
- Enable the add-on if it isn't enabled automatically.
Use this path if you want to try unreleased changes or work on the extension locally.
-
Build a ZIP from the
point_cloud_io/directory (or download a release):blender --command extension build --source-dir point_cloud_io --output-dir .That produces a universal
point_cloud_io-<version>.zipcontaining every platform's wheels. Add--split-platformsto get one smaller ZIP per platform instead, which is what releases ship. -
In Blender:
Edit > Preferences > Get Extensions. -
Click the drop-down (top right) → Install from Disk.
-
Pick
point_cloud_io.zipand enable the extension.
Every export dialog can be told which point attribute feeds each channel it writes, and warns about anything it leaves out — see Choosing which attributes get exported.
File > Import > E57 Point Cloud (.e57)
Options in the sidebar:
- Colors — extract RGB and write a
colorpoint attribute. - Normals — extract normal vectors as a
normalpoint attribute. - Intensity — extract scalar intensity (LiDAR return strength) normalised to 0–1.
- Scale — global multiplier applied to coordinates.
- Point Radius — visible radius written to the
radiuspoint attribute. - Merge Scans — combine all scans in the file into one PointCloud object.
To see colors in the viewport, switch to Material Preview or Rendered shading.
File > Export > E57 Point Cloud (.e57)
Each selected PointCloud object becomes one scan in the output file.
Options:
- Colors — write the
colorattribute as RGB uint8 (0–255). - Intensity — write the
intensityattribute as a float field. - Apply Modifiers — evaluate the depsgraph first so Geometry Nodes-driven clouds are captured at their final state.
- Apply Transforms — bake object Location/Rotation/Scale into the exported coordinates.
- Selection Only — when off, every PointCloud in the scene is exported.
Note: normals are not written. pye57's writer (which wraps libE57Format) only exposes cartesian / spherical / intensity / color / index fields. Normals are an extension field and would require bypassing write_scan_raw.
File > Import > PLY Point Cloud (.ply)
Reads positions (x y z), colors (red green blue [alpha], auto-detects 0–255 vs 0–1 ranges), normals (nx ny nz), and any other per-vertex scalar properties as point attributes. ASCII and binary little-endian / big-endian variants are supported. The reader is pure Python + numpy — no extra wheel dependencies.
Note: this entry is distinct from Blender's built-in
File > Import > Stanford PLY (.ply), which produces a mesh. The Point Cloud I/O entry produces a PointCloud object — much faster for million-point datasets and easier to drive with Geometry Nodes.
File > Export > PLY Point Cloud (.ply)
Writes positions, normals, colors (as RGB uint8), and every other POINT-domain attribute. FLOAT / INT / BOOLEAN attributes keep their own name; vector and color attributes that no channel claimed are split into <name>_x/_y/_z and <name>_r/_g/_b columns rather than being dropped. Binary little-endian by default; tick ASCII for a human-readable file.
File > Import > LAS/LAZ Point Cloud (.las, .laz)
Reads positions (with the file's scale + offset applied), RGB (16-bit → normalized), intensity (normalized to 0–1), classification (uint8 → INT attribute), and optional return-number / number-of-returns. Compressed .laz files are decompressed by the bundled lazrs codec.
Classification codes follow the ASPRS standard: 2 = ground, 5 = high vegetation, 6 = building, 9 = water, etc. The values land in an INT point attribute named classification so you can drive material colors or visibility per-class in Geometry Nodes.
File > Export > LAS/LAZ Point Cloud (.las, .laz)
Writes Point Data Record Format 3 (LAS 1.2) — broadly compatible, includes RGB + intensity + classification + return info + GPS time. Tick Compress (LAZ) to write .laz via the lazrs codec.
Positions are quantized to millimeter precision (scale=0.001), with the offset set to the data minimum so the int32 storage stays in range. Color is upscaled from Blender's 8-bit to LAS's 16-bit channels.
File > Import > PCD Point Cloud (.pcd)
Reads positions (x y z), normals (normal_x/y/z), packed RGB / RGBA, intensity, and any other per-point scalar fields as point attributes. Supports all three PCD data modes: ascii, binary, and binary_compressed (LZF). LZF compression is handled by a pure-Python decompressor — no extra wheels.
File > Export > PCD Point Cloud (.pcd)
Writes a single unordered cloud (HEIGHT = 1). Columns are x y z + optionally normal_x/y/z, packed rgb, and intensity. The Data Mode dropdown chooses between binary (default), binary_compressed (LZF — same as PCL writes by default, smaller files), or ascii. Multiple selected PointCloud objects are concatenated into one PCD.
File > Import > XYZ Point Cloud (.xyz, .txt, .csv)
Plain-text format with no header — column layout is inferred:
| Columns | Interpretation |
|---|---|
| 3 | x y z |
| 4 | x y z intensity |
| 6 | x y z r g b (auto-detects 0–1 vs 0–255) |
| 7 | x y z intensity r g b |
| 9 | x y z r g b nx ny nz |
| other | first 3 are positions, remaining columns become extra_0, extra_1, … FLOAT attributes |
Comma-separated values are auto-detected from the first data line; #-prefixed comment lines are skipped.
File > Export > XYZ Point Cloud (.xyz)
Always ASCII. Column order: x y z [intensity] [r g b] [nx ny nz]. Tick the Write Colors / Normals / Intensity options to include each section; columns are only emitted when the underlying attribute exists on every exported object.
File > Import > PTS Point Cloud (.pts)
Leica Cyclone text format — basically XYZ with a single integer count line at the top. The reader auto-detects the column layout from the row width: x y z, x y z intensity, x y z r g b, or x y z intensity r g b (the Leica canonical 7-column form). Intensity is normalised to 0–1; RGB auto-detects 0–255 vs 0–1.
File > Export > PTS Point Cloud (.pts)
Writes the count header followed by x y z [intensity] [r g b] rows. Intensity is scaled back to the 0–2047 Leica integer range; RGB is written as uint8 0–255. Tick the Write Intensity / Write Colors boxes to control which columns appear.
Every exporter maps Blender point attributes onto the channels its format carries — color, intensity, and for LAS also classification and return numbers. Clouds imported by this add-on already use the names the exporters look for, but clouds built in Geometry Nodes or imported by other tools often do not, and an unrecognised attribute used to be dropped without a word.
Each export dialog now has a Source Attributes section with one dropdown per channel:
| Setting | Meaning |
|---|---|
| Auto (default) | Match by name — the canonical name first, then common aliases |
| None | Leave this channel out of the file |
| an attribute name | Use exactly that attribute, whatever it is called |
The dropdown lists only attributes whose data type actually fits the channel, including any created by Geometry Nodes when Apply Modifiers is on.
Recognised names. Auto-detection compares names with case and punctuation
ignored, so scalar_Intensity, Scalar Intensity and intensity all match.
| Channel | Canonical name | Also recognised |
|---|---|---|
| Color | color |
colors, col, rgb, rgba, vertex_color, diffuse_color, base_color |
| Normal | normal |
normals, n, nrm, vertex_normal |
| Intensity | intensity |
i, scalar_Intensity, reflectance, amplitude |
| Classification | classification |
class, scalar_Classification, category, label |
| Return Number | return_number |
return_num, ret_num, return_index |
| Number of Returns | number_of_returns |
num_returns, n_returns, return_count |
Color accepts FLOAT_COLOR, BYTE_COLOR and 3-component FLOAT_VECTOR
attributes; normals accept FLOAT_VECTOR; the scalar channels accept FLOAT,
INT, INT8 and BOOLEAN, converting as needed.
Warnings. The export dialog lists anything that will not make it into the file before you click Export, and the same list is reported afterwards:
Some attributes were left out: XYZ did not write scalar_Deviation, gps_time.
Recognised names: 'intensity', 'color', 'normal' — or pick a source above.
An attribute is reported when the target format has no column for it — LAS carries classification, XYZ does not — or when its name matched no channel. Setting a channel to None is a deliberate choice and is never reported.
PLY is the exception: it writes every point attribute through under its own
name, splitting vectors and colors into <name>_x/_y/_z and
<name>_r/_g/_b components, so a PLY export normally drops nothing.
Intensity ranges. The exporters assume intensity is normalised to 0–1, which is what the importers produce. LAS scales 0–1 up to 16-bit and passes larger values through unchanged; PTS clamps to the 0–2047 Leica range. If you map a channel holding raw 16-bit counts, check the output range.
After importing, press N in the 3D Viewport → Point Cloud tab. The panel shows point count and present attributes, plus a radius control with a logarithmic slider and ÷10 / ÷2 / Auto / ×2 / ×10 buttons for quick magnitude changes.
point_cloud_io/
├── __init__.py # extension entry point
├── blender_manifest.toml # Blender extension manifest
├── operators/
│ ├── __init__.py # operator registration + menu wiring
│ ├── _base.py # shared export operator base (roles, warnings)
│ ├── import_e57.py # File > Import > E57 operator
│ ├── export_e57.py # File > Export > E57 operator
│ ├── import_ply.py # File > Import > PLY operator
│ ├── export_ply.py # File > Export > PLY operator
│ ├── import_las.py # File > Import > LAS/LAZ operator
│ ├── export_las.py # File > Export > LAS/LAZ operator
│ ├── import_pcd.py # File > Import > PCD operator
│ ├── export_pcd.py # File > Export > PCD operator
│ ├── import_xyz.py # File > Import > XYZ operator
│ ├── export_xyz.py # File > Export > XYZ operator
│ ├── import_pts.py # File > Import > PTS operator
│ └── export_pts.py # File > Export > PTS operator
├── formats/
│ ├── __init__.py
│ ├── _attrs.py # attribute roles, name aliases, export planning
│ ├── _common.py # shared PointCloud build / read helpers
│ ├── e57.py # E57 read + write logic
│ ├── ply.py # PLY read + write logic
│ ├── las.py # LAS/LAZ read + write logic
│ ├── pcd.py # PCD read + write logic
│ ├── xyz.py # XYZ read + write logic
│ └── pts.py # PTS read + write logic
├── ui/
│ ├── __init__.py
│ └── panel.py # 3D Viewport sidebar (N-panel)
└── wheels/ # bundled wheels — cp313 + cp314 (pye57, lazrs), pure-Python (laspy, pyquaternion)
Adding a new format means dropping formats/<format>.py and a matching operators/import_<format>.py (or export_<format>.py) and wiring it up in operators/__init__.py.
Uses pye57 (libE57Format Python bindings).
GPL-3.0-or-later. Bundled wheels (pye57, pyquaternion) remain under their original licenses (MIT).


