pyldraw3
A modern Python package for creating and manipulating LDraw format files - the standard for CAD applications that create LEGO models. It is a drop-in replacement for the unmaintained pyldraw library.
Features
- 🧱 Complete LDraw Support: Full compatibility with the LDraw standard format
- 🐍 Pythonic API: Import LEGO parts directly as Python modules
- 📦 Dynamic Library Generation: Automatically generate Python modules from LDraw libraries
- 📜 Comprehensive Guide: Jump into the quick start below, or read the published documentation and local guide source
Table of Contents
- Features
- Quick Start
- Requirements
- Configuration
- CLI Reference
- Development
- Architecture
- Contributing
- License
- Trademarks
- Credits
Quick Start
Installation
uv add pyldraw3
Setup
Activate your virtual environment and set up the LDraw library - this will download the LDraw library and create the parts classes:
source .venv/bin/activate
ldraw download --yes
ldraw generate --yes
By default ldraw download fetches the complete LDraw release (~80 MB, everything LDraw publishes). To pin a specific dated release instead - useful for reproducible builds or a smaller download - pass --version, e.g. ldraw download --version 2018-02 --yes. Each downloaded release is cached separately, and ldraw generate builds ldraw.library.* from whichever release is currently configured (see Configuration).
Examples
Check the examples/ directory for sample scripts demonstrating various features:
# Run an example
python examples/figures.py > my_model.ldr
Basic Usage
This package allows users to create LDraw scene descriptions using Pieces which are Parts that have a specific position and orientation. Piece.to_ldraw() and Group.to_ldraw() produce LDraw file content; str(piece) and str(group) delegate to those serializers:
from ldraw.library.colours import Light_Grey
from ldraw.library.parts.bricks import Brick1X2WithClassicSpaceLogoPattern
from ldraw.pieces import Group, Piece
from ldraw.geometry import Vector, Identity
# Create a simple model
model = Group()
Piece(Light_Grey, Vector(-10, -32, -90), Identity(), Brick1X2WithClassicSpaceLogoPattern, model)
with open("my_model.ldr", "w") as ldr_file:
print(model, file=ldr_file)
ldraw.library.* is generated by ldraw generate and gives you every part as an importable, autocompletable Python name (as used above). If you'd rather look a part up by its catalog description or LDraw code at runtime - for example when the part name isn't known until your program runs - load the parts catalog directly instead:
from pathlib import Path
from ldraw.config import Config
from ldraw.parts import Parts
config = Config.load()
parts = Parts(Path(config.ldraw_library_path) / "ldraw" / "parts.lst")
cowboy_hat = parts.get_entry_by_description("Hat Cowboy").code # -> "3629"
head = parts.get_entry_by_description("Head with Solid Stud").code # -> "3626a"
brick1x1 = parts.get_entry_by_description("Brick 1 x 1").code # -> "3005"
Both cowboy_hat and Brick1X2WithClassicSpaceLogoPattern are just LDraw part code strings, so either style can be passed as the part argument to Piece.
For new code, Piece.place offers a keyword-first constructor with sensible defaults (main colour, origin position, identity rotation):
from ldraw import Piece
piece = Piece.place("3005", colour=4) # red 1x1 brick at the origin
The core construction types are all importable from the top-level package:
Model, Piece, Group, Person, Colour, Vector, Matrix,
Identity, Parts, and the read/validate/BOM helpers. Deep imports
(from ldraw.pieces import Piece) keep working.
Building Models Programmatically
Model bridges piece construction and file I/O — build, query, and save:
from ldraw import Group, Model, Person, Piece, Vector
model = Model.from_pieces(
[Piece.place("3001", colour=4)],
name="scene.ldr",
description="A scene",
author="you",
)
# Figures flow in through their group
group = Group()
figure = Person(position=Vector(0, -48, 0), group=group)
figure.head(colour=14)
figure.torso(colour=4)
model.add_group(group)
# Submodels: registers the section and returns the referencing piece
wheels = Model.from_pieces([Piece.place("3005", colour=0)], name="wheels.ldr")
model.add_submodel(wheels, position=Vector(0, -24, 0))
model.find_pieces(colour=4) # query by part and/or colour
list(model.iter_pieces()) # leaf pieces, submodels expanded
model.bill_of_materials() # counted (part, colour) rows
model.save("scene.ldr")
Reading and Writing Model Files
read_model parses whole .ldr and .mpd files - including MPD 0 FILE /
0 NOFILE sections - into a Model you can inspect, modify, and save:
from ldraw import read_model
model = read_model("my_model.ldr")
print(model.description, model.author)
for piece in model.pieces:
print(piece.part, piece.position)
# MPD documents: the first 0 FILE section is the root model,
# later sections are submodels resolvable from their type-1 references.
for ref in model.pieces:
if (submodel := model.submodel_for(ref)) is not None:
print(f"{ref.reference} -> {len(submodel.pieces)} pieces")
model.save("my_model_out.ldr")
Every parsed object (Piece, Line, Triangle, Quadrilateral,
OptionalLine, Comment, MetaCommand) has a to_ldraw() method, so
parsed content round-trips back to LDraw text - subfile reference casing
included, byte for byte. Parse errors report the file and 1-based line
number. ldraw validate exposes the same checks on the command line.
Building steps are first-class: model.add_step() appends a 0 STEP
marker and model.steps returns the pieces grouped step by step. Header
lines are managed through model.set_header(description=..., name=..., author=..., ldraw_org=..., license=...).
Part Geometry Queries
Parts can answer placement questions directly from the library's part
files, resolving subfile references recursively:
from ldraw import Parts
parts = Parts.get("~/ldraw/parts.lst")
box = parts.bounding_box("3001") # axis-aligned, in LDU
print(box.min, box.max, box.size) # origin sits on the stud plane; +Y is down
print(parts.stud_positions("3001")) # centres of the 8 top studs
for stud in parts.studs("3062b"): # every stud primitive, tubes included
print(stud.name, stud.description, stud.position, stud.is_top_stud)
Boxes are composed from memoized per-subfile boxes, exact under the
axis-aligned rotations that dominate the library. Stud queries expand stud
group primitives down to individual stud* references; is_top_stud
distinguishes upward connectors from underside tubes.
IDE Autocompletion and Type Checking
The package ships a py.typed marker, so the hand-written API is typed for
mypy/pyright out of the box. For the generated ldraw.library.* modules,
run:
ldraw stubs
from your project root. This writes an ldraw-stubs/ PEP 561 stub package
that Pylance/pyright discover automatically (for mypy, ensure the project
root is on mypy_path). Regenerate the stubs after switching library
versions with ldraw download/ldraw generate, and add ldraw-stubs/ to
your .gitignore. Use --out PATH to write the stubs somewhere else.
Stub discovery from a project root is standard PEP 561 behavior but can vary
by tool version - the stubs mirror the generated modules exactly, so pointing
your checker's stub path at them always works.
Requirements
- Python 3.12+
Configuration
ldraw download and ldraw generate write their settings to a YAML config file in an OS-appropriate config directory (via platformdirs). Run ldraw config to see the current values:
$ ldraw config
generated_path: /Users/you/Library/Application Support/pyldraw3/generated
ldraw_library_path: /Users/you/Library/Caches/pyldraw3/2018-02
ldraw_library_path- the downloaded LDraw release currently in use (switch releases by re-runningldraw download --version ...)generated_path- whereldraw generatewrites theldraw.library.*package that youimport
CLI Reference
The published docs include a standalone CLI reference.
usage: ldraw [-h] command ...
Download the LDraw parts library and generate the ldraw.library Python
modules.
positional arguments:
command
download Download and unpack an LDraw parts library release.
generate Generate the ldraw.library modules from the downloaded library.
parts Query the parts catalog.
validate Validate an LDraw file (.ldr, .mpd, or .dat).
bom Print a bill of materials for an LDraw model file.
stubs Write a type-stub package for ldraw.library into your project.
config Print the current configuration.
version Print the installed pyldraw3 version.
options:
-h, --help show this help message and exit
ldraw download [--version VERSION] [--yes]- download and unpack an LDraw release (default version:complete)ldraw generate [--yes] [--force]- (re)generateldraw.library.*from the currently configured release;--forceregenerates even if already up to dateldraw parts search TERM [--limit N]- search the catalog by description or code substring (exit code 1 when nothing matches)ldraw parts info CODE- show a part's description, category, file path, and the generated-library import to useldraw validate FILE [--strict]- lint a file: malformed lines, unknown parts and colour codes are errors; suspect matrices, legacy dithered colours, and unknown meta-commands are warnings (--strictmakes warnings fail; exit code 1 on errors)ldraw bom FILE [--format table|csv|json] [-o OUT]- print a bill of materials counted by part and colour, submodels expandedldraw stubs [--out PATH]- write anldraw-stubs/PEP 561 stub package for IDE autocompletionldraw config- print the current configuration as YAMLldraw version- print the installedpyldraw3version
Run ldraw <command> --help for a command's full option list.
Development
This project uses uv for dependency management and packaging.
Setup Development Environment
# Clone the repository
git clone https://github.com/hbmartin/pyldraw3.git
cd pyldraw3
# Install dependencies
uv sync
# Activate virtual environment
source .venv/bin/activate
# Download and set up LDraw library (fetches the latest complete release;
# pass --version 2018-02 to pin a dated release for reproducible builds)
uv run ldraw download --yes
uv run ldraw generate --yes
Development Commands
# Run tests
uv run pytest # All tests
uv run pytest --cov=ldraw # With coverage
uv run pytest --integration # Integration tests only
# Code formatting and linting
uv run ruff format . # Format code
uv run ruff check # Lint code
uv run ruff check --fix # Fix linting issues
# Build package
uv build
Documentation Site
The documentation site is built with Zensical from
the Markdown sources in docs/. The API reference is expanded from
ldraw.__all__ at build time and rendered from type annotations and docstrings
via mkdocstrings-python.
uv run zensical serve
uv run zensical build --clean --strict
GitHub Pages must be configured to publish from GitHub Actions. If Pages is still configured to publish from a branch, GitHub will keep serving that branch instead of the Zensical workflow artifact.
Architecture
Core Components
- CLI Interface (
ldraw/cli.py): Command-line interface withdownload,generate,parts,validate,stubs,config, andversionsubcommands - Dynamic Library Generation (
ldraw/generation/): Converts LDraw libraries to Python modules (with.pyistubs) - Import System (
ldraw/imports.py): Custom meta path hook for dynamic imports
Key Classes
Model(ldraw/model.py) - Reads and writes whole.ldr/.mpdmodel filesParts- Manages parts catalog and loadingPiece- Represents individual LEGO pieces in modelsFigure- High-level minifigure construction- Geometry classes - Matrix operations and 3D mathematics
Contributing
Contributions are welcome! See CONTRIBUTING.md for the fork/branch/PR workflow.
License
This project is licensed under the GNU General Public License v3.0 or later - see the license (COPYING) file for details.
pyldraw, a Python package for creating LDraw format files.
Copyright (C) 2008 David Boddie <david@boddie.org.uk>
Some parts Copyright (C) 2021 Matthieu Berthomé <matthieu@mmea.fr>
Some parts Copyright (C) 2025 Harold Martin <harold.martin@gmail.com>
Trademarks
LDraw is a trademark of the Estate of James Jessiman. LEGO is a registered trademark of the LEGO Group.
Credits
- Original Author: David Boddie
- Previous Maintainer: Matthieu Berthomé
- Current Maintainer: Harold Martin
This repository was extracted from the original Mercurial repository and modernized for current Python practices.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyldraw3-1.2.0.tar.gz.
File metadata
- Download URL: pyldraw3-1.2.0.tar.gz
- Upload date:
- Size: 63.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2f96133d8459fb44930991493bd024d201019f812a921261aea3f777e6d92d2
|
|
| MD5 |
8aff3b4c2e33cad3f10e27a191b8a57d
|
|
| BLAKE2b-256 |
7770f51f6784d960f0ddfc6d816d6c1e30e0519d93234d310931df63dd02e631
|
Provenance
The following attestation bundles were made for pyldraw3-1.2.0.tar.gz:
Publisher:
publish.yml on hbmartin/pyldraw3
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyldraw3-1.2.0.tar.gz -
Subject digest:
f2f96133d8459fb44930991493bd024d201019f812a921261aea3f777e6d92d2 - Sigstore transparency entry: 2129727965
- Sigstore integration time:
-
Permalink:
hbmartin/pyldraw3@3d34542a8195e76843da8d437ed704dd675a72b8 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/hbmartin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d34542a8195e76843da8d437ed704dd675a72b8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pyldraw3-1.2.0-py3-none-any.whl.
File metadata
- Download URL: pyldraw3-1.2.0-py3-none-any.whl
- Upload date:
- Size: 78.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d429a626519be9142af3c691ce336145da585447597c15303b830d52628dce11
|
|
| MD5 |
a876c12b080bc82e9223208c84df9810
|
|
| BLAKE2b-256 |
6d9bec2809f9b5ad74a386f55530bd519cb3047bc0af1288a8d0f8bf0fec451f
|
Provenance
The following attestation bundles were made for pyldraw3-1.2.0-py3-none-any.whl:
Publisher:
publish.yml on hbmartin/pyldraw3
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyldraw3-1.2.0-py3-none-any.whl -
Subject digest:
d429a626519be9142af3c691ce336145da585447597c15303b830d52628dce11 - Sigstore transparency entry: 2129728085
- Sigstore integration time:
-
Permalink:
hbmartin/pyldraw3@3d34542a8195e76843da8d437ed704dd675a72b8 -
Branch / Tag:
refs/tags/v1.2.0 - Owner: https://github.com/hbmartin
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3d34542a8195e76843da8d437ed704dd675a72b8 -
Trigger Event:
release
-
Statement type: