Skip to content

geomotif.io.dxf

Write a design as DXF R12, in pure standard library.

DXF is what CAD, CAM and most laser and CNC toolchains read, and R12 is the version of it worth hand-writing: small, frozen since 1992, exhaustively documented, and accepted by everything that reads DXF at all. That keeps the core dependency-free all the way out to the file.

The format is a flat stream of group codes: an integer on one line saying what the next line means, then the value. A polyline is not one entity but several -- a POLYLINE header, a VERTEX per point, and a SEQEND to close the run. R12 has no LWPOLYLINE; that arrived with R14, and using it would give up the compatibility R12 was chosen for.

Unlike SVG, DXF is y-up, the same convention the motifs are written in, so nothing is mirrored on the way out and a design keeps its own measurements. If you want it scaled, scale the design -- :meth:Design.fit -- and the file will say so in its own units.

Layers are the one part of :mod:geomotif.core.style that DXF models natively: a styled design writes each of its layers into the file's layer table and puts every entity on its own. color is the part DXF barely models at all -- R12 knows 255 indexed colors and no arbitrary ones -- so the seven it can name are written and anything else is left to the layer.

Functions:

Name Description
to_dxf

Render a design as a DXF R12 document.

save_dxf

Write a design to a DXF file and return the path written.

to_dxf

to_dxf(design: Design, *, layer: str = '0', precision: int = 4) -> str

Render a design as a DXF R12 document.

Parameters:

Name Type Description Default
design Design

What to write. Strokes become POLYLINE entities -- closed ones carry the closed flag rather than a repeated final vertex -- and loose points become POINT entities.

required
layer str

Layer for geometry that does not name one of its own. "0" is the layer every DXF file already has; any other name is declared in the file's layer table, so the result is valid on its own rather than relying on the reader to invent the layer.

'0'
precision int

Decimal places for coordinates.

4

Returns:

Type Description
str

A complete DXF R12 document.

Raises:

Type Description
ValueError

If the design is empty, the precision is negative, or a layer name -- the argument's or a style's -- is not one R12 permits.

Source code in src/geomotif/io/dxf.py
def to_dxf(design: Design, *, layer: str = "0", precision: int = 4) -> str:
    """Render a design as a DXF R12 document.

    Parameters
    ----------
    design : Design
        What to write. Strokes become ``POLYLINE`` entities -- closed ones
        carry the closed flag rather than a repeated final vertex -- and loose
        points become ``POINT`` entities.
    layer : str, optional
        Layer for geometry that does not name one of its own. ``"0"`` is the
        layer every DXF file already has; any other name is declared in the
        file's layer table, so the result is valid on its own rather than
        relying on the reader to invent the layer.
    precision : int, optional
        Decimal places for coordinates.

    Returns
    -------
    str
        A complete DXF R12 document.

    Raises
    ------
    ValueError
        If the design is empty, the precision is negative, or a layer name --
        the argument's or a style's -- is not one R12 permits.
    """
    if not len(design):
        raise ValueError("cannot write an empty design to DXF: there is nothing to draw")
    if precision < 0:
        raise ValueError(f"precision must be >= 0, got {precision}")

    used = _layers_used(design, layer)
    bounds = design.bounds

    def num(value: float) -> str:
        return f"{value:.{precision}f}"

    parts = [
        *_section(
            "HEADER",
            _tag(9, "$ACADVER"),
            _tag(1, "AC1009"),
            # The drawing extents: what "zoom to fit" uses when the file opens.
            _tag(9, "$EXTMIN"),
            _point(bounds.min_x, bounds.min_y, num),
            _tag(9, "$EXTMAX"),
            _point(bounds.max_x, bounds.max_y, num),
        ),
        *_section("TABLES", *_layer_table(used)),
        *_section("ENTITIES", *_entities(design, layer, num)),
        _tag(0, "EOF"),
    ]
    return "".join(parts)

save_dxf

save_dxf(design: Design, path: str | PathLike[str], *, layer: str = '0', precision: int = 4) -> Path

Write a design to a DXF file and return the path written.

See :func:to_dxf for what the options mean.

Source code in src/geomotif/io/dxf.py
def save_dxf(
    design: Design,
    path: str | PathLike[str],
    *,
    layer: str = "0",
    precision: int = 4,
) -> pathlib.Path:
    """Write a design to a DXF file and return the path written.

    See :func:`to_dxf` for what the options mean.
    """
    target = pathlib.Path(path)
    target.write_text(to_dxf(design, layer=layer, precision=precision))
    return target