trine is a Rust CLI that converts static triangle-mesh scenes between FBX,
3MF, STL, OBJ, OFF, glTF, GLB, DAE (COLLADA), PLY, and STEP.
- Resolves units, scale, and up-axis, with explicit overrides.
- Keeps the node hierarchy where the target supports it and bakes transforms where it does not.
- Copies encoded texture bytes without decoding them.
- Writes one output per group with
--split-groups. - Reports unsupported or approximated data.
--strictfails on data loss. - Uses wgpu for vertex transforms, smooth normals, and tangent generation on large meshes, with a CPU fallback.
cargo install --path .
# CPU-only build:
cargo install --path . --no-default-featuresRequirements: Rust 1.87 or newer (edition 2024), a C compiler for the vendored
ufbx sources, and on Linux the usual Vulkan/GL headers if you build with the
gpu feature (enabled by default).
trine <INPUT> <OUTPUT> [OPTIONS]
| Option | Meaning |
|---|---|
-f, --from <FORMAT> |
Input format; auto (default) uses the extension |
-t, --to <FORMAT> |
Output format; defaults to the output extension |
--split-groups |
Write one file per source group/node/object |
--scale <FACTOR> |
Uniform scale applied after unit conversion |
--scale-axes X,Y,Z |
Per-axis scale multipliers |
--unit-in <UNIT> |
Source units: um, mm, cm, m, in, ft |
--unit-out <UNIT> |
Target units |
--no-unit-conversion |
Preserve raw coordinates; disable unit defaults |
--up-axis <AXIS> |
Target up axis: y or z |
--source-up-axis <AXIS> |
Override the detected source up axis |
--keep-axes |
Disable up-axis conversion |
--normals <MODE> |
preserve (default), smooth, flat, none |
--gpu <MODE> |
auto (default), on, off |
--tessellation-tolerance <V> |
STEP tessellation tolerance in source units |
--force |
Overwrite existing outputs |
--strict |
Exit 1 if the conversion would lose input data |
-q, --quiet |
Suppress the summary and warnings |
-v, --verbose |
Print format, unit, and axis diagnostics |
Exit codes: 0 success, 1 conversion, validation, or strict failure, 2
command line usage error. Outputs are staged in a temporary directory and moved
into place after the conversion succeeds.
| Formats | Behavior |
|---|---|
| OBJ, STL, PLY, OFF | No unit metadata. Input is assumed to be millimeters. |
| FBX | Reads UnitScaleFactor; assumes centimeters only if absent. |
| 3MF | Reads and writes the 3MF unit attribute (default millimeters). |
| DAE | Reads and writes <unit meter="..."> (default meters). |
| glTF / GLB | Always meters. --unit-out other than m is rejected. |
| STEP | Reads SI and conversion-based length units; writes the resolved target unit. |
--no-unit-conversion keeps raw numbers. With glTF/GLB the file still declares
meters, so a note is printed. Unit assumptions are warnings and do not fail
--strict. Loss of unit metadata when writing a unitless target does.
Format defaults are Y-up for FBX, DAE, glTF, GLB, and OBJ; Z-up for 3MF, STEP, STL, PLY, and OFF. Geometry is rotated only when the source and target conventions differ. STEP and 3MF metadata describe their conventional Z-up coordinates.
--strict fails when a conversion would drop or approximate input data:
materials, textures, UVs, vertex colors, normals, tangents, hierarchy, animation,
skinning, morph targets, cameras, lights, constraints, or unsupported shader
graphs. Unit assumptions, nonstandard but valid choices, and GPU fallbacks are
reported but do not fail strict mode.
--split-groups writes one file per non-empty node/object. With an output
pattern containing {group}, each piece replaces it with the sanitized node
name. Without a pattern, names are <output_stem>_<group>.<ext>, or
<input_stem>_<group>.<ext> when the output is a directory. Empty groups are
skipped; duplicate names get _2, _3; empty names become group_0001.
STL, PLY, and OFF have no group concept and ignore splitting: they write one merged file and warn.
| Read | Write | Normals | Tangents | UVs | Vertex colors | Materials | Textures | Units | Hierarchy | |
|---|---|---|---|---|---|---|---|---|---|---|
| FBX | yes | yes (7.4 ASCII) | yes | yes | yes | yes | PBR flattened to Phong | copied | yes | yes |
| 3MF | yes | yes | – | – | – | – | base color | – | yes | components |
| STL | yes | yes (binary) | facet normals only | – | – | – | – | – | – | – |
| OBJ | yes | yes + MTL | yes | – | yes | – | Phong | copied | – | – |
| OFF | yes (COFF) | yes (COFF) | – | – | – | yes | – | – | – | – |
| glTF | yes | yes | yes | yes | yes | yes | PBR metallic-roughness | copied/embedded | m only | yes |
| GLB | yes | yes | yes | yes | yes | yes | PBR metallic-roughness | embedded | m only | yes |
| DAE | yes | yes (1.4.1) | yes | – | yes | yes | Phong | base color | yes | yes |
| PLY | yes | yes (binary LE) | yes | – | yes | yes | – | – | – | – |
| STEP | yes (tessellated) | yes (AP214 faceted) | – | – | – | – | – | – | yes | flattened |
Notes:
- FBX import uses the vendored
ufbxC library and supports binary and ASCII FBX from version 3000 onward. Export writes FBX 7.4 ASCII with models, geometry layers, Phong materials, and external textures. Shader graphs are flattened; skinning, animation, morphs, cameras, lights, and constraints are reported and skipped. - STEP import tessellates B-rep shells with
truck; the tolerance is--tessellation-tolerance, or 0.1% of the shell diagonal by default. STEP output is an AP214AUTOMOTIVE_DESIGNfile containing a faceted B-rep (MANIFOLD_SOLID_BREP+CLOSED_SHELLfor closed primitives,SHELL_BASED_SURFACE_MODEL+OPEN_SHELLotherwise). Only geometry and units are written; materials, UVs, and vertex normals are reported as dropped. - DAE import covers
<triangles>,<polylist>,<polygons>,<trifans>, and<tristrips>, node matrices/TRS, materials, and diffuse textures. Export writes Phong materials and base-color textures. Tangents are not part of COLLADA core and are dropped. - 3MF core only: no textures, UVs, normals, or vertex colors. Base colors are preserved. Objects with several materials are split into component objects so each face keeps its color.
- STL import does not create vertex normals; the writer emits the geometric facet normal.
- PLY writes binary little-endian with
x/y/z, optionalnx/ny/nz,s/t, andred/green/blue/alpha. - OBJ export writes a sibling
.mtl. PBR metallic values are approximated with the Phong model and reported. - GLB is used for embedded buffers and images;
.gltfwrites a sibling.binand a texture directory. Image bytes are copied, never decoded or re-encoded.
The default build enables the gpu feature. With --gpu auto (the default),
the GPU is used for meshes with at least 50,000 vertices when an adapter is
available; otherwise the CPU path runs. --gpu on requires an adapter and fails
if none exists. --gpu off, and builds with --no-default-features, do not
reference wgpu symbols.
Compute work:
- vertex positions, normals, and tangents during transform baking;
- area-weighted smooth normals using fixed-point atomic accumulation;
- per-vertex tangent/bitangent accumulation and Gram-Schmidt orthonormalization.
CPU and GPU results are compared in tests/gpu.rs; run that gated test with
TRINE_TEST_GPU=1 cargo test --test gpu on a machine with an adapter.
Oversized buffers, mapping failures, or a missing adapter fall back to the CPU
instead of producing partial results.
src/
cli.rs argument parsing and validation
convert.rs end-to-end pipeline
error.rs exit-code-aware errors
report.rs warning taxonomy, de-duplication, strict accounting
scene/ canonical scene, units, transforms, triangulation,
validation, topology
io/ one module per format; reads/writes the canonical scene
ops/ capability filtering, flattening, splitting, normals,
tangents, GPU acceleration
output/ staged writes and texture planning
The canonical Scene is format-neutral: nodes with local transforms, meshes
with one or more primitives, core PBR materials, encoded images, and
unit/up-axis metadata. Readers triangulate and do not apply unit defaults;
defaults are resolved once in convert.rs. Writers receive a scene filtered for
their capabilities, so warnings are reported once per feature category rather
than per triangle.
Generated group and texture file names are sanitized ([A-Za-z0-9._-]), and
texture references are relative to the output file.
cargo test # default (GPU) build
cargo test --all-features
cargo test --no-default-features --lib
TRINE_TEST_GPU=1 cargo test --test gpu
cargo build --release --no-default-featuresTests generate fixtures at runtime and validate writers by reading their output
back with native readers (ufbx, the trine glTF reader, the custom 3MF and
PLY readers, dae-parser, tobj, stl_io, and ruststep/truck). There are
no checked-in binary fixtures. Unit tests cover unit and axis math, warning
de-duplication, strict accounting, triangulation, topology, and PLY/OBJ details.
CLI tests cover exit codes, --strict, --quiet, --force, unit conversion,
axis conversion, and group splitting.
- Static triangle meshes only. Lines, points, NURBS/Bezier surfaces, skinning, morph targets, animation, cameras, lights, and constraints are not converted.
- One UV set, one vertex color set, and core PBR materials. Format-specific material graphs are flattened and reported.
- STEP import is a tessellating importer, not a B-rep kernel. STEP export is faceted and does not carry analytic surfaces.
- DAE import keeps the diffuse texture; other texture slots are not imported. DAE export writes only base-color textures.
- Texture images are copied byte-for-byte and never validated or transcoded.
- FBX export is ASCII 7.4 only; binary FBX output is not implemented.
MIT. See LICENSE.