Skip to content

Models

Build scripts create models with a ModelBuilder, and a model stores only part ids, colors and poses.

ModelBuilder

ModelBuilder(
    name: str = "model", *, catalog: Catalog | None = None
)

Build a model in agent-friendly coordinates.

Examples:

>>> builder = ModelBuilder("wall")
>>> builder.place("3001", at=(0, 0, 0), color="Red")
'e1'
>>> builder.place("3001", at=(0, 2, 3), color="Red", rotation="R90")
'e2'
>>> builder.build().element_count()
2

Start an empty model, resolved against catalog or the default one.

catalog property

catalog: Catalog

The catalog this builder resolves parts and colors against.

component

component(name: str) -> ComponentBuilder

Start a reusable component and return the builder that fills it.

A script may fill components in any order, before or after it places their instances.

Raises:

Type Description
ValueError

If a component with this name already exists.

build

build() -> Model

Return the finished model.

Raises:

Type Description
ValueError

If an instance places an unknown or empty component, or if components place each other in a cycle.

provenance

provenance() -> Provenance

Return where the build script placed every element and instance so far.

ComponentBuilder

ComponentBuilder(scope: _Scope)

Fill one component with elements and instances.

Wrap the scope a component's parts go into.

model

The canonical model format: what a build is, stored once.

A model is a small tree: top-level elements and instances, plus the named components that instances place. The model refers to catalog parts and colors by id and never copies their data, so analyses derive everything else, such as world geometry and connections. Positions are in 0.4 mm base units, and rotations use the grid rotation names of :mod:bricky.geometry.

Model

A build: elements and instances, plus the components the instances place.

element_count

element_count() -> int

Count the elements the model expands to, with every instance unfolded.

Component

A group of elements and nested instances, stored once and placed by instances.

Element

One part in one color, placed in a model.

Instance

One placed copy of a component.

files

Reading and writing model files and their source maps.

A model file (name.bricky.json) holds the model alone. Its source map (name.bricky.map.json) records the build-script lines that placed each element and the SHA-256 of the model file it describes, so bricky detects a stale map and ignores it.

load_model

load_model(path: Path) -> Model

Read a model file.

Raises:

Type Description
BrickyError

If the file is missing, cannot be read or is not a valid model.

save_model

save_model(model: Model, path: Path) -> None

Write a model file as indented JSON.

Raises:

Type Description
BrickyError

If the file cannot be written.

load_source_map

load_source_map(model_path: Path) -> Provenance | None

Return a model file's provenance, or None if it has no current source map.

The function ignores a map that is missing, unreadable or written for other model bytes, since provenance only adds detail to a report.

save_source_map

save_source_map(
    provenance: Provenance,
    *,
    script: Path,
    model_path: Path,
) -> Path

Write the source map of a saved model file and return its path.

Raises:

Type Description
BrickyError

If the model file cannot be read or the map cannot be written.

fill

Fill voxels with bricks and plates, the way a builder lays courses.

Many models are easiest to describe as voxels: which color fills each stud square, one plate tall. :func:plan_fill converts such a plan into parts a script places: bricks where the three layers of a course share a color, plates everywhere else, each part as large as fits.

The courses bond. Each layer lays its parts along x or along y, alternating from layer to layer, so joints rarely line up above each other. Every part rests on at least one voxel of the layer below, so an overhang takes a part that reaches back over what holds it. Overhangs, and the squares beside them, take plates, so each layer of an overhang reaches back on its own. A voxel that no part can hold still gets a part; the checks then report it as floating.

BRICKS module-attribute

BRICKS: Final[dict[tuple[int, int], str]] = {
    (1, 1): "3005",
    (1, 2): "3004",
    (1, 3): "3622",
    (1, 4): "3010",
    (1, 6): "3009",
    (1, 8): "3008",
    (2, 2): "3003",
    (2, 3): "3002",
    (2, 4): "3001",
    (2, 6): "2456",
    (2, 8): "3007",
    (2, 10): "3006",
}

Bricks a fill uses, by footprint: the shorter side first, along x when unrotated.

PLATES module-attribute

PLATES: Final[dict[tuple[int, int], str]] = {
    (1, 1): "3024",
    (1, 2): "3023",
    (1, 3): "3623",
    (1, 4): "3710",
    (1, 6): "3666",
    (1, 8): "3460",
    (2, 2): "3022",
    (2, 3): "3021",
    (2, 4): "3020",
    (2, 6): "3795",
    (2, 8): "3034",
    (2, 10): "3832",
    (4, 4): "3031",
    (4, 6): "3032",
    (4, 8): "3035",
    (6, 6): "3958",
    (6, 8): "3036",
}

Plates a fill uses, by footprint: the shorter side first, along x when unrotated.

Placement dataclass

Placement(
    part: str,
    at: tuple[int, int, int],
    rotation: str,
    color: int | str,
)

One part a fill places: what, where, how rotated, and in which color.

at instance-attribute

at: tuple[int, int, int]

Minimum corner, in studs, studs and plates.

rotation instance-attribute

rotation: str

R0 keeps the shorter side along x; R90 lays the longer side along x.

plan_fill

plan_fill(
    voxels: Mapping[tuple[int, int, int], int | str],
    *,
    made_in: MadeIn | None = None,
) -> list[Placement]

Return the bricks and plates that fill exactly the voxels, bottom layer first.

Parameters:

Name Type Description Default
voxels Mapping[tuple[int, int, int], int | str]

The color of every voxel to fill, keyed (x, y, z) in studs, studs and plates.

required
made_in MadeIn | None

Whether a part comes in a color; the fill then uses only parts that do, and a 1 x 1 part where none does. Every part is allowed when omitted.

None

Examples:

>>> wall = {(x, 0, z): "Red" for x in range(4) for z in range(3)}
>>> [(p.part, p.at, p.rotation) for p in plan_fill(wall)]
[('3010', (0, 0, 0), 'R90')]

The package exports model_voxels as bricky.voxels, the name the guide uses.

voxels

The model as voxels: which element fills each voxel.

A voxel is one stud wide, one stud deep and one plate tall: the grid that parts are placed on. An element fills a voxel when the voxel's center lies inside its body. Every voxel of a model free of overlaps therefore belongs to at most one element, and a comparison with a target made of voxels is exact. Studs belong to no body and fill no voxel.

VoxelKey

VoxelKey = tuple[int, int, int]

A voxel by its minimum corner: x and y in studs, z in plates.

Voxel dataclass

Voxel(uid: str, color: int)

A filled voxel: the element whose body fills it, and that element's color.

uid instance-attribute

uid: str

The element's uid, such as "bay2/e3".

color instance-attribute

color: int

The element's color id.

model_voxels

model_voxels(
    model: Model, catalog: Catalog | None = None
) -> dict[VoxelKey, Voxel]

Return each voxel a model fills, keyed by its minimum corner in studs and plates.

Examples:

>>> from bricky import ModelBuilder, voxels
>>> builder = ModelBuilder("wall")
>>> builder.place("3004", at=(0, 0, 0), color="Red")  # Brick 1 x 2
'e1'
>>> sorted(voxels(builder.build()))
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (0, 1, 1), (0, 1, 2)]

world_voxels

world_voxels(world: World) -> dict[VoxelKey, Voxel]

Return every filled voxel of a placed model, keyed by its minimum corner.

A voxel that overlapping elements both fill belongs to the one placed first.