rebolt-cgm
Load a CGM figure from an aircraft maintenance manual in Python, and get its SVG, hotspots, text and PNG crops with sidecars.
rebolt_cgm reads a CGM (ISO/IEC 8632) file in the WebCGM 2.0 or 2.1, S1000D or ATA iSpec 2200 profile, binary or clear text, plain or gzip-compressed, and applies its ATA CMP companion. It gives the SVG, the sheet, the hotspots, the text runs and the diagnostics. It renders any region to PNG with a sidecar that maps the pixels back to the sheet, and splits a sheet into tiles. Use it to convert manuals in bulk, or to crop figures for a vision model and map the model's answer back to the hotspots.
The package is a thin layer over the cgm-tools Rust library, so it draws exactly what the cgm command line and the browser viewer draw. A Figure is immutable. Loading and rendering release the GIL, so threads that share one figure render in parallel. Results are frozen dataclasses generated from the Rust types, and the package ships type hints. Failures raise cgm.LoadError, cgm.RegionError or cgm.RenderError, each with a typed kind.
Install
pip install rebolt-cgm
pip install "rebolt-cgm[pil]" # with Pillow, for Rendered.to_pil()
There is a wheel for CPython 3.10 and later on macOS (arm64 and x86_64), Linux (x86_64 and aarch64, glibc 2.17 or later) and Windows (x86_64). It has no required dependencies. The import package is rebolt_cgm, and the examples import it as cgm.
Quick start
import rebolt_cgm as cgm
figure = cgm.Figure.load("figure.cgm", companion=cgm.DISCOVER)
print([hotspot.id for hotspot in figure.hotspots])
with open("figure.svg", "w") as file:
file.write(figure.svg)
rendered = figure.render(cgm.RegionSheet(), cgm.OutputSizeDpi(dpi=150.0))
with open("figure.png", "wb") as file:
file.write(rendered.png)
companion=cgm.DISCOVER looks for the companion beside the CGM file, as cgm convert --discover-companion does. To name it, pass its path or its bytes: companion="figure.cmp". Figure.load also takes the CGM as bytes.
Convert a folder to SVG and PNG
For large batches, use the CLI: cgm convert DIR --out OUT --svg --png --discover-companion converts in parallel, resumes after an interruption, and writes a report. For a few hundred files, or to keep the work in Python:
import pathlib
from concurrent.futures import ThreadPoolExecutor
import rebolt_cgm as cgm
source, out = pathlib.Path("figures"), pathlib.Path("out")
out.mkdir(exist_ok=True)
def convert(path: pathlib.Path) -> str | None:
try:
figure = cgm.Figure.load(path, companion=cgm.DISCOVER)
png = figure.render(cgm.RegionSheet(), cgm.OutputSizeDpi(dpi=150.0)).png
except (cgm.LoadError, cgm.RenderError) as error:
return f"{path.name}: {error.kind}"
(out / f"{path.stem}.svg").write_text(figure.svg)
(out / f"{path.stem}.png").write_bytes(png)
return None
with ThreadPoolExecutor() as pool:
failures = [f for f in pool.map(convert, sorted(source.glob("*.cgm"))) if f]
print("\n".join(failures))
Crop a hotspot for a vision model, with its sidecar
The sidecar lists every hotspot and text run that shows in the image, with its box in image pixels, so a model's answer about the image maps back to the figure.
import dataclasses
import json
import rebolt_cgm as cgm
figure = cgm.Figure.load("figures/pump.cgm", companion=cgm.DISCOVER)
crop = figure.render(
cgm.RegionHotspot(id=cgm.HotspotId("VALVE_1"), margin=cgm.MarginMillimetres(5.0)),
cgm.OutputSizeFit(width=1568, height=1568),
)
image = crop.png # PNG bytes for the model request
for hotspot in crop.sidecar.hotspots:
print(hotspot.id, hotspot.bizdesc, hotspot.pixels)
sidecar_json = json.dumps(dataclasses.asdict(crop.sidecar))
try:
figure.render(cgm.RegionHotspot(id=cgm.HotspotId("NOPE"), margin=cgm.MarginFraction(0.0)),
cgm.OutputSizeWidth(px=800))
except cgm.RenderError as error:
assert isinstance(error.kind, cgm.RenderErrorUnknownHotspot)
The same crop from the shell is cgm render FIG.cgm --discover-companion --region hotspot:ID:5mm --fit 1568x1568 --out crop.png --sidecar crop.json, and the PNG is byte-identical. In both, the size is the size of the image you get, after rotation or --rotate turns it.
Tile a sheet at 300 DPI
from concurrent.futures import ThreadPoolExecutor
import rebolt_cgm as cgm
figure = cgm.Figure.load("figures/schematic.cgm")
tiles = figure.tiles(3, 2, cgm.MarginMillimetres(10.0)) # neighbours share 10 mm
def render(tile: cgm.Tile) -> cgm.Rendered:
return figure.render(cgm.RegionUnits(rect=tile.rect), cgm.OutputSizeDpi(dpi=300.0))
with ThreadPoolExecutor() as pool:
for tile, rendered in zip(tiles, pool.map(render, tiles)):
with open(f"r{tile.row}c{tile.column}.png", "wb") as file:
file.write(rendered.png)
A CGM with several pictures
ISO/IEC 8632-1 lets a metafile hold any number of pictures, and each is its own image. WebCGM, S1000D and ATA GREXCHANGE files hold one, so most callers never select. Figure.pictures lists them without writing any SVG, picture= selects one, cgm.PictureIndex(1) by zero-based index or cgm.PictureName("Detail A") by its BEGIN PICTURE identifier, and Figure.load_all loads every picture from one decode. A companion applies to the first picture only; the others report companion-picture.
import rebolt_cgm as cgm
for info in cgm.Figure.pictures("deck.cgm"):
print(info.index, info.name)
detail = cgm.Figure.load("deck.cgm", picture=cgm.PictureIndex(1))
by_name = cgm.Figure.load("deck.cgm", picture=cgm.PictureName("Detail A")) # exactly one picture has this name
for figure in cgm.Figure.load_all("deck.cgm"):
with open(f"deck~p{figure.picture.index}.svg", "w") as file:
file.write(figure.svg)
Without picture=, a CGM with several pictures raises LoadError with kind LoadErrorPictureCount.
A metafile may also hold no picture (ISO/IEC 8632-1 B.3.1). Figure.pictures then returns (), Figure.load_all returns (), and Figure.load raises LoadError with kind LoadErrorPictureCount(found=0), whose message says there is nothing to write. Its .diagnostics hold the information no-picture.
Checking a profile
Loading checks no profile rule. profile= on Figure.load and Figure.load_all checks the CGM against the rules of a profile: cgm.ProfileCheck.DETECT for the one its METAFILE DESCRIPTION declares, or WEBCGM, S1000D or GREXCHANGE whatever it declares. What the check finds joins .diagnostics with source cgm.DiagnosticSource.PROFILE; it never fails the load, and the SVG is the same with and without it.
checked = cgm.Figure.load("fig.cgm", profile=cgm.ProfileCheck.DETECT)
findings = [d for d in checked.diagnostics if d.source is cgm.DiagnosticSource.PROFILE]
Errors
| Exception | When | kind |
|---|---|---|
LoadError |
the CGM has an error, holds several pictures and none is selected or holds none, the selection names no single picture, or the picture cannot be written as SVG | LoadErrorDecode, LoadErrorPictureCount, LoadErrorPictureOutOfRange, LoadErrorUnknownPictureName, LoadErrorAmbiguousPictureName, LoadErrorUnrepresentable; .diagnostics holds every diagnostic |
RenderError |
the region or size cannot be placed on the sheet, the hotspot is unknown or has no box, or the image is above 64 Mpx | RenderErrorUnknownHotspot, RenderErrorNoPhysicalSize, RenderErrorTooLarge, ... |
RegionError |
tiles with a negative overlap, or a millimetre overlap on a sheet with abstract scaling |
RegionErrorInvalidRegion, RegionErrorNoPhysicalSize |
ValueError |
a request value outside its Rust type: a negative rectangle, a non-finite number, a negative pixel count, zero tiles, a picture index outside 0 to 2^32 - 1 | |
OSError |
a file cannot be read |
All three typed exceptions subclass cgm.CgmError. Branch on the class and its kind, not on the message.
Development
The classes in python/rebolt_cgm/_types.py are generated from schema/cgm.schema.json, which is generated from the Rust types. After changing a Rust type, regenerate both:
cargo run -p cgm-schema -- write
The test generated_files_are_fresh (cargo test -p cgm-schema) fails while either file is stale. To build and test the wheel:
cargo build --release -p cgm-cli # the parity test compares with `cgm render`
cd crates/cgm-python
python3 -I ../../tools/release/licenses.py write python . # LICENSE.md, COMMERCIAL.md, licenses/
maturin build --release -o dist
pip install dist/rebolt_cgm-*.whl pytest mypy Pillow
pytest tests # includes mypy --strict and stubtest
Licence
rebolt-cgm is free for research, education, personal and non-profit use under the PolyForm Noncommercial License 1.0.0, in LICENSE.md among the wheel's licence files.
Any use by a company needs a commercial licence. That includes evaluation, internal tools and pipelines, and running the package on a server or behind an API. To get a commercial licence, write to founders@rebolt.ai. COMMERCIAL.md lists the uses that need one.
Copyright (c) 2026 Tieki Inc (Rebolt AI). The wheel also carries third-party components under their own licences, which licenses/THIRD-PARTY-NOTICES.md lists.
Metadata
Release files for rebolt-cgm 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| rebolt_cgm-0.1.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details for rebolt_cgm-0.1.0-cp310-abi3-win_amd64.whl |
| rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details for rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details for rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| rebolt_cgm-0.1.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for rebolt_cgm-0.1.0-cp310-abi3-macosx_11_0_arm64.whl |
| rebolt_cgm-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for rebolt_cgm-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 17.9 MB
Release files / rebolt_cgm-0.1.0-cp310-abi3-win_amd64.whl
| Download URL | rebolt_cgm-0.1.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1fbbb99da585391a3fc23b58aa7886fb2d06fc13b4a7c788799b563a2c1ca30b
|
|
BLAKE2b-256 checksum How to use checksums |
8e59e523acee67459fb528c427c5cc4c8ebae3392614461c30c26285f0692cbf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 11, 2026.
Transparency logRelease files / rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
100cd8df80025358f98b13d0ec44d16b7af7fa8236c68dd427c66c9457781309
|
|
BLAKE2b-256 checksum How to use checksums |
8b8a0f82c9c2e5b4f1560a0c39b00616ec1e8321dd08c5ea2a99deaae0706133
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 11, 2026.
Transparency logRelease files / rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | rebolt_cgm-0.1.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
a42228019cceda210bdb3175868f0198280047ad3c2d1d4d03a0cefa226a0200
|
|
BLAKE2b-256 checksum How to use checksums |
e142e1cd57f620d62210c5c65b707b3638477be11b3260d920b027c214b3e752
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 11, 2026.
Transparency logRelease files / rebolt_cgm-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | rebolt_cgm-0.1.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
471a8fa6e12fcdce768898fb8afc3948003d334d1d877901a32fdf552e52a0df
|
|
BLAKE2b-256 checksum How to use checksums |
70f2acb4ff8a915716a920edb2f91888744fa9fff268647a5dea549902e043ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 11, 2026.
Transparency logRelease files / rebolt_cgm-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | rebolt_cgm-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.7 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
b15744abe6077df041f86b4d17726db8fa4a1b7461957434e42aee45b47aa264
|
|
BLAKE2b-256 checksum How to use checksums |
606862c3afda418746aafd71fcca79334ac57279e18e20b4adb3bd20a378ba6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Nov 11, 2026.
Transparency logSecurity for rebolt-cgm 0.1.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 5 files in this release have been uploaded with provenance metadata.
Attestation for rebolt_cgm-0.1.0-cp310-abi3-win_amd64.whl
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 Nov 11, 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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.