cadboss.dev

Python

cadboss in Python

pip install cadboss (or uv add cadboss) installs prebuilt abi3 wheels for CPython 3.12 and later, no toolchain required. The package is the same Rust reader behind one Document class. Every script on this page ran on plant.dxf, a copy of the demo drawing in the repository; the output is printed as it came.

Document.open, Document.load

Open a path or load bytes.

Document.open reads the file into one buffer after checking its size against max_bytes; Document.load copies the bytes it is given. Both return a Document with the metadata as properties.

metadata.py
from cadboss import Document

doc = Document.open("plant.dxf")
print(doc.format, doc.version, doc.units)
print(doc.extents)
print(doc.blocks)
print(doc.entity_counts[:3])

with open("plant.dxf", "rb") as f:
    same = Document.load(f.read())
print(same.version, len(same.layouts))
output
$ python3 metadata.py
DXF AC1032 Millimeters
((0.0, 0.0, 0.0), (12500.0, 8000.0, 0.0))
['PUMP', 'TITLEBLOCK', 'SITE-PLAN']
[('INSERT', 4), ('DIMENSION', 3), ('LINE', 3)]
AC1032 3

layouts, layers

Layouts and layers as objects.

A Layout has a name, a kind (Model or Paper) and an entity count. A Layer has a name, a color and the frozen, off and locked flags.

structure.py
from cadboss import Document

doc = Document.open("plant.dxf")
for layout in doc.layouts:
    print(layout.name, layout.kind, layout.entity_count)
for layer in doc.layers[:4]:
    print(layer.name, layer.color, layer.frozen, layer.off, layer.locked)
output
$ python3 structure.py
Model Model 14
Layout1 Paper 0
Sheet 1 Paper 1
0 7 False False False
WALLS 7 False False False
EQUIPMENT 3 False False False
PIPING 5 False False False

text(group, layout)

Text groups, item by item.

doc.text(group, layout=None) returns TextItem objects. The groups are title-block, annotations, names, attributes, references, properties and all. Each item carries its text, its source, its layer and layout, and its position and height where the drawing has them.

groups.py
from cadboss import Document

doc = Document.open("plant.dxf")
for item in doc.text("title-block"):
    print(item.text, item.source, item.layout, item.position, item.height)
for item in doc.text("references"):
    print(item.source, item.text)
for item in doc.text("attributes", layout="Model"):
    print(item.layer, item.text)
output
$ python3 groups.py
DRAWN annotation Sheet 1 (234.0, 38.0, 0.0) 3.0
CHECKED annotation Sheet 1 (324.0, 38.0, 0.0) 3.0
SCALE annotation Sheet 1 (234.0, 18.0, 0.0) 3.0
REV annotation Sheet 1 (324.0, 18.0, 0.0) 3.0
image-path photos/pump-room.png
xref ../xrefs/site-plan.dxf
hyperlink https://example.com/specs/pipe-class-1A.pdf
EQUIPMENT TAG=P-101
EQUIPMENT DUTY=DUTY
EQUIPMENT TAG=P-102
EQUIPMENT DUTY=STANDBY

report()

What the reader could not decode.

truncated.dxf is plant.dxf cut at byte 6000. Opening it does not raise: the document holds what was read, and report() lists the rest as Skipped entries with a kind, a reason and a count.

report.py
from cadboss import Document

doc = Document.open("truncated.dxf")
for skipped in doc.report():
    print(skipped.kind, skipped.reason, skipped.count)
print(len(doc.layouts), len(doc.text("all")))
output
$ python3 report.py
section truncated 1
entity unknown 1
2 12

str | bytes

Undecodable text arrives as bytes.

legacy.dxf is plant.dxf with the byte 0x81, which Windows-1252 leaves undefined, in place of the space in PUMP ROOM. The item comes back as bytes instead of a placeholder, which is why every text and name in the API is typed str | bytes.

undecodable.py
from cadboss import Document

doc = Document.open("legacy.dxf")
item = doc.text("annotations")[-1]
print(item.layer, item.text)
output
$ python3 undecodable.py
NOTES b'PUMP\x81ROOM'

