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
Runbutton orCtrl+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 Scriptcommand 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.Meshpymeshlab.MeshSetpymeshlab.FilterInfopymeshlab.FilterRunResultpymeshlab.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_modesby. Obtain one withcurrent_mesh_id(),mesh_id(index)orMesh.id(); check it is still live withmesh_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:
keyidplugin_idplugin_namenamepython_nameapplicableapplicability_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:
boolintfloatstrNone3-item
tupleorlistfor 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:
successdocument_modifiederror_messageinfo_messagesnew_mesh_indicesoutput_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¶
_meshlabis the private compiled extension module. User scripts should prefer the predefinedmsobject orimport pymeshlab.msoperates on the live document; scripts can modify the scene.mlguidepends 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.