Files
solvecraft/docs/assembly-properties.md
PaddleStroke fd5d0e6738 Assemblies: mass, centre of mass and inertia per occurrence and rigid group (#9715) (#9717)
* claim: #9715

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Assemblies: mass, centre of mass and inertia per occurrence and rigid group (#9715)

assembly.mass_properties (and Session::mass_properties) gives, for every
occurrence and every rigidly-joined group, mass (kg), centre of mass (mm) and
the inertia tensor about it in world and frame axes (kg mm2), the input
the multibody dynamics work needs. Bodies are measured as Properties
measures them (kernel volume and centre, mesh gyration), once per body,
then moved by each placement and combined by the parallel-axis theorem.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:35:29 +00:00

6.3 KiB
Raw Permalink Blame History

Assembly physical properties

The Browser's component Properties action measures the selected assembly placement, including its nested components. Select the root design to measure every instance in the assembly. Inspect → Measure → Properties opens the same window for the one selected placement, or else for the active component. The window shows material-aware mass, volume, surface area, center of mass, first moments, the moment of inertia and the world bounding box. Pending occurrence moves are included without capturing them. While the window is open, the view marks the center of mass with its principal axes, numbered and colored as the window lists them. Copy matrix, under the tensor, puts it on the clipboard: three lines of tab-separated values at full precision, in the units shown, ready to paste into a spreadsheet or code. The window for the whole design is titled just Properties; one opened on a component, placement or bodies carries its name.

The same measurements are available through commands:

{"command":"component.properties","params":{"component":"Bracket"}}
{"command":"component.properties","params":{"component":"root"}}
{"command":"component.properties","params":{"component":"Subassembly","descendants":false}}
{"command":"occurrence.properties","params":{"occurrence":"Bracket:2"}}
{"command":"component.properties","params":{"component":"root","about":[0,0,0]}}

component.properties defaults to the active component and measures its first placement. occurrence.properties measures the chosen instance. A nested occurrence can appear beneath several instances of its parent; supply path (the complete list of occurrence ids from the root) to choose one of those placements. An ambiguous occurrence without a path returns an error. Both commands accept descendants: false to measure only directly owned bodies.

Results contain a bodies list and a total object. Each body's entry identifies the original body name, owning component and occurrence path. Physical material density is resolved from that original body, so copies keep their material and mass. Values use millimetres, square millimetres, cubic millimetres and grams; coordinates use the world frame.

Mass and center of mass account for each body's density. Volume and area are summed over placed bodies, including overlapping bodies and repeated instances. Measurements use the kernel's tessellation-based body measurement. An empty component reports zero totals and null center of mass and bounding box. A failed body measurement returns an error instead of presenting an incomplete assembly total. These commands leave geometry, pending placements and undo history unchanged.

Moment of inertia

Each closed body's entry has inertia_g_mm2: its inertia tensor about its own center of mass, in world axes, in g·mm². The total object has:

  • first_moment_g_mm: the first moments of mass, Σ m·c (the total mass times the center of mass).
  • inertia_g_mm2: the inertia tensor about the total center of mass, in world axes. Each body's own tensor is moved there with the parallel-axis theorem.
  • principal_moments_g_mm2 (smallest first) and principal_axes (unit vectors, one per moment). The first two axes point along the world axis each is nearest to; the third is the first × the second, so the three make a right-handed frame. Moments within 0.1% of the largest count as equal, as a round body's two equal moments do. Their axes are then the world axes nearest the plane they share.
  • inertia_about, only when the command has about: [x, y, z] (mm, world): {point, inertia_g_mm2}, the tensor about that point in world axes.

The tensor is the matrix that turns an angular velocity into angular momentum. Its diagonal holds the moments of inertia (Ixx = ∫ (y² + z²) dm …), and the off-diagonal terms are the negated products of inertia (Ixy = −∫ x y dm …), the convention URDF and most dynamics libraries take. For kg·m², divide by 10⁹.

The tensor is integrated over each body's welded export mesh, the one URDF export takes its inertia from. It is then scaled to the body's measured mass, so it agrees with the mass and center of mass shown. Planar bodies are exact. A round body is within about 0.1% at the standard export quality. Each body is tessellated once, however many times the assembly places it; each placement turns its tensor. A closed body whose mesh does not close has inertia_g_mm2: null, and so does the total. Open bodies have no volume, so they have no tensor either, and the total leaves them out.

Per occurrence and per rigid group

assembly.mass_properties (no parameters) gives what a dynamics solver needs of each moving part, measured as above, in kg, mm and kg·mm²:

{"command":"assembly.mass_properties","params":{}}
  • occurrences: the design's own bodies (occurrence: 0) and then every occurrence, each with its frame (origin and unit axes in world coordinates), its own bodies (material, density, volume and mass), and mass: its bodies' total mass_kg, center_of_mass_mm (world) and center_of_mass_local_mm (in its frame), and the tensor about that centre in world axes (inertia_world_kg_mm2) and in its frame's axes (inertia_local_kg_mm2). Only the bodies the occurrence's component owns count, not those of occurrences nested in it.
  • groups: occurrences that move as one body, each with the occurrences it holds, the same mass totals (combined with the parallel-axis theorem) in the frame of its lowest occurrence (frame_occurrence; 0 is the world), and fixed when it holds the design itself or a grounded occurrence. An unsuppressed rigid joint (Rigid Group makes them) puts its two occurrences in one group, and a nested occurrence that no joint places goes with the occurrence it sits in, since the joint solver leaves it where its parent puts it.
  • warnings: open bodies (no mass), closed bodies whose mesh does not close (inertia_*: null), scaled placements (no tensor in their own axes) and subassemblies placed more than once (the occurrences inside them are listed at the first placement, where joints place them).

A body without a material assignment has the default material's density, as Properties shows. Pending occurrence moves count. Rust callers get the same numbers, typed, from Session::mass_properties().