Skip to main content

MassCompose

MassCompose takes a mesh and the point masses bolted to it, like a battery, motor, or controller, and gives you one total mass, one center of mass, and one 3×3 inertia tensor about that center of mass.

trimesh already handles the mesh's own properties, so the part this package adds is the parallel-axis composition. The shifts have to come from the system center of mass instead of the origin, since a wrong tensor is still symmetric and positive semi-definite and looks fine until the simulation starts tumbling.

PyPI release

https://pypi.org/project/masscompose/

Demo app

There's a small GUI demo in the github releases page (https://github.com/Iarush/masscompose) as Demo.zip. Unzip it and run python run.py (or ./run.sh on Mac/Linux), which sets up a local .venv and installs everything the first time. After that you can skip the install checks by launching app.py with the venv's Python directly, .venv\Scripts\python app.py on Windows or .venv/bin/python app.py on Mac/Linux.

Installation

pip install masscompose

It only needs numpy and trimesh. From a checkout, pip install -e ".[dev]" adds pytest.

Usage

import numpy as np
import trimesh

from masscompose import PointMass, compose, load_mesh, to_urdf_inertial

# Stand-in for your own file: a 300 x 200 x 8 mm plate, exported in mm
trimesh.creation.box(extents=[300.0, 200.0, 8.0]).export("chassis.stl")

# PETG at 40% infill. Units are always declared, never guessed
chassis = load_mesh("chassis.stl", mesh_units="mm", density=1240.0, infill=0.4)

payload = [
    PointMass([0.000, 0.000, 0.050], mass=0.420, name="battery"),
    PointMass([0.080, -0.040, 0.020], mass=0.075, name="esc"),
    PointMass([-0.120, 0.000, 0.090], mass=0.031, name="camera"),
]

mp = compose(chassis, payload)

print(f"mass = {mp.mass:.4f} kg")
print(f"com  = {np.round(mp.com, 5).tolist()} m")
print(to_urdf_inertial(mp))
mass = 0.7641 kg
com  = [0.00298, -0.00393, 0.0331] m
<inertial>
  <origin xyz="0.002983980735 -0.00392629044079 0.0330986284159" rpy="0 0 0"/>
  <mass value="0.76408"/>
  <inertia ixx="0.00139712657604" ixy="0.000231048057795" ixz="0.000290264872788" iyy="0.00320050197129" iyz="-3.92958852476e-05" izz="0.0036070176526"/>
</inertial>

Point mass positions are in meters and share the mesh file's frame after unit conversion. The tensor is about mp.com, which is why the <origin> carries it.

If you only need the mesh

You don't need this package for that, since trimesh does it in three lines:

mesh = trimesh.load("chassis.stl", force="mesh")
mesh.apply_scale(0.001)   # your file's units -> meters
mesh.density = 1240.0     # then read mesh.mass, mesh.center_mass, mesh.moment_inertia

Use MassCompose when there are discrete masses to add on top.

Scope

Does Doesn't
One watertight mesh with a density or known mass Multi-body assemblies or sub-bodies with their own orientation
N point masses at explicit XYZ Point masses with their own extent
Required mesh_units ("mm", "cm", "m") Guess units from the file
Effective density through infill Per-region or graded density
Raises on open meshes and bad inertia Repair a broken mesh
URDF <inertial> export MJCF, USD, SDF

Everything is SI on output: kg, m, kg·m².

API

load_mesh(path, *, mesh_units, density=None, mass=None, infill=1.0, allow_open=False) -> MeshBody

Mass properties of one mesh, with inertia about the mesh's own COM. You need exactly one of density (kg/m³) or mass (kg), and infill can't be combined with mass. A non-watertight mesh raises ValueError unless allow_open=True, which turns it into a warning.

compose(body: MeshBody, point_masses: Sequence[PointMass]) -> MassProperties

Total mass, system COM, and the inertia tensor about that COM. An empty list just returns the mesh's own properties, and the output is checked before it's returned.

to_urdf_inertial(mp: MassProperties) -> str

The <inertial> block as a string, ready to paste inside a <link>. The i** attributes are plain matrix entries (ixy is inertia[0, 1]), so there's no sign flip.

PointMass(position: np.ndarray, mass: float, name: str | None = None)

A discrete mass in the mesh's frame. position has to be shape (3,), and a (3, 1) column gets rejected instead of flattened since a transposed vector gives a wrong but plausible answer.

MeshBody(mass, com, inertia)         # returned by load_mesh
MassProperties(mass, com, inertia)   # returned by compose

Metadata

Release files for masscompose 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for masscompose 0.1.1
File Size Uploaded Provenance Actions
masscompose-0.1.1.tar.gz 12.6 kB Without provenance Details for masscompose-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for masscompose 0.1.1
File Interpreter ABI Platform Actions
masscompose-0.1.1-py3-none-any.whl Python 3 none any Details for masscompose-0.1.1-py3-none-any.whl

Total release size: 28.6 kB

Release files / masscompose-0.1.1.tar.gz

Download URL masscompose-0.1.1.tar.gz
Size 12.6 kB
Tags Source
SHA-256 checksum
How to use checksums
60300a22d71327ef7c395b7707f1692831e8765651aac2c65b5c1be2d2faa0d8
BLAKE2b-256 checksum
How to use checksums
14c06ea55be5d72c86f6252a0e20daa4775ebd6c32a72ac59cfd51a7a243768f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / masscompose-0.1.1-py3-none-any.whl

Download URL masscompose-0.1.1-py3-none-any.whl
Size 16.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
403520149d5ebf1b33750cc9230015fe8b32a59066fbbc09f1917c3cb5f69bb2
BLAKE2b-256 checksum
How to use checksums
6684b1acb2b31194a6a12bae76000c1034f6754412bd04286c7561265dbf1e73
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for masscompose 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page