budget

Budgets and errors.

Pass budget as a dict to Document.open or Document.load. The keys are max_bytes, max_entities, max_layouts, max_nesting, max_string_bytes and max_tokens; the CLI page lists what each one caps and its default. A cap that is reached shows up in the report; a file over max_bytes or one that is not a DXF raises CadError.

budget.py
from cadboss import CadError, Document

doc = Document.open("plant.dxf", budget={"max_entities": 5})
for skipped in doc.report():
    print(skipped.kind, skipped.reason, skipped.count)

for path, budget in [("plant.dxf", {"max_bytes": 4096}), ("notes.txt", None)]:
    try:
        Document.open(path, budget=budget)
    except CadError as error:
        print("CadError:", error)

try:
    Document.open("plant.dxf", budget={"max_foo": 1})
except ValueError as error:
    print("ValueError:", error)
output
$ python3 budget.py
entity entity_limit 4
CadError: max_bytes budget exceeded (limit 4096)
CadError: not a dxf file
ValueError: unknown budget key: max_foo

markdown(), json()

Markdown and JSON as strings.

doc.markdown(title=None) and doc.json(compact=False) return the same exports as cadboss md and cadboss json.

output.py
from cadboss import Document

doc = Document.open("plant.dxf")
markdown = doc.markdown(title="Pump station P-101")
print("\n".join(markdown.splitlines()[:5]))
print(doc.json(compact=True)[:72])
output
$ python3 output.py
# Pump station P-101

## Metadata

- Format: DXF AC1032
{"metadata":{"format":"Dxf","version":"AC1032","codepage":"ANSI_1252","u

Reference

The whole API, from the type stub.

The package exports Document, Layer, Layout, TextItem, Skipped, CadError and __version__. All properties are read-only.

Document

MemberTypeNotes
Document.open(path, *, budget=None)DocumentReads the file into one buffer after checking its size against max_bytes.
Document.load(data, *, budget=None)DocumentCopies the bytes, bytearray or memoryview it is given.
formatstrDXF
versionstr | bytesSuch as AC1032.
unitsstrSuch as Millimeters.
layerslist[Layer]The layer table.
layoutslist[Layout]The model layout and the paper layouts.
blockslist[str | bytes]Block names.
entity_countslist[tuple[str, int]]Entities per type, most frequent first.
extentstuple[tuple[float, float, float], tuple[float, float, float]] | NoneMinimum and maximum corner.
text(group="all", layout=None)list[TextItem]One group, optionally one layout.
markdown(title=None)strThe Markdown export.
json(compact=False)strThe JSON export.
report()list[Skipped]What the reader could not decode, counted.

Layer

MemberTypeNotes
namestr | bytes
colorintThe color index.
frozen, off, lockedbool

Layout

MemberTypeNotes
namestr | bytes
kindstrModel or Paper.
entity_countint

TextItem

MemberTypeNotes
textstr | bytesbytes when it does not decode.
sourcestrSuch as annotation, xref, image-path or hyperlink.
layerstr | bytes
layoutstr | bytes
positiontuple[float, float, float] | NoneAfter the insert transform.
heightfloat | None

Skipped

MemberTypeNotes
kindstrSuch as entity or section.
reasonstrSuch as truncated, unknown or entity_limit.
countint

FAQ

Questions developers ask.

Which Python versions does the wheel support?
CPython 3.12 and later. The wheels are built against the stable ABI (abi3), so no toolchain is needed to install them.
Does Document.open memory-map the file?
No. Document.open reads the file into one buffer after checking its size against max_bytes, and Document.load copies the bytes it is given. The CLI is the path that memory-maps the input.
What does it raise?
CadError for a file that cannot be read, is not a DXF or is larger than max_bytes, and ValueError for an unknown budget key or text group. Skipped content does not raise; it is listed by report().
Why is a name or a text sometimes bytes?
A string that does not decode under the drawing's codepage is kept as bytes instead of a placeholder, so nothing is lost. Decode it yourself when you know the encoding.