Python API cheat sheet: common MeshInspector scripting calls

The Python calls you need most often in MeshInspector, each with a short tested example and a link to the full guide

8 min read

This page collects the Python calls you use most often in MeshInspector, each with a short example. Every example was run in MeshInspector 2.5.9; the guides linked in the sections explain the calls in detail.

Where to run the scripts

Run the scripts in the Python window of the desktop app: open Home › Python, paste a script into the field at the top and click Run. What the script prints, and the error message if it fails, appears in Output. You do not need to install Python: MeshInspector includes its own interpreter and the MeshLib modules. If you have not used the window yet, start with How to Control MeshInspector’s UI with Python.

Start every script with the two imports:

from meshlib import mrmeshpy as mm    # MeshLib: load, process and save geometry
from meshlib import mrviewerpy as mv  # MeshInspector: scene, interface and view
  • Replace the paths in the examples with files on your computer. On Windows write paths with forward slashes, for example C:/Data/part.stl.
  • The built-in Python has a reduced standard library: os, re, time, math and collections work; json, pathlib, glob, shutil and datetime do not. NumPy is not included.
  • An AI assistant connected over MCP runs the same Python with its python_run tool; see MCP configuration instruction for MeshInspector users.

Task → call

Task Call
Load a mesh file mm.loadMesh(path)
Add a mesh to the scene mv.addMeshToScene(mesh, "Part")
Fit the view to the scene mv.Viewer().preciseFitDataViewport()
Select objects mv.selectByName("Part"), mv.selectByType("Meshes"), mv.unselectAll()
Get the selected objects or their meshes mv.getSelectedObjects(), mv.getSelectedMeshes()
List the objects of the scene mm.SceneRoot.get().children()
Hide, rename or remove an object obj.setVisible(False), obj.setName("New name"), obj.detachFromParent() inside mv.runFromGUIThread()
Replace the mesh of the selected object mv.modifySelectedMesh(mesh)
Remove all objects mv.clearScene()
Count vertices, triangles and holes mesh.topology.numValidVerts(), mesh.topology.numValidFaces(), mesh.topology.findNumHoles()
Size, area and volume mesh.getBoundingBox(), mesh.area(), mesh.volume()
Move, rotate or scale a mesh mesh.transform(xf)
Move an object in the scene obj.setXf(xf)
Fill holes mm.fillHoles(mesh, edges, params)
Simplify mm.decimateMesh(mesh, settings)
Offset mm.offsetMesh(mm.MeshPart(mesh), distance, params)
Boolean operation mm.boolean(meshA, meshB, mm.BooleanOperation.Union)
Save a mesh mm.saveMesh(mesh, path)
Open a tab or a tool, press a button mv.uiPressButton(path)
List the controls of a window mv.uiListEntries(path)
Read or change a value mv.uiReadValueReal(path), mv.uiWriteValue(path, value)
Save an image of the 3D view mv.Viewer().captureScreenShot(path)

Load files and add them to the scene

mesh = mm.loadMesh("C:/Data/part.stl")
mv.addMeshToScene(mesh, "Part")
mv.Viewer().preciseFitDataViewport()
  • loadMesh() reads STL, OBJ, PLY and the other mesh formats. The mesh is now in memory, not in the scene.
  • addMeshToScene() adds a copy of the mesh to the scene; it appears in the scene tree as Part. Point clouds, lines and voxels have addPointCloudToScene(), addLinesToScene() and addVoxelsToScene().
  • preciseFitDataViewport() fits the view so that the scene is visible.

More in Loading, Adding, Selecting, and Saving Meshes.

Select and change scene objects

mv.selectByName("Part")             # select "Part", unselect all other objects
objects = mv.getSelectedObjects()   # the selected scene objects
meshes = mv.getSelectedMeshes()     # copies of their meshes
print(objects[0].name(), "| triangles:", meshes[0].topology.numValidFaces())
  • selectByType("Meshes") selects all meshes (also "Points" and "Voxels"); unselectAll() clears the selection.
  • getSelectedMeshes() returns copies: changing them does not change the scene. To put a changed mesh back, select exactly one object and call mv.modifySelectedMesh(mesh).
  • clearScene() removes all objects without asking to save them.

