Skip to content

About

Import and export point cloud files in various formats.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Latest commit

 

History

28 Commits

Folders and files

Repository files navigation

Point Cloud I/O for Blender

Latest release DOI Blender License: GPL v3

A Blender 5.1+ extension for importing and exporting point cloud files. Built around Blender's native PointCloud object type.

Stanford bunny imported from E57

Supported formats

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) ✅ ✅

About E57

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.

About LAS / LAZ

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 .laz tiles by drawing a polygon on the map
  • OpenTopography — curated LiDAR datasets from research and government sources around the world

Small samples for quick testing:

  • laspy test 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_offset custom 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.

Requirements

  • 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.

Installation

From the Blender Extensions repository

  1. In Blender: Edit > Preferences > Get Extensions.
  2. Search for Point Cloud I/O.
  3. Click Install.
  4. Enable the add-on if it isn't enabled automatically.

From source (development / unreleased changes)

Use this path if you want to try unreleased changes or work on the extension locally.

  1. 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>.zip containing every platform's wheels. Add --split-platforms to get one smaller ZIP per platform instead, which is what releases ship.

  2. In Blender: Edit > Preferences > Get Extensions.

  3. Click the drop-down (top right) → Install from Disk.

  4. Pick point_cloud_io.zip and enable the extension.

Usage

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.

Importing E57

File > Import > E57 Point Cloud (.e57)

Import dialog options

Options in the sidebar:

  • Colors — extract RGB and write a color point attribute.
  • Normals — extract normal vectors as a normal point 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 radius point 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.

Exporting E57

File > Export > E57 Point Cloud (.e57)

Each selected PointCloud object becomes one scan in the output file.

Options:

  • Colors — write the color attribute as RGB uint8 (0–255).
  • Intensity — write the intensity attribute 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.

Importing PLY

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.

Exporting PLY

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.

Importing LAS / LAZ

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.

Exporting LAS / LAZ

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.

Importing PCD

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.

Exporting PCD

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.

Importing XYZ

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.

Exporting XYZ

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.

Importing PTS

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.

Exporting PTS

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.

Choosing which attributes get exported

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.

Sidebar panel

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.

Sidebar panel with point count and radius controls

Project layout

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.

Credits

Uses pye57 (libE57Format Python bindings).

License

GPL-3.0-or-later. Bundled wheels (pye57, pyquaternion) remain under their original licenses (MIT).

About

Import and export point cloud files in various formats.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages