Python Scripting

MeshLab can expose an embedded Python console and script editor when it is built with MESHLAB2_PYTHON_CONSOLE=ON. The embedded interpreter works on the live application document, so scripts can inspect the current scene, run filters, load/save meshes and rasters, and capture view snapshots.

The shared API is the pymeshlab.MeshSet interface, and MeshLab reuses that same model inside the desktop application. Generated API reference files live under docs/api/ when regenerated from the current filter descriptors; the local generated snapshot can lag until the app is run with --generate-docs. The desktop-only additions are the predefined live ms object and the mlgui helper.

Predefined Names

The script environment is the Python __main__ namespace used by the embedded console. Names created in the console and names created by scripts share the same namespace for the current MeshLab session.

ms

ms is the live document, exposed as a pymeshlab.MeshSet-compatible object. This is the main entry point for document and filter operations.

print("Meshes:", ms.mesh_number())
print("Current mesh:", ms.current_mesh())
print("Rasters:", ms.raster_number())

Important: ms borrows the live MeshLab Document. Calling modifying methods on it changes the open document.

If you already know pymeshlab, you can think of ms as “the current MeshLab document presented through the same MeshSet-style API”.

mlgui

mlgui is the live GUI/view helper. It is backed by the private _meshlab.MlGui type. It is available in the desktop application when an active render view exists.

print(mlgui.camera_state_json())
print(mlgui.render_state_json())
mlgui.save_snapshot("/tmp/meshlab_snapshot.png", 1200, 900)

Use mlgui for view state and rendering operations. It is not meant to be a general document API.

pymeshlab

pymeshlab is the public Python facade for standalone, GUI-less mesh processing. In the embedded MeshLab console it is injected as a lightweight module backed by the same private _meshlab extension used by the headless package.

Use it when you want a separate mesh set that does not operate on the live GUI document.

import pymeshlab

other = pymeshlab.MeshSet()
print("Standalone mesh set:", other.mesh_number())

Most scripts inside MeshLab should use the predefined ms object. Import pymeshlab only when you want the same public API exposed by the GUI-less package, or when you want to create an independent MeshSet.

Console and Script Editor

The Python dock contains a script editor and an interactive console side by side. Both execute in the same __main__ namespace, so variables created in a script remain available at the console prompt and vice versa.

Useful entry points:

  • Run the editor contents with the Run button or Ctrl+Enter / Cmd+Return.

  • Use the console for quick one-line calls and Up/Down history navigation.

  • Use the filter panel’s copy-to-console action when you need exact parameter ids.

  • Use the undo graph’s Generate Python Script command to export the current undo path into the script editor.

History export offers two styles: full scripts keep every recorded parameter for exact replay, while compact scripts omit parameters that matched the descriptor defaults at the time each action was recorded.

Available Module Objects

The public pymeshlab facade exposes:

  • pymeshlab.Mesh

  • pymeshlab.MeshSet

  • pymeshlab.FilterInfo

  • pymeshlab.FilterRunResult

  • pymeshlab.MlGui

MeshSet Basics

The live ms object supports the following core methods:

  • mesh_number()

  • current_mesh(), current_mesh_index(), set_current_mesh(index)

  • mesh(index)

  • current_mesh_id(), mesh_id(index), mesh_id_exists(id)

  • set_current_mesh_visibility(visible)

  • set_mesh_visibility(index, visible)

  • is_current_mesh_visible(), is_mesh_visible(index)

  • load_new_mesh(path)

  • save_current_mesh(path)

  • raster_number()

  • current_raster(), set_current_raster(index)

  • load_new_raster(path)

  • clear()

  • load_project(path), save_project(path)

  • filter_list(), list_filters()

  • apply_filter(filter, params={})

  • render_snapshot(render_state_json, width, height)

Mesh index vs mesh id

A mesh is addressed two ways, and they are not interchangeable:

  • index – the 0-based position in the layer list. Every operation above (mesh, set_current_mesh, set_mesh_visibility, is_mesh_visible) takes an index, and it is what the GUI writes into exported scripts. An index shifts whenever a layer below it is removed.

  • id – a persistent opaque handle, minted from 1 and never reused. It survives removals and reordering, and it is what the render-state JSON keys mesh_render_modes by. Obtain one with current_mesh_id(), mesh_id(index) or Mesh.id(); check it is still live with mesh_id_exists(id).

The first mesh loaded therefore has index 0 and id 1. Passing an index where an id is wanted is the common mistake, so note that mesh_render_modes wants the id, as a string:

import json

ms.load_new_mesh("bunny.ply")
state = {"mesh_render_modes": [
    {"mesh_id": str(ms.current_mesh_id()), "settings": {"show_fill": True}},
]}
png = ms.render_snapshot(json.dumps(state), 800, 600)