List the objects at the top of the scene tree without changing the selection:

for obj in mm.SceneRoot.get().children():
    print(obj.name(), "| visible:", obj.isVisible())

To hide, rename or remove an object, run the change through mv.runFromGUIThread(). It runs the function in the interface thread of MeshInspector and returns when the change is done:

mv.selectByName("Part")
obj = mv.getSelectedObjects()[0]
mv.runFromGUIThread(lambda: obj.setVisible(False))    # hide; True shows it again
mv.runFromGUIThread(lambda: obj.setName("Part 2"))    # rename
mv.runFromGUIThread(lambda: obj.detachFromParent())  # remove from the scene

Read mesh information

mesh = mm.loadMesh("C:/Data/part.stl")
box = mesh.getBoundingBox()
print("Vertices:", mesh.topology.numValidVerts())
print("Triangles:", mesh.topology.numValidFaces())
print("Holes:", mesh.topology.findNumHoles())
print("Size:", box.size(), "| Diagonal:", box.diagonal())
print("Area:", mesh.area(), "| Volume:", mesh.volume())

Output for a CAD housing:

Vertices: 39730
Triangles: 79516
Holes: 0
Size: Vector3f(311.606 135.89 222.706) | Diagonal: 406.401611328125
Area: 391367.83535834984 | Volume: 1726248.3282264005
  • The values are in the units of the model, usually millimeters.
  • box.min and box.max are the corners of the bounding box and box.center() is its center; each point has .x, .y and .z.
  • volume() is meaningful for a closed mesh. If the mesh has holes, MeshLib closes each hole virtually before it computes the volume, so fill the holes first if you need an exact value.

Move, rotate and scale

transform() changes the coordinates of the mesh itself:

import math

mesh = mm.loadMesh("C:/Data/part.stl")

# Move 10 units along X
mesh.transform(mm.AffineXf3f.translation(mm.Vector3f(10, 0, 0)))

# Rotate 90 degrees around Z, around the center of the bounding box
rot = mm.Matrix3f.rotation(mm.Vector3f(0, 0, 1), math.pi / 2)
mesh.transform(mm.AffineXf3f.xfAround(rot, mesh.getBoundingBox().center()))

# Scale 2 times, around the origin
mesh.transform(mm.AffineXf3f.linear(mm.Matrix3f.scale(2.0)))

An object in the scene can be moved instead, as with the Transform panel. The mesh keeps its own coordinates, and the object gets a transform:

mv.selectByName("Part")
obj = mv.getSelectedObjects()[0]
mv.runFromGUIThread(lambda: obj.setXf(mm.AffineXf3f.translation(mm.Vector3f(0, 0, 5))))
print("Translation:", obj.xf().b)

# getSelectedMeshes() returns the mesh without the object's transform:
# apply the world transform (it includes the transforms of parent objects)
# before you save the mesh where you see it in the scene
mesh = mv.getSelectedMeshes()[0]
mesh.transform(obj.worldXf())

Process meshes with MeshLib

mrmeshpy gives scripts the MeshLib algorithms that MeshInspector is built on. The functions below change the mesh in memory or return a new one; add the result to the scene or save it. The examples use a mesh loaded with mm.loadMesh(). The full list of functions is in the MeshLib Python API reference, and more examples are in the MeshLib code samples.

Fill holes

mesh = mm.loadMesh("C:/Data/part.stl")
params = mm.FillHoleParams()
params.metric = mm.getUniversalMetric(mesh)
mm.fillHoles(mesh, mesh.topology.findHoleRepresentiveEdges(), params)
print("Holes left:", mesh.topology.findNumHoles())

findHoleRepresentiveEdges() (this is the spelling of the function) returns one edge of each hole, and fillHoles() closes these holes with new triangles. In our test, a teapot model with 34 holes had 0 holes and 7288 triangles instead of 6320 afterwards. In the interface, the same job is done by Fill Holes.

Simplify

settings = mm.DecimateSettings()
settings.maxError = 0.01
result = mm.decimateMesh(mesh, settings)
print("Triangles:", mesh.topology.numValidFaces(), "| deleted:", result.facesDeleted)

