Gridquill
Build editable diagrams in Python using reactive coordinates, nested canvases,
elements, signals, captions and connectors. Export to .excalidraw for Excalidraw
or .excalidraw.md for Obsidian. Python 3.10+; no runtime dependencies.
Install from this checkout
python -m pip install -e .
The package is prepared for PyPI but has not been published.
Draw a diagram
import gridquill as gq
gq.set_color_scheme(
element_color=0x1971C2, element_fill=0xA5D8FF,
signal_color=0xF08C00, signal_fill=0xFFEC99,
)
with gq.canvas("Example") as diagram:
with gq.canvas("Acquisition", column=2, row=1,
background=0xEDF2FF, margin_left=20, margin_right=20,
margin_top=20, margin_bottom=20):
adc = gq.element("ADC", column=0, top=0, length=4)
fpga = gq.element("FPGA", column=4, top=0, length=4)
samples = gq.signal("Samples", row=adc.port(2), components=[adc])
samples.components.append(fpga)
gq.connector(fpga, samples, inout="in")
gq.caption("Acquisition", row=-1, column=0)
diagram.save("example.excalidraw.md")
Coordinates and ownership
Literal coordinates are local to their canvas. On save, coordinate references
resolve to positions in the drawing, including all parent offsets. A captured
reference such as fpga.column follows later moves and assignments. Sizes and
lengths are distances; coordinate formulas return drawing coordinates.
fpga.column = lambda: adc.column + 5
diagram.resolve() # resolve without writing a file
Geometry fields and span entries accept numbers, reactive arithmetic or
zero-argument functions. Each save evaluates them in passes. Pending dependencies
read as NaN; stalled resolution raises ReactiveResolutionError naming the
unresolved fields. Functions must be pure. float(value) reads a snapshot of the
last resolved value. Failed resolution leaves an existing output file untouched.
Creating a canvas opens it. Subsequent objects attach automatically; pop()
returns to the previous canvas. with canvas(...) restores the previous scope
on exit. Use canvas(activate=False) for explicit ownership and append().
Objects
element(label, column=..., top=..., bottom=... / length=...): vertical box. Alternatively usespan=[top, bottom].element.port(index)returns a reactive row, with 0.5 grid-unit spacing; negative indices count from the bottom.signal(label, row=..., left=..., right=... / length=...): horizontal box. Alternatively usespan=[left, right]or a mutablecomponentslist whose outermost columns are connected. Endpoint formulas are prepared at save time.caption(label, row=..., column=..., fill=..., length=...): automatically sized label box; omitlengthto fit the text on each save.connector(element_or_column, signal_or_row, position="auto", inout="out"): chevron marker. With a signal, the default choosesleftorrightwhen its span lies entirely on that side of the element's column, including a signal ending at the element. The side and arrow direction follow layout changes on every save. A signal crossing both sides, or a numeric row, defaults toleft. Setposition="left"or"right"to override. Flow isin,outorinout.chevron(...): individual direction stencil.canvas(...): group with an optional background, border style and per-side margins in pixels. Bounds follow its members automatically.
Default grid size is 80 pixels. set_color_scheme() changes global defaults;
explicit object colors take precedence. Assign None to inherit again.
get_color_scheme() returns a copy; reset_color_scheme() restores defaults.
draw_diagram() provides the lower-level renderer. To normalize existing
drawings, run gridquill-normalize drawing.excalidraw.md (edits the input file).
Drawing order
Set z_index to control overlaps. Higher values appear in front, and each box
and its label stay together:
with gq.canvas() as diagram:
block = gq.element("FPGA", column=2, span=[0, 4])
bus = gq.signal("Bus", row=2, span=[0, 4], z_index=1)
bus.z_index = 2 # editable after creation; applied on the next save
diagram.save("layers.excalidraw")
Signals, elements, chevrons, captions, connectors and canvases accept z_index
(default 0). Negative and fractional values are allowed, as are reactive
formulas. A canvas adds its z_index to its descendants' values, so a connector
or a nested canvas can shift all its members together. Layers compare across
canvas boundaries. Canvas backgrounds remain behind all drawing objects.
Equal values preserve the existing order: signals (including captions), then
elements, then chevrons, in traversal order within each kind. Existing diagrams
therefore keep their drawing order. For draw_diagram(), set z_index in each
horizontal or vertical item dictionary; connection arrows remain behind boxes.
Examples and development
python -m pip install -e ".[dev]"
python -m unittest discover -s tests
python examples/canvas_scope_example.py
python examples/recreate_udt_overview.py --output overview.excalidraw.md
python -m build
python -m twine check dist/*
Source lives in src/gridquill; tests and runnable examples live in tests and
examples. The firmware-specific CSV layout exporter remains in the firmware
repository because it depends on the separate fmake toolchain.
Publishing
Add author details and actual project URLs to pyproject.toml.
Set __version__ in src/gridquill/__init__.py for a release.
Build and validate the distributions using the commands above. When ready,
upload explicitly with python -m twine upload dist/* using your PyPI account.
No publishing credentials or upload automation are included.
License
MIT. See LICENSE.
Metadata
Release files for gridquill 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| gridquill-0.1.1.tar.gz | 43.1 kB | Without provenance | Details for gridquill-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| gridquill-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for gridquill-0.1.1-py3-none-any.whl |
Total release size: 67.9 kB
Release files / gridquill-0.1.1.tar.gz
| Download URL | gridquill-0.1.1.tar.gz |
|---|---|
| Size | 43.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
715ed0599f856432d580d572953e74df67192f8d270bc01ddd244887f3e93082
|
|
BLAKE2b-256 checksum How to use checksums |
707bed030630bd7327f6ab20617878392a4ed10682744a2a037e83e4cd0acd52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / gridquill-0.1.1-py3-none-any.whl
| Download URL | gridquill-0.1.1-py3-none-any.whl |
|---|---|
| Size | 24.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4524307f7ebb88896584a8dc558c5f2ec49da03e1cf0082db0c94343d380d3e
|
|
BLAKE2b-256 checksum How to use checksums |
7a3b38ffa2de2e209fc6015f5229ad8947afd34d374ba4014b43db9ae7943473
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for gridquill 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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.
To learn more about protecting yourself from malicious actors, visit our package security documentation.