Skip to main content

zumen for Python

Read 2D drawings of Japanese construction and render them, with the Rust implementation of zumen. The result is the same Scene model as the @zumenjs/core viewer in the browser.

Status: early release (0.x), for Python 3.12 or newer. SXF (SFC and P21, also compressed as P2Z and SFZ), Jw_cad JWW, DXF and DWG (AutoCAD 1.0 to 2018) are supported.

Download a wheel for your platform from the GitHub release and install it with pip install path/to/zumen-0.1.1-….whl. The release includes wheels for Linux x86_64 and aarch64 (glibc 2.28 or newer), macOS arm64, and a source distribution for other platforms.

import zumen

scene = zumen.read("plan.jww")  # the format is detected from the contents
print(scene.version, len(scene), scene.sheet)
png = scene.render_png(dpi=150)  # for thumbnails or as an image for vision models
pdf = scene.render_pdf()  # one page, the sheet at its real size, with searchable text
svg = scene.render_svg()  # millimeters, with text as <text>
part = scene.render_png(dpi=300, region=(100, 50, 200, 120))  # paper millimeters
json_text = scene.to_json()  # pass to sceneFromJson in @zumenjs/core

Texts and entities, in paper millimeters with the Y axis up:

for text in scene.texts(region=(100, 50, 200, 120)):  # omit region for the whole sheet
    print(text["id"], text["text"], text["x"], text["y"], text["height"], text["angle"])
entity = scene.entity("#120")  # id, kind, type, source, layers, bbox, text, properties, values, placement
entity["kind"], entity["values"]["radius"]  # "circle", 25.0: the same keys in every format
rows = scene.entities(hidden_layers=[])  # [] includes the layers that the drawing hides
table = scene.to_arrow()  # a pyarrow.Table, for pandas, Polars or DuckDB (pip install "zumen[arrow]")

scene.unit is the length unit of the drawing's own coordinates, those of values ("millimeter" in SXF and JWW, the unit of $INSUNITS in DXF and DWG, such as "inch" or "meter"). Entity ids come from the file (an SXF instance number, a JWW record, a DXF or DWG handle, with the placements or block references the entity sits in) and stay the same for the same bytes in every version, so they can be stored and looked up again with scene.entity(id) (Entity ids).

texts joins the parts of a text that share a line and puts a line break between lines. properties are the label and value pairs that the format gives, as in the viewer's property panel, and values maps their stable keys (layer, start, radius, text …) to the unformatted values, such as numbers and (x, y). kind (line, text, dimension …) and the keys are the same in every format, while type and the labels are Japanese (the list). scene.metadata_values does the same for the metadata.

geometry=True adds the shapes drawn, in drawing order, so that an application or an AI model can compute with the coordinates of lines, outlines and hatched areas:

entity = scene.entity("#120", geometry=True)  # also scene.entities(region=..., geometry=True)
for shape in entity["geometry"]:
    shape["shape"]  # "polyline", "segments", "fill", "hatch", "point" or "text"
    shape["points"]  # polyline: [(x, y), ...] in paper millimeters, with shape["closed"]
    shape["real"]  # the same coordinates in real-world millimeters, or None outside a placement

Segments have segments (pairs of points), fills and hatches rings (the outline and its holes; hatch lines are left out), and points and text parts one point (for text, the lower left, with text, height and angle). Arcs, circles, ellipses and splines come as the polyline drawn (arcs, circles and ellipses within 0.02 mm of the curve on the sheet); their center, radius and angles are in values. real goes through the placement of the entity (an SXF sub-figure or part, a JWW layer group, a DXF viewport or model space), so it is in the coordinates of the drawing itself: the real-world coordinates of JWW, those of the SXF sub-figure or part, and DXF model space in millimeters, also for drawings in inches.

render returns the image with the conversion between pixels and paper millimeters and the box of every entity drawn, so that an application can tell which entity a vision model refers to:

image = scene.render(size=(2000, 2000))  # or dpi=..., region=... as in render_png
image.png, image.width, image.height, image.scale  # scale: pixels per paper millimeter
x, y = image.to_paper(640, 480)  # and image.to_pixel(x, y)
for entity in image.entities_at(640, 480):  # boxes that contain the pixel, smallest first
    print(entity["id"], entity["type"], entity["box"], entity["text"])

