pymat
A hierarchical material library for CAD applications and Monte Carlo particle transport, with build123d integration.
Features
- Hierarchical Materials: Chain grades, tempers, treatments, and vendors
- Property Inheritance: Children inherit parent properties unless overridden
- Lazy Loading: Categories load on first access
- TOML Data Storage: Easy-to-edit material definitions
- Formula Parsing + Molar Mass: Computed from the chemical formula via
Material.molar_mass, with fractional stoichiometry (Lu1.8Y0.2SiO5) and dopant suffix stripping (LYSO:Ce). Atomic weights mirror the Rustrs-materialscrate for Python ↔ Rust parity. - build123d Integration: Apply materials to shapes with automatic mass calculation
- PBR Rendering via
material.vis: Scalars (roughness, metallic, base_color) and lazy-fetched textures from mat-vis - periodictable Integration: Auto-fill composition from chemical formulas for compounds; auto-fill density for pure elements
- Factory Functions: Temperature/pressure-dependent materials (water, air, saline)
- Separation of Concerns: Optical properties (physics) separate from
vis(visualization) - Python 3.10 – 3.13 supported, core library depends only on
pint
Installation
# From PyPI (recommended)
pip install py-materials
# or: uv add py-materials
# From the main branch (development)
pip install git+https://github.com/MorePET/mat.git@main
# With optional extras
pip install "py-materials[periodictable]" # auto-fill from chemical formulas
pip install "py-materials[build123d]" # build123d Shape integration (Python <= 3.12)
pip install "py-materials[all]" # everything above
Quick Start
Creating Materials
Create materials with convenient parameters:
from pymat import Material
# Using convenience parameters
steel = Material(name="Steel", density=7.85)
assert steel.density == 7.85
# With visualization color
aluminum = Material(name="Aluminum", density=2.7, color=(0.88, 0.88, 0.88))
assert aluminum.vis.base_color[:3] == (0.88, 0.88, 0.88)
# With formula
lyso = Material(name="LYSO", formula="Lu1.8Y0.2SiO5", density=7.1)
assert lyso.formula == "Lu1.8Y0.2SiO5"
Using Property Groups
Define multiple properties at once using property group dictionaries:
from pymat import Material
# Define steel with multiple property groups
steel = Material(
name="Stainless Steel 304",
mechanical={"density": 8.0, "youngs_modulus": 193, "yield_strength": 170},
thermal={"melting_point": 1450, "thermal_conductivity": 15.1},
vis={"base_color": (0.75, 0.75, 0.77, 1.0), "metallic": 1.0},
)
assert steel.properties.mechanical.density == 8.0
assert steel.properties.mechanical.youngs_modulus == 193
assert steel.properties.thermal.melting_point == 1450
assert steel.vis.metallic == 1.0
Applying Materials to Shapes
Apply materials to build123d shapes for visualization and mass calculation:
from build123d import Box
from pymat import Material
# Create material
steel = Material(name="Steel", density=7.85, color=(0.7, 0.7, 0.7))
# Create shape and apply material
box = Box(10, 10, 10)
steel.apply_to(box)
assert box.material.name == "Steel"
assert box.mass > 0
assert box.color is not None
Computing Molar Mass
Material.molar_mass is a computed property that parses the
chemical formula and looks up each element's atomic weight.
It supports fractional stoichiometry and strips dopant
notation like LYSO:Ce so doped-crystal aliases work
unchanged.
Nothing is stored — it recomputes on each access. That's
intentional: molar mass is definitionally derived from
formula and should never drift. Missing or unknown-element
formulas return None. See
docs/decisions/0001-derived-chemistry-properties-live-on-material.md.
from pymat import Material
# Pure element
iron = Material(name="Iron", formula="Fe")
assert iron.molar_mass == 55.85
# Simple compound
alumina = Material(name="Alumina", formula="Al2O3")
assert abs(alumina.molar_mass - 101.96) < 0.01
# Fractional stoichiometry (a PET-scanner scintillator)
lyso = Material(name="LYSO", formula="Lu1.8Y0.2SiO5")
assert abs(lyso.molar_mass - 440.87) < 0.1
# Dopant suffix is stripped
lyso_ce = Material(name="LYSO:Ce", formula="Lu1.8Y0.2SiO5:Ce")
assert lyso_ce.molar_mass == lyso.molar_mass
# Unit-aware companion accessor (Pint Quantity)
qty = iron.molar_mass_qty
assert qty.to("kg/mol").magnitude == pytest.approx(0.05585, abs=1e-4)
# Gracefully returns None when no formula is set
unknown = Material(name="Unknown Alloy")
assert unknown.molar_mass is None
Low-level: pymat.elements
For callers that don't need a full Material object —
e.g. quick stoichiometry calculations inside a Monte Carlo
transport loop — the low-level pymat.elements module
exposes the same machinery directly.
The ATOMIC_WEIGHT table is a line-for-line mirror of the
Rust rs-materials crate, so Python and Rust Monte Carlo
engines get identical molar masses byte-for-byte.
from pymat.elements import (
ATOMIC_WEIGHT,
compute_molar_mass,
parse_formula,
)
# Atomic weight lookup
assert ATOMIC_WEIGHT["Fe"] == 55.85
assert ATOMIC_WEIGHT["Lu"] == 175.0
# Formula parser: fractional stoichiometry + repeat handling
counts = parse_formula("Lu1.8Y0.2SiO5")
assert counts == {"Lu": 1.8, "Y": 0.2, "Si": 1.0, "O": 5.0}
# Molar mass directly from a formula string
assert abs(compute_molar_mass("Al2O3") - 101.96) < 0.01
Chainable Material Hierarchy
Build hierarchies with grades, tempers, and treatments:
from pymat import Material
# Create base stainless steel
stainless = Material(name="Stainless Steel", density=8.0, thermal={"melting_point": 1450})
# Add grade
s304 = stainless.grade_("304", name="SS 304", mechanical={"yield_strength": 170})
assert s304.density == 8.0 # Inherited
assert s304.properties.mechanical.yield_strength == 170
# Add treatment
passivated = s304.treatment_("passivated", name="SS 304 Passivated")
assert (
passivated.path == "stainless_steel.304.passivated"
) # name -> lowercase with underscores
assert passivated.density == 8.0 # Inherited through chain
Direct Material Access
Load materials and access them directly from the library:
from pymat import aluminum, lyso, stainless
# Direct access to materials
s316L = stainless.s316L
assert s316L.grade == "316L"
al6061 = aluminum.a6061
assert al6061.density == 2.7 # Inherited from aluminum
lyso_crystal = lyso
assert "LYSO" in lyso_crystal.name
Customizing Appearance Safely
Materials reached via pymat["..."] or category imports
(from pymat import stainless) are shared instances — the
same object every caller in the process sees. Mutating
m.vis.roughness = 0.6 or flipping m.vis.finish on those
leaks into every other consumer.
The safe pattern uses two methods that pair cleanly:
import pymat
from pymat.vis import to_threejs
steel = pymat["Stainless Steel 304"] # registry singleton — DON'T mutate
# Derive an independent variant (1) and attach it to a fresh Material (2)
polished_vis = steel.vis.override(roughness=0.05, finish="polished")
shiny = steel.with_vis(polished_vis)
three = to_threejs(shiny) # uses the polished values
assert three["roughness"] == 0.05
assert steel.vis.material_id == "Metal012" # registry untouched
# ``Vis.override(**deltas) -> Vis`` → deep-copied Vis with deltas
# (finishes deep-copied; identity changes invalidate the texture
# cache atomically; unknown kwargs raise TypeError).
# ``Material.with_vis(vis) -> Material`` → registry-detached Material
# with the supplied Vis attached. Equivalent to ``Material.copy()``
# followed by slotting the new vis in.
# ``Material.copy() -> Material`` → generic detach if you need to
# tweak more than just the Vis.
# Materials *you* construct directly (Material(name="custom",
# vis={...})) are not shared and can be mutated freely.
Physics Properties vs Visualization
Understand the difference between measured optical properties (physics) and rendering properties (visualization):
from pymat import Material
# Create transparent material
glass = Material(
name="Optical Glass",
color=(0.9, 0.9, 0.9, 0.8), # Visual: 80% opaque white
optical={"transparency": 95, "refractive_index": 1.517}, # Physics: 95% transmission
vis={"transmission": 0.8}, # Rendering: how transparent it looks
)
# Physics properties (measured)
assert glass.properties.optical.transparency == 95
assert glass.properties.optical.refractive_index == 1.517
# Visualization properties (rendering)
assert glass.vis.base_color[3] == 0.8 # Alpha
assert glass.vis.transmission == 0.8
Scintillator-Specific Properties
Define detector crystals with optical physics properties:
from pymat import Material
lyso_crystal = Material(
name="LYSO:Ce Crystal",
density=7.1,
optical={
"refractive_index": 1.82,
"transparency": 92,
"light_yield": 32000, # photons/MeV
"decay_time": 41, # ns
"emission_peak": 420, # nm
},
vis={"base_color": (0.0, 1.0, 1.0, 0.85), "transmission": 0.85},
)
assert lyso_crystal.properties.optical.light_yield == 32000
assert lyso_crystal.properties.optical.decay_time == 41
assert lyso_crystal.vis.transmission == 0.85
Temperature-Dependent Materials
Use factory functions for materials with properties that depend on external parameters:
from pymat.factories import water
# Water at different temperatures
cold_water = water(4) # Max density
room_water = water(20) # Room temperature
hot_water = water(80) # Heated
assert cold_water.density > room_water.density
assert room_water.density > hot_water.density
# Verify realistic values
assert 0.99 < cold_water.density < 1.01
assert 0.95 < hot_water.density < 0.98
Air at Different Conditions
Create air material at specific temperature and pressure:
from pymat.factories import air
sea_level = air(15, 1.0) # 15°C, 1 atm
high_altitude = air(15, 0.5) # 15°C, 0.5 atm (5500m)
assert sea_level.density > high_altitude.density
Saline Solutions
Create solutions with specific concentration and temperature:
from pymat.factories import saline, water
# Physiological saline at body temperature
phantom = saline(0.9, temperature_c=37)
# Saline is slightly denser than pure water at same temperature
pure_water_37 = water(37)
assert phantom.density > pure_water_37.density
# Seawater (3.5% NaCl) at 20°C
seawater = saline(3.5, temperature_c=20)
# Higher concentration = higher density
assert seawater.density > phantom.density
Loading Metal Materials
Access various metal materials from the metals category:
from pymat import aluminum, copper, stainless
# Stainless steel variants
s304 = stainless.s304
s316L = stainless.s316L
assert s304.density == s316L.density # Same base density
# Aluminum alloys
al6061 = aluminum.a6061
_ = aluminum.a7075
assert al6061.density == 2.7
# Copper
copper_material = copper
assert copper_material.density == 8.96
Plastic Materials
Access plastic materials for 3D printing and engineering:
from pymat import pc, peek, pla, pmma
# Engineering plastics
assert peek.properties.manufacturing.print_nozzle_temp == 360
# 3D printing plastics
assert pla.properties.manufacturing.printable_fdm is True
# Transparent plastics
assert pmma.properties.optical.transparency == 92
assert pc.properties.optical.transparency == 89
Scintillator Crystals
Access scintillator materials for radiation detectors:
from pymat import bgo, lyso, nai
# LYSO crystal
assert lyso.properties.optical.light_yield == 32000
assert lyso.properties.optical.refractive_index == 1.82
# BGO crystal
assert bgo.properties.optical.light_yield == 8500
# NaI crystal
assert nai.properties.optical.light_yield == 38000
Gas Materials
Access gases for simulation and detector design:
from pymat import air, argon, helium, nitrogen, xenon
# Common gases at STP
assert 0.0012 < air.density < 0.0013 # g/cm³
assert nitrogen.density > helium.density # Helium is lightest
assert xenon.density > argon.density # Heavier noble gases
# Detector gases
assert argon.properties.compliance.radiation_resistant is True
Property Inheritance
Child materials inherit properties from parents unless overridden:
from pymat import Material
# Create material hierarchy
root = Material(
name="Base", density=7.8, thermal={"melting_point": 1500, "thermal_conductivity": 50}
)
grade1 = root.grade_("G1", mechanical={"yield_strength": 400})
assert grade1.density == 7.8 # Inherited
assert grade1.properties.mechanical.yield_strength == 400 # New property
assert grade1.properties.thermal.melting_point == 1500 # Inherited
# Override inherited property
grade2 = root.grade_("G2", thermal={"melting_point": 1600})
assert grade2.properties.thermal.melting_point == 1600 # Overridden
Automatic Mass Calculation
Materials with density automatically calculate shape mass:
from build123d import Box
from pymat import aluminum, stainless
# 10x10x10 mm³ box = 1000 mm³ = 1 cm³
steel_box = Box(10, 10, 10)
stainless.apply_to(steel_box)
# Density = 8.0 g/cm³, Volume = 1 cm³ → Mass = 8.0 g
assert 7.9 < steel_box.mass < 8.1
# Aluminum box
al_box = Box(10, 10, 10)
aluminum.apply_to(al_box)
# Density = 2.7 g/cm³ → Mass = 2.7 g
assert 2.6 < al_box.mass < 2.8
Material Visualization
Materials render with appropriate colors for visualization:
from build123d import Box
from pymat import aluminum, lyso, stainless
# Create shapes
steel_part = Box(10, 10, 10)
al_part = Box(10, 10, 10)
crystal = Box(10, 10, 10)
# Apply materials
stainless.apply_to(steel_part)
aluminum.apply_to(al_part)
lyso.apply_to(crystal)
# Verify colors are set
assert steel_part.color is not None
assert al_part.color is not None
assert crystal.color is not None
# Colors should differ
assert steel_part.color != al_part.color
assert crystal.color != steel_part.color
Material Categories
- Metals: Stainless steel, aluminum, copper, tungsten, lead, titanium, brass
- Scintillators: LYSO, BGO, NaI, CsI, LaBr3, PWO, plastic scintillators
- Plastics: PEEK, Delrin, Ultem, PTFE, ESR, Nylon, PLA, ABS, PETG, TPU, PMMA, PE, PC
- Ceramics: Alumina, Macor, Zirconia, Glass (borosilicate, fused silica, BK7)
- Electronics: FR4, Rogers, Kapton, copper PCB, solder, ferrite
- Liquids: Water, Heavy Water, Mineral Oil, Glycerol, Silicone Oil
- Gases: Air, Nitrogen, Oxygen, Argon, CO₂, Helium, Hydrogen, Neon, Xenon, Methane, Vacuum
Property Groups
Each material can have properties organized in these domains:
- Mechanical: density, Young's modulus, yield strength, hardness
- Thermal: melting point, thermal conductivity, expansion coefficient
- Electrical: resistivity, conductivity, dielectric constant
- Optical: refractive index, transparency, light yield (PHYSICS - measured values)
- Vis (on
material.vis): base_color, metallic, roughness, ior, transmission, textures, finishes — visual/rendering layer, fetches PBR textures from mat-vis on demand - Manufacturing: machinability, printability, weldability
- Compliance: RoHS, REACH, food-safe, biocompatible
- Sourcing: cost, availability, suppliers
Advanced Usage
Custom Materials
Create materials using property group dictionaries:
from pymat import Material
my_material = Material(
name="Custom Alloy",
mechanical={
"density": 8.1,
"youngs_modulus": 200,
"yield_strength": 450
},
vis={
"base_color": (0.7, 0.7, 0.75, 1.0),
"metallic": 1.0,
"roughness": 0.4
}
)
Loading from TOML
from pymat import load_toml
materials = load_toml("my_materials.toml")
my_material = materials["my_material"]
Enrichment from Chemical Formulas
enrich_from_periodictable reads the material's formula, populates
the composition dict (element → atom count), and sets the density
only for pure elements — compound density is not derivable from
periodictable's dataset and requires a crystallographic source like
Materials Project. Molar mass is always available regardless, via the
computed Material.molar_mass property (see the Quick Start).
from pymat import Material, enrich_from_periodictable
# Pure element — density is set
iron = Material(name="Iron", formula="Fe")
enrich_from_periodictable(iron)
assert iron.density == 7.874 # set from periodictable
assert iron.molar_mass == 55.85 # computed from formula
# Compound — composition is set, density is not
alumina = Material(name="Alumina", formula="Al2O3")
enrich_from_periodictable(alumina)
assert alumina.composition == {"Al": 2, "O": 3}
assert alumina.molar_mass == 101.96 # computed from formula
assert alumina.density is None # use enrich_from_matproj for compounds
Optical vs Visual Properties
Important: Understand the distinction between physics and visualization:
-
Optical Properties (
properties.optical.*): Measured/calculated physical valuestransparency: % light transmission (measured)refractive_index: optical property (measured)light_yield: scintillator brightness (measured)
-
Visual Properties (
material.vis.*): Rendering/visualization parametersbase_color: RGBA values (0-1) for displaytransmission: how transparent it LOOKS in rendersmetallic: surface finish appearanceroughness: surface roughness appearancesource+material_id+tier: mat-vis appearance identity (since 3.1; the three fields match mat-vis-client's positional-arg signature — see ADR-0002)textures: PBR texture maps (lazy-fetched from mat-vis oncesource+material_idare set)finishes: named alternate looks (e.g.brushed/polished/oxidized) — each entry is an inline{source, id}tablemtlx(property): MaterialX document accessor (.xml,.export(path),.original)client(property): escape hatch to the sharedMatVisClientsingleton — for any operation not material-keyed
These can differ intentionally! A material might be physically transparent (95% optical transmission) but rendered opaque (0% vis.transmission) for CAD clarity.
License
MIT
Design Decisions (ADRs)
Non-trivial architectural decisions live under docs/decisions/ as
lightweight ADRs. They explain why the code is shaped the way it is
and the conditions under which the decision should be revisited.
- ADR-0001 — Derived chemistry properties live on
Material(whymolar_massis a computed@property, not a stored field)
Links
- GitHub: https://github.com/MorePET/mat
- Issues: https://github.com/MorePET/mat/issues
- PyPI: https://pypi.org/project/py-materials/
- Rust crate (
rs-materials): https://crates.io/crates/rs-materials
Metadata
Release files for py-materials 3.10.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| py_materials-3.10.0.tar.gz | 147.9 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| py_materials-3.10.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 222.7 kB
Release files / py_materials-3.10.0.tar.gz
| Download URL | py_materials-3.10.0.tar.gz |
|---|---|
| Size | 147.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1a4e7dfcc60cc84fa22600498dc28c0e4395eae6569faaeffc1a9415d9992fc0
|
|
BLAKE2b-256 checksum How to use checksums |
db5594134495638030be3867a9113b2608a96a3933340180155534fd10a7059b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 6, 2026.
Transparency logRelease files / py_materials-3.10.0-py3-none-any.whl
| Download URL | py_materials-3.10.0-py3-none-any.whl |
|---|---|
| Size | 74.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7c6ea91d6fa9db3e097c21583506e60a1c24c04c953b0df59fa9b220e840f28e
|
|
BLAKE2b-256 checksum How to use checksums |
b68ec6950e5e478f7f42e1e130b95456a9b468f1606132c04503fc950928f056
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 6, 2026.
Transparency logSecurity for py-materials 3.10.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for py_materials-3.10.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 6, 2026.
Transparency logA 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.