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.
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))$ 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 3layouts, 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.
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)$ 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 Falsetext(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.
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)$ 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=STANDBYreport()
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.
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")))$ python3 report.py
section truncated 1
entity unknown 1
2 12str | 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.
from cadboss import Document
doc = Document.open("legacy.dxf")
item = doc.text("annotations")[-1]
print(item.layer, item.text)$ 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.
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)$ python3 budget.py
entity entity_limit 4
CadError: max_bytes budget exceeded (limit 4096)
CadError: not a dxf file
ValueError: unknown budget key: max_foomarkdown(), 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.
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])$ python3 output.py
# Pump station P-101
## Metadata
- Format: DXF AC1032
{"metadata":{"format":"Dxf","version":"AC1032","codepage":"ANSI_1252","uReference
The whole API, from the type stub.
The package exports Document, Layer, Layout, TextItem, Skipped, CadError and __version__. All properties are read-only.
Document
| Member | Type | Notes |
|---|---|---|
| Document.open(path, *, budget=None) | Document | Reads the file into one buffer after checking its size against max_bytes. |
| Document.load(data, *, budget=None) | Document | Copies the bytes, bytearray or memoryview it is given. |
| format | str | DXF |
| version | str | bytes | Such as AC1032. |
| units | str | Such as Millimeters. |
| layers | list[Layer] | The layer table. |
| layouts | list[Layout] | The model layout and the paper layouts. |
| blocks | list[str | bytes] | Block names. |
| entity_counts | list[tuple[str, int]] | Entities per type, most frequent first. |
| extents | tuple[tuple[float, float, float], tuple[float, float, float]] | None | Minimum and maximum corner. |
| text(group="all", layout=None) | list[TextItem] | One group, optionally one layout. |
| markdown(title=None) | str | The Markdown export. |
| json(compact=False) | str | The JSON export. |
| report() | list[Skipped] | What the reader could not decode, counted. |
Layer
| Member | Type | Notes |
|---|---|---|
| name | str | bytes | |
| color | int | The color index. |
| frozen, off, locked | bool |
Layout
| Member | Type | Notes |
|---|---|---|
| name | str | bytes | |
| kind | str | Model or Paper. |
| entity_count | int |
TextItem
| Member | Type | Notes |
|---|---|---|
| text | str | bytes | bytes when it does not decode. |
| source | str | Such as annotation, xref, image-path or hyperlink. |
| layer | str | bytes | |
| layout | str | bytes | |
| position | tuple[float, float, float] | None | After the insert transform. |
| height | float | None |
Skipped
| Member | Type | Notes |
|---|---|---|
| kind | str | Such as entity or section. |
| reason | str | Such as truncated, unknown or entity_limit. |
| count | int |
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.