A Rendering shows itself as an image in Jupyter.

Drawings of every format can be written as DXF (AutoCAD 2013, UTF-8). DXF and DWG keep their handles, layers, styles, blocks and layouts, and read again, the DXF draws the same scene. SXF and JWW are mapped into the AutoCAD drawing model: model space at real size, and the sheet as a layout whose viewports show each SXF sub-figure and each JWW layer group scale where the drawing had it, with frames and title blocks in paper space. The layout draws the same as the original:

converted = zumen.convert("plan.jww", "dxf")  # or bytes; converted.data holds the DXF
converted.save("plan.dxf")
for notice in converted.notices:  # what could not be written as it was, such as images, by kind and count
    print(notice["code"], notice["message"], notice["count"])

They can also be written as SXF (SFC of SXF Ver.3.1). The sheet of the layout that the drawing opens in becomes the SXF sheet, each viewport a sub-figure at real size placed at its scale, and each block a drawing part. What SXF has no feature for, such as multiline texts, leaders and dimensions, is written as the lines, fills and texts that zumen draws for it, and read again, the SFC draws the same:

zumen.convert("plan.dwg", "sfc").save("plan.sfc")
zumen.convert("plan.dxf", "p21", version="2.0").save("plan.p21")  # SXF Ver.2.0 as P21 (also "sfz" and "p2z")

DWG (AutoCAD 2013) and JWW (Jw_cad 8) are written from the same model: zumen.convert("plan.sfc", "dwg") and zumen.convert("plan.dxf", "jww"). In JWW, the layout is the sheet and the model space in each viewport is a layer group at the viewport's scale.

Editing drawings

zumen.edit changes a drawing in place: it applies a list of operations and keeps everything else as it was, in the same format, version and text encoding, including what zumen cannot read (extended data, objects, unread entities). DXF (R12 to 2018, ASCII and binary), DWG (R13 to 2018), JWW and SXF (SFC, P21, SFZ and P2Z) can be edited.

operations = [  # the operations may also be a JSON string
    {"op": "add-layer", "name": "CHECK", "color": "#ff0000"},
    {"op": "add", "kind": "line", "layer": "CHECK", "start": [0, 0], "end": [5000, 0]},
    {"op": "add", "kind": "text", "layer": "CHECK", "position": [100, 200], "text": "確認済み", "height": 350},
    {"op": "update", "id": "3F2", "set": {"end": [5200, 0], "lineType": "DASHED"}},
    {"op": "delete", "id": "3F5"},
]
edited = zumen.edit("plan.dxf", operations)  # or bytes
edited.created  # ["CHECK", "4A1", "4A2"]: what add-layer and add created, in order
edited.save("plan.dxf")
  • Entities are chosen by the id of the scene and described by the kind and the property keys that Scene reports. Coordinates are the drawing's own, the raw of properties (Scene.entity(id)["values"]), not paper millimeters. Every id in one edit names an entity of the drawing as it was read. Ids that are places in the file (#12, in DXF without handles) change when an edit adds or removes records before them, so read the edited drawing again.
  • add takes container: "model" (the default), "layout:<name>" or "block:<name>" in DXF and DWG, "group:<0-F or name>" (a layer group) in JWW, and "sheet" (the default) or "figure:<name>" in SXF.
  • Kinds that can be added: line, polyline (vertices, bulges, closed), arc, circle, ellipse, ellipse-arc, point, text (MTEXT when it has line breaks) and block-reference (block, position, scale, rotation). Every kind takes layer, color (a color number, "#rrggbb", "bylayer" or "byblock"), lineType and lineWidth (millimeters).
  • If any operation cannot be applied, nothing is written and zumen.EditError is raised with code (such as "not-found", "unknown-key" or "inside-block") and operation, the number of the operation from 0.
  • Values that the format cannot hold exactly, such as a true color in an old DXF, are written as the nearest one and listed in edited.notices.
  • Checking that the file is the version that was read (a checksum) is left to the application.