mesh_visibility in the same JSON is, by contrast, a positional array, which is why set_mesh_visibility takes an index.

Example:

print("Mesh count:", ms.mesh_number())

if ms.mesh_number() > 0:
    print("Current mesh index:", ms.current_mesh_index())
    print("Current mesh id:", ms.current_mesh_id())
    print("Current mesh label:", ms.current_mesh().label())

Listing Filters

Use ms.list_filters() for structured filter information:

for info in ms.list_filters()[:20]:
    print(info.python_name, "-", info.name)

Each FilterInfo has:

  • key

  • id

  • plugin_id

  • plugin_name

  • name

  • python_name

  • applicable

  • applicability_error

Example:

for info in ms.list_filters():
    if "texture" in info.name.lower():
        print(info.python_name, "|", info.name)

Running Filters

There are two equivalent styles.

Call apply_filter() with the filter Python name:

result = ms.apply_filter("simplification_quadric_edge_collapse", {
    "TargetFaceNum": 10000,
    "PreserveBoundary": True,
})
print(result.success, result.error_message)

Or call the dynamically generated convenience method:

result = ms.simplification_quadric_edge_collapse(
    TargetFaceNum=10000,
    PreserveBoundary=True,
)
print(result.success)

The generated convenience methods are attached to the shared MeshSet type when the console starts. Their names come from each filter descriptor’s pythonName.

Filter Parameters

Filter parameters are passed as Python keyword arguments or as a dictionary. Currently supported parameter value types are:

  • bool

  • int

  • float

  • str

  • None

  • 3-item tuple or list for point/vector values, for example (1.0, 0.0, 0.0)

Example with a vector parameter:

result = ms.apply_filter("translate", {
    "traslMethod": "xyz",
    "axis": (1.0, 0.0, 0.0),
    "transformResult": "assign_matrix",
})
print(result.success)

Parameter names must match the filter descriptor ids. The easiest way to get a valid call is to use the filter panel’s copy-to-console action. For longer reproducible scripts, export the current undo path from the action-history graph and choose either the full or compact format.

Filter Results

Filter calls return a pymeshlab.FilterRunResult-compatible object.

result = ms.apply_filter("mesh_info", {"precision": 3})

print("success:", result.success)
print("modified:", result.document_modified)
print("error:", result.error_message)
print("new meshes:", result.new_mesh_indices)
print("outputs:", result.output_values)

for message in result.info_messages:
    print(message)

Fields:

  • success

  • document_modified

  • error_message

  • info_messages

  • new_mesh_indices

  • output_values

Snapshot Examples

Save the current view to a PNG:

mlgui.save_snapshot("/tmp/meshlab_view.png", 1600, 1200)

Capture and reapply render state:

state = mlgui.render_state_json()
mlgui.save_snapshot("/tmp/meshlab_view.png", 1600, 1200, state)

Get raw RGBA bytes:

state = mlgui.render_state_json()
pixels = mlgui.render_snapshot(state, 800, 600)
print("Bytes:", len(pixels))

Render from a standalone mesh set without borrowing the live GUI document:

import pymeshlab
from pathlib import Path

other = pymeshlab.MeshSet()
other.load_new_mesh("/tmp/input.ply")
state_json = Path("/tmp/render_state.json").read_text()
pixels = other.render_snapshot(state_json, 800, 600)
print("Bytes:", len(pixels))

mlgui uses the live active render view. MeshSet.render_snapshot(...) uses a hidden HeadlessRenderContext, so it is the right path for standalone processing and batch-style scripts.

Minimal Useful Scripts

Print a short document report:

print("Meshes:", ms.mesh_number())
print("Rasters:", ms.raster_number())

for info in ms.list_filters()[:10]:
    print(info.python_name, "-", info.name)

Load a mesh and save it in another format:

ms.load_new_mesh("/tmp/input.obj")
ms.save_current_mesh("/tmp/output.ply")

Run a filter and inspect the result:

result = ms.apply_filter("mesh_info", {"precision": 2})

if not result.success:
    print("Filter failed:", result.error_message)
else:
    for message in result.info_messages:
        print(message)

Caveats

  • _meshlab is the private compiled extension module. User scripts should prefer the predefined ms object or import pymeshlab.

  • ms operates on the live document; scripts can modify the scene.

  • mlgui depends on an active desktop render view.

  • Standalone pymeshlab.MeshSet() objects own their own document and do not automatically share the live application document.

  • History-generated scripts replay recorded action metadata. Compact scripts are intentionally smaller and omit descriptor-default parameters; full scripts are better when exact parameter visibility matters.

  • The API is still evolving. Prefer generated filter calls from the filter panel when you need exact parameter names.