maxError is the largest allowed deviation from the original surface, in the units of the model. On a CAD test part about 5 units long, 0.01 reduced 12946 triangles to 3058. In the interface: Simplify.

Offset

params = mm.OffsetParameters()
params.voxelSize = mm.suggestVoxelSize(mm.MeshPart(mesh), 1e6)
offset = mm.offsetMesh(mm.MeshPart(mesh), 0.05, params)
mv.addMeshToScene(offset, "Offset")

offsetMesh() returns a new mesh whose surface lies the given distance outside the original one; a negative distance moves it inside. The offset is computed on a voxel grid: suggestVoxelSize(..., 1e6) picks a voxel size for about one million voxels, and a smaller voxel gives more detail and takes more time and memory. On the test part, an offset of 0.05 made the part 0.1 longer along X, 0.05 on each side.

Boolean operations

a = mm.loadMesh("C:/Data/part_a.stl")
b = mm.loadMesh("C:/Data/part_b.stl")
result = mm.boolean(a, b, mm.BooleanOperation.Union)
if result.valid():
    mv.addMeshToScene(result.mesh, "Union")
else:
    print("Boolean failed:", result.errorString)
  • The operations are Union, Intersection, DifferenceAB (A minus B) and DifferenceBA (B minus A).
  • The meshes should be closed and have no self-intersections where they intersect, as the MeshLib documentation of boolean() notes. If the operation cannot be done, valid() returns False and errorString says why.

Save results

mm.saveMesh(mesh, "C:/Data/part_out.obj")
mm.saveMesh(mv.getSelectedMeshes()[0], "C:/Data/selected.stl")

The format is chosen by the extension of the file name: .stl, .obj, .ply or another mesh format.

Drive tools and the interface

Scripts reach tabs, tools and their parameters by paths, lists of names in the interface tree. Tool buttons in Ribbon use the internal tool name, and the parameters of an open tool are under the title of its window:

import time

def wait_progress_bar():
    mv.Viewer().skipFrames(2)
    while any(e.name == "ProgressBar" for e in mv.uiListEntries([])):
        time.sleep(0.2)

mv.uiPressButton(['RibbonTabs', 'Mesh Repair'])  # open a tab
mv.uiPressButton(['Ribbon', 'Mesh Healer'])      # open a tool of this tab
wait_progress_bar()                              # wait for the inspection

print([e.name for e in mv.uiListEntries(['Mesh Healer'])])    # controls of the window
val = mv.uiReadValueReal(['Mesh Healer', 'Repair Resolution'])
print(val.value, val.min, val.max)                           # value and limits
mv.uiWriteValue(['Mesh Healer', 'Holes'], 1)                  # check a check box

mv.uiPressButton(['Ribbon', 'Mesh Healer'])      # close the tool

To save an image of the 3D view, without the Ribbon and panels:

mv.Viewer().captureScreenShot("C:/Data/view.png")

Handle errors

Problem What happens What to do
The file does not exist loadMesh() stops the script with RuntimeError: Cannot open file for reading and the path. Catch RuntimeError, or check the path with os.path.exists().
The output folder does not exist saveMesh() stops the script with RuntimeError: Cannot open file for writing and the path. Create the folder before you save.
A wrong name in a path RuntimeError with "no such entry" and the list of names that exist. List the entries with uiListEntries() and copy the exact name.
Nothing is selected getSelectedMeshes() returns an empty list; modifySelectedMesh() stops with "Exactly one mesh must be selected". Check the list before you use [0].
A Boolean operation fails result.valid() is False. Print result.errorString; repair the meshes first.
A button is unavailable Nothing happens; the MeshInspector log gets a warning. Check the status of the button.
try:
    mesh = mm.loadMesh("C:/Data/missing.stl")
except RuntimeError as e:
    print("Cannot load:", e)

meshes = mv.getSelectedMeshes()
if not meshes:
    print("Select a mesh first")

Customize Consent Preferences

We use cookies and similar technologies, such as local storage. You will find detailed information about all of them under each consent category below.

To learn more about how Google processes personal information, visit: Google privacy policy

Always Active