zumen.create("dxf", operations, units="mm") starts a new drawing (AutoCAD 2013 DXF) and applies the operations. "dwg" starts an AutoCAD 2013 DWG, "jww" an A3 JWW, and "sfc", "p21", "sfz" or "p2z" an A3 SXF sheet; JWW and SXF lengths are in millimeters, so they take only units="mm".

Comparing revisions

zumen.compare matches entities by type, shape and text, not by id, so revisions that were saved again, or saved in another format, compare too:

changes = zumen.compare(zumen.read("plan-rev1.jww"), zumen.read("plan-rev2.jww"))
changes.removed, changes.added  # entities, as Scene.entities returns them
for pair in changes.changed:  # the same entity changed, such as a dimension value
    print(pair["before"]["text"], "->", pair["after"]["text"])
changes.regions  # boxes around nearby changes, in the earlier revision's paper millimeters
changes.scene.render_png(dpi=150)  # red before, blue after, gray unchanged

tolerance (0.01 paper millimeters by default) sets how close coordinates count as the same, style=True also counts changes of color, line weight, line type and layer, and a revision that moved as a whole is moved back by the texts both revisions share (changes.offset; align=False turns this off).

Title block

title_block guesses the drawing number, revision, title, project, scale and date, so that drawings can be filed by number and revision:

block = scene.title_block()
block["number"], block["revision"], block["title"]  # None when not found
for field in block["fields"]:  # every candidate, the most likely first for each key
    print(field["key"], field["value"], field["source"], field["label"], field["entity"])

It reads attribute tags (DWGNO, REV, TITLE), labels such as 図面番号 or DWG. NO. with the text to their right or below, labels with their value in the same text (縮尺 1/100), the sheet name and the metadata (the title block attributes of SXF). The values are guesses from the drawing's texts; check them before relying on them.

Rooms and areas

regions finds the regions that drawn lines close, such as rooms, with their areas:

for room in scene.regions(layers=[layer["id"] for layer in scene.layers if "壁" in layer["name"]]):
    real = room["real"]  # None outside a placement drawn at a scale
    print([text["text"] for text in room["texts"]], real and real["area"] / 1e6, "m²")

Lines, polylines, arcs, circles, splines and solids are split where they cross or meet, ends closer than tolerance (0.01 paper millimeters) are joined, and every face they close is a region: its outline (counterclockwise) and holes, area (without the holes), outline_area and perimeter on the sheet, the same in real-world millimeters in real, the boundary entities and the texts inside. Lines that end nowhere close nothing, and closed shapes that touch no other line (columns, furniture) are holes. layers keeps the lines on some layers (walls), kinds changes the kinds used, and min_area leaves out smaller regions. A room whose walls have an opening drawn as a gap joins the next one (decision 0015).

MCP server

zumen-mcp lets AI applications that speak the Model Context Protocol open, render, search, measure and compare drawings on this computer. It serves over stdio and only reads. Regions are given in paper millimeters (region) or as fractions of the frame (frame_region), as in zumen.tools.

pip install "zumen[mcp]"
claude mcp add zumen -- zumen-mcp --root ~/drawings  # Claude Code; other clients take the same command
Tool What it returns
open_drawing Format, sheet, views, layers with entity counts, entity types, the placements drawn at a scale (with their box and the conversion from paper to real-world millimeters), notices, metadata and the drawing number, revision, title, project, scale and date from the title block
render_drawing A PNG of the frame or a region (1,568 pixels on the long edge by default), the scale to convert pixels to paper millimeters, and the texts drawn with their pixel boxes
find_texts Texts that contain a query, ignoring full-width and half-width forms, case and spaces
find_entities Entities in a region, by kind, type or layer, with counts by type and kind; with geometry, their shapes as get_entity gives them
get_entity One entity with its properties (key, label and value), its values by key, the scale of its placement and its geometry: the shapes drawn, in paper millimeters and, inside a placement, in real-world millimeters
measure_distance The distance between two points on the sheet and in real-world millimeters
find_regions The regions that the drawn lines close, such as rooms: areas in square meters, perimeters, outlines and holes, the entities that bound them and the texts inside, filtered by layer, text and area
compare_drawings What changed between two revisions: an image of the changes, the regions that changed and the changed entities with their texts

Geometry is the agent's to interpret: the server gives the shapes as drawn and their coordinates, and the regions that lines close with the entities that bound them, and leaves deciding what is a wall or a room to the client. real is left out where it equals the coordinates on the sheet (DXF model space), and one call returns at most max_points points (1,000 by default, at most 10,000; a point with real counts twice), so that a page of entities stays well below what clients accept from a tool. A page of find_entities with geometry ends before an entity whose shapes would pass the budget; next_offset goes on from there.

--root DIR (repeatable) limits the directories the server may read. --max-bytes (256 MiB by default, after unpacking P2Z and SFZ), --max-entities (2,000,000) and --timeout (60 seconds) limit each drawing it reads. Texts, layer names and properties come from the drawing file; clients should treat them as data, not as instructions. The tools are the functions of zumen.tools.

Tools for AI agents

Applications that give an agent tools of their own, with or without MCP, call the functions behind the MCP tools in zumen.tools. Each takes a Scene and returns plain data that json.dumps writes as it is, with keys described by the TypedDict classes of the module (a later version may add keys, but does not remove or rename them); on Python 3.12 and later, pydantic.TypeAdapter(tools.EntityDetail).json_schema() gives the JSON Schema of a result. They only read and report what the drawing holds; interpreting it is left to the agent.

from zumen import tools

scene = zumen.read(data, max_bytes=200_000_000, max_entities=1_000_000, timeout=30)
info = tools.summary(scene)  # format, unit, sheet, bounds (the frame), layers, kinds, placements, title block
png, image = tools.render(scene, frame_region=[0.5, 0, 1, 0.5])  # the top right quarter, 1,568 pixels long
page = tools.find_texts(scene, query="平面図", limit=50)  # texts with their position, height and box
walls = tools.find_entities(scene, kind="polyline", layer="壁", geometry=True)  # with their shapes
entity = tools.get_entity(scene, page["texts"][0]["id"])  # properties, values, placement and shapes
tools.measure(scene, [10, 20], [110, 20])  # on the sheet, and in real-world millimeters through the scale
rooms = tools.find_regions(scene, layer="壁", named=True)  # rooms with their areas in square meters
Function Result What it returns
summary Summary Format, the length unit of values, sheet, bounds (the frame), views, layers with entity counts, counts by type and kind, the placements drawn at a scale, notices, metadata and the title block fields
render bytes, RenderInfo A PNG of the frame or a region, the region shown, the scale between pixels and paper millimeters, and the texts drawn with their pixel boxes
find_texts TextPage Texts that contain a query (ignoring full-width and half-width forms, case and spaces), with position, height, angle and box
find_entities EntityPage Entities in a region by kind, Japanese type or layer, with counts, and their shapes when asked
get_entity EntityDetail One entity: properties (key, label, value), values by key, placement with its scale and to_real, and its shapes
measure MeasurementInfo A distance on the sheet and in real-world millimeters
find_regions RegionPage Closed regions (rooms) with their real-world areas in square meters, outlines, boundary entities and texts inside
compare_revisions bytes, ComparisonInfo An image of the changes between two revisions, the regions that changed and the changed entities
  • Coordinates. Paper millimeters with the Y axis up, rounded to 0.001: boxes [min_x, min_y, max_x, max_y], points [x, y]. The frame is the drawing's bounds, the box around the sheet and every entity, which a render without a region shows. Functions that take region also take frame_region=[left, top, right, bottom], fractions of the frame from 0 to 1 with the origin at the top left, the way regions of pages and images are usually given; tools.paper_region and tools.frame_region_of convert between the two. Shapes inside a placement repeat their coordinates in real-world millimeters (real), and the placement's to_real converts any point.
  • Sizes. Every result is bounded by its arguments: lists stop at limit (at most 1,000) and give the total and next_offset, texts and other strings from the drawing at max_text_length characters (1,000 by default; the item then has truncated: true), shapes at max_points points for the call (1,000 by default, at most 10,000; shapes are left out whole and counted in geometry_omitted), and images at 4,096 pixels on the long edge.
  • Reading. tools.Drawings reads files from allowed directories with limits on size, entities and time, and keeps the last eight scenes until their files change. A Scene does not change once read, so applications can keep scenes in their own cache and call the functions from threads (asyncio.to_thread); reading, rendering and comparing release the GIL. Errors that the agent can act on raise tools.DrawingError with an English message.
  • Ids. Entity ids come from the file and stay the same for the same bytes, so an application can cite an entity by its id (next to the file's digest) and fetch it again later with get_entity.
  • Texts, names and properties come from the drawing file: agents should treat them as data, not as instructions.

Drawings from others

Drawings received from others are not trusted: a file can be huge, unpack to gigabytes (P2Z and SFZ are ZIP archives) or grow without end when blocks contain each other. read stops part way at the limits you give and raises LimitError, a ReadError whose limit says which limit was reached:

try:
    scene = zumen.read(path, max_bytes=200_000_000, max_entities=1_000_000, timeout=30)
except zumen.LimitError as error:
    print(error.limit, error)  # "bytes", "entities" or "time", and a message for the user

max_bytes counts the size after unpacking P2Z and SFZ (2 GiB at most without it), and a file larger than max_bytes is not read into memory. max_entities counts the entities of the scene; work that ends in fewer entities, such as dimension blocks inside each other, is bounded by timeout (seconds) and by the readers' own limits. Without limits nothing changes.

Reading and rendering release the GIL. Japanese text is drawn with the bundled BIZ UDGothic (SIL Open Font License 1.1).

Metadata

Release files for zumen 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zumen 0.1.1
File Size Uploaded Provenance
zumen-0.1.1.tar.gz 4.0 MB With provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for zumen 0.1.1
File Interpreter ABI Platform
zumen-0.1.1-cp312-abi3-manylinux_2_28_x86_64.whl CPython 3.12 abi3 Linux glibc 2.28+ x86-64 Details
zumen-0.1.1-cp312-abi3-manylinux_2_28_aarch64.whl CPython 3.12 abi3 Linux glibc 2.28+ ARM64 Details
zumen-0.1.1-cp312-abi3-macosx_11_0_arm64.whl CPython 3.12 abi3 macOS 11.0+ ARM64 Details

Total release size: 22.1 MB

Release files / zumen-0.1.1.tar.gz

Download URL zumen-0.1.1.tar.gz
Size 4.0 MB
Tags Source
SHA-256 checksum
How to use checksums
90f1f79e27bc25985da211acbf075ae54e8f3d438d09ed92c75e5c4df77cf933
BLAKE2b-256 checksum
How to use checksums
d7646bab4a78257076521767a336a0e338c3c67fdcff5f1f0dd59b0320e37efb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / zumen-0.1.1-cp312-abi3-manylinux_2_28_x86_64.whl

Download URL zumen-0.1.1-cp312-abi3-manylinux_2_28_x86_64.whl
Size 6.2 MB
Tags CPython 3.12 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
e31d4f7b789af11929fca3cb5957c8256c58b3669779645b094517533afff17f
BLAKE2b-256 checksum
How to use checksums
7b124032f27d1be9aad43532a572c237e13477e8d0c9a881f9cc34e83d499387
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / zumen-0.1.1-cp312-abi3-manylinux_2_28_aarch64.whl

Download URL zumen-0.1.1-cp312-abi3-manylinux_2_28_aarch64.whl
Size 6.0 MB
Tags CPython 3.12 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
5da027a93e5c178401d38926c06ac739db8869b821b9cfc0ca473a9ef6dc5941
BLAKE2b-256 checksum
How to use checksums
4fa8b558adc27596769c7826a4aa00f0e19137cebbeb3a7496d0f2937ec87728
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / zumen-0.1.1-cp312-abi3-macosx_11_0_arm64.whl

Download URL zumen-0.1.1-cp312-abi3-macosx_11_0_arm64.whl
Size 5.9 MB
Tags CPython 3.12 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
5d21f453e1c53515318d3fe76aa07bbdbfc97bb96167628025520b434c998fb3
BLAKE2b-256 checksum
How to use checksums
d8a5661238a80aecae841348982acfa9f6fac4e94f1f42b06545369ac5b5775d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for zumen 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 4 files in this release have been uploaded with provenance metadata.

Select from 4 files

Attestation for zumen-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.1 This release

4 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page