gridquill 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gridquill-0.1.1/CHANGELOG.md +5 -0
- gridquill-0.1.1/LICENSE +21 -0
- gridquill-0.1.1/MANIFEST.in +3 -0
- gridquill-0.1.1/PKG-INFO +164 -0
- gridquill-0.1.1/README.md +138 -0
- gridquill-0.1.1/examples/__init__.py +1 -0
- gridquill-0.1.1/examples/canvas_background_example.py +32 -0
- gridquill-0.1.1/examples/canvas_scope_example.py +42 -0
- gridquill-0.1.1/examples/draw_chevrons_example.py +15 -0
- gridquill-0.1.1/examples/draw_connectors_example.py +17 -0
- gridquill-0.1.1/examples/draw_diagram_example.py +21 -0
- gridquill-0.1.1/examples/draw_objects_example.py +24 -0
- gridquill-0.1.1/examples/recreate_udt_overview.py +139 -0
- gridquill-0.1.1/examples/software_data_path.py +323 -0
- gridquill-0.1.1/pyproject.toml +40 -0
- gridquill-0.1.1/setup.cfg +4 -0
- gridquill-0.1.1/src/gridquill/__init__.py +18 -0
- gridquill-0.1.1/src/gridquill/model.py +846 -0
- gridquill-0.1.1/src/gridquill/normalize.py +145 -0
- gridquill-0.1.1/src/gridquill/reactive.py +254 -0
- gridquill-0.1.1/src/gridquill/render.py +128 -0
- gridquill-0.1.1/src/gridquill.egg-info/PKG-INFO +164 -0
- gridquill-0.1.1/src/gridquill.egg-info/SOURCES.txt +29 -0
- gridquill-0.1.1/src/gridquill.egg-info/dependency_links.txt +1 -0
- gridquill-0.1.1/src/gridquill.egg-info/entry_points.txt +2 -0
- gridquill-0.1.1/src/gridquill.egg-info/requires.txt +5 -0
- gridquill-0.1.1/src/gridquill.egg-info/top_level.txt +1 -0
- gridquill-0.1.1/tests/__init__.py +0 -0
- gridquill-0.1.1/tests/test_auto_connectors.py +100 -0
- gridquill-0.1.1/tests/test_gridquill.py +846 -0
- gridquill-0.1.1/tests/test_layers.py +109 -0
gridquill-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rpeschke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gridquill-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gridquill
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Reactive grid-based diagrams with nested canvases and editable Excalidraw output
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: diagrams,excalidraw,reactive,grid,visualization
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
23
|
+
Requires-Dist: setuptools>=77.0.3; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=6; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Gridquill
|
|
28
|
+
|
|
29
|
+
Build editable diagrams in Python using reactive coordinates, nested canvases,
|
|
30
|
+
elements, signals, captions and connectors. Export to `.excalidraw` for Excalidraw
|
|
31
|
+
or `.excalidraw.md` for Obsidian. Python 3.10+; no runtime dependencies.
|
|
32
|
+
|
|
33
|
+
## Install from this checkout
|
|
34
|
+
|
|
35
|
+
```console
|
|
36
|
+
python -m pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The package is prepared for PyPI but has not been published.
|
|
40
|
+
|
|
41
|
+
## Draw a diagram
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import gridquill as gq
|
|
45
|
+
|
|
46
|
+
gq.set_color_scheme(
|
|
47
|
+
element_color=0x1971C2, element_fill=0xA5D8FF,
|
|
48
|
+
signal_color=0xF08C00, signal_fill=0xFFEC99,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with gq.canvas("Example") as diagram:
|
|
52
|
+
with gq.canvas("Acquisition", column=2, row=1,
|
|
53
|
+
background=0xEDF2FF, margin_left=20, margin_right=20,
|
|
54
|
+
margin_top=20, margin_bottom=20):
|
|
55
|
+
adc = gq.element("ADC", column=0, top=0, length=4)
|
|
56
|
+
fpga = gq.element("FPGA", column=4, top=0, length=4)
|
|
57
|
+
samples = gq.signal("Samples", row=adc.port(2), components=[adc])
|
|
58
|
+
samples.components.append(fpga)
|
|
59
|
+
gq.connector(fpga, samples, inout="in")
|
|
60
|
+
gq.caption("Acquisition", row=-1, column=0)
|
|
61
|
+
|
|
62
|
+
diagram.save("example.excalidraw.md")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Coordinates and ownership
|
|
66
|
+
|
|
67
|
+
Literal coordinates are local to their canvas. On save, coordinate references
|
|
68
|
+
resolve to positions in the drawing, including all parent offsets. A captured
|
|
69
|
+
reference such as `fpga.column` follows later moves and assignments. Sizes and
|
|
70
|
+
lengths are distances; coordinate formulas return drawing coordinates.
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
fpga.column = lambda: adc.column + 5
|
|
74
|
+
diagram.resolve() # resolve without writing a file
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Geometry fields and span entries accept numbers, reactive arithmetic or
|
|
78
|
+
zero-argument functions. Each save evaluates them in passes. Pending dependencies
|
|
79
|
+
read as NaN; stalled resolution raises `ReactiveResolutionError` naming the
|
|
80
|
+
unresolved fields. Functions must be pure. `float(value)` reads a snapshot of the
|
|
81
|
+
last resolved value. Failed resolution leaves an existing output file untouched.
|
|
82
|
+
|
|
83
|
+
Creating a canvas opens it. Subsequent objects attach automatically; `pop()`
|
|
84
|
+
returns to the previous canvas. `with canvas(...)` restores the previous scope
|
|
85
|
+
on exit. Use `canvas(activate=False)` for explicit ownership and `append()`.
|
|
86
|
+
|
|
87
|
+
## Objects
|
|
88
|
+
|
|
89
|
+
- `element(label, column=..., top=..., bottom=... / length=...)`: vertical box.
|
|
90
|
+
Alternatively use `span=[top, bottom]`. `element.port(index)` returns a reactive
|
|
91
|
+
row, with 0.5 grid-unit spacing; negative indices count from the bottom.
|
|
92
|
+
- `signal(label, row=..., left=..., right=... / length=...)`: horizontal box.
|
|
93
|
+
Alternatively use `span=[left, right]` or a mutable `components` list whose
|
|
94
|
+
outermost columns are connected. Endpoint formulas are prepared at save time.
|
|
95
|
+
- `caption(label, row=..., column=..., fill=..., length=...)`: automatically sized
|
|
96
|
+
label box; omit `length` to fit the text on each save.
|
|
97
|
+
- `connector(element_or_column, signal_or_row, position="auto", inout="out")`:
|
|
98
|
+
chevron marker. With a signal, the default chooses `left` or `right` when its
|
|
99
|
+
span lies entirely on that side of the element's column, including a signal
|
|
100
|
+
ending at the element. The side and arrow direction follow layout changes on
|
|
101
|
+
every save. A signal crossing both sides, or a numeric row, defaults to `left`.
|
|
102
|
+
Set `position="left"` or `"right"` to override. Flow is `in`, `out` or `inout`.
|
|
103
|
+
- `chevron(...)`: individual direction stencil.
|
|
104
|
+
- `canvas(...)`: group with an optional background, border style and per-side
|
|
105
|
+
margins in pixels. Bounds follow its members automatically.
|
|
106
|
+
|
|
107
|
+
Default grid size is 80 pixels. `set_color_scheme()` changes global defaults;
|
|
108
|
+
explicit object colors take precedence. Assign `None` to inherit again.
|
|
109
|
+
`get_color_scheme()` returns a copy; `reset_color_scheme()` restores defaults.
|
|
110
|
+
|
|
111
|
+
`draw_diagram()` provides the lower-level renderer. To normalize existing
|
|
112
|
+
drawings, run `gridquill-normalize drawing.excalidraw.md` (edits the input file).
|
|
113
|
+
|
|
114
|
+
## Drawing order
|
|
115
|
+
|
|
116
|
+
Set `z_index` to control overlaps. Higher values appear in front, and each box
|
|
117
|
+
and its label stay together:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
with gq.canvas() as diagram:
|
|
121
|
+
block = gq.element("FPGA", column=2, span=[0, 4])
|
|
122
|
+
bus = gq.signal("Bus", row=2, span=[0, 4], z_index=1)
|
|
123
|
+
|
|
124
|
+
bus.z_index = 2 # editable after creation; applied on the next save
|
|
125
|
+
diagram.save("layers.excalidraw")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Signals, elements, chevrons, captions, connectors and canvases accept `z_index`
|
|
129
|
+
(default `0`). Negative and fractional values are allowed, as are reactive
|
|
130
|
+
formulas. A canvas adds its `z_index` to its descendants' values, so a connector
|
|
131
|
+
or a nested canvas can shift all its members together. Layers compare across
|
|
132
|
+
canvas boundaries. Canvas backgrounds remain behind all drawing objects.
|
|
133
|
+
|
|
134
|
+
Equal values preserve the existing order: signals (including captions), then
|
|
135
|
+
elements, then chevrons, in traversal order within each kind. Existing diagrams
|
|
136
|
+
therefore keep their drawing order. For `draw_diagram()`, set `z_index` in each
|
|
137
|
+
horizontal or vertical item dictionary; connection arrows remain behind boxes.
|
|
138
|
+
|
|
139
|
+
## Examples and development
|
|
140
|
+
|
|
141
|
+
```console
|
|
142
|
+
python -m pip install -e ".[dev]"
|
|
143
|
+
python -m unittest discover -s tests
|
|
144
|
+
python examples/canvas_scope_example.py
|
|
145
|
+
python examples/recreate_udt_overview.py --output overview.excalidraw.md
|
|
146
|
+
python -m build
|
|
147
|
+
python -m twine check dist/*
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Source lives in `src/gridquill`; tests and runnable examples live in `tests` and
|
|
151
|
+
`examples`. The firmware-specific CSV layout exporter remains in the firmware
|
|
152
|
+
repository because it depends on the separate `fmake` toolchain.
|
|
153
|
+
|
|
154
|
+
## Publishing
|
|
155
|
+
|
|
156
|
+
Add author details and actual project URLs to `pyproject.toml`.
|
|
157
|
+
Set `__version__` in `src/gridquill/__init__.py` for a release.
|
|
158
|
+
Build and validate the distributions using the commands above. When ready,
|
|
159
|
+
upload explicitly with `python -m twine upload dist/*` using your PyPI account.
|
|
160
|
+
No publishing credentials or upload automation are included.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Gridquill
|
|
2
|
+
|
|
3
|
+
Build editable diagrams in Python using reactive coordinates, nested canvases,
|
|
4
|
+
elements, signals, captions and connectors. Export to `.excalidraw` for Excalidraw
|
|
5
|
+
or `.excalidraw.md` for Obsidian. Python 3.10+; no runtime dependencies.
|
|
6
|
+
|
|
7
|
+
## Install from this checkout
|
|
8
|
+
|
|
9
|
+
```console
|
|
10
|
+
python -m pip install -e .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package is prepared for PyPI but has not been published.
|
|
14
|
+
|
|
15
|
+
## Draw a diagram
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import gridquill as gq
|
|
19
|
+
|
|
20
|
+
gq.set_color_scheme(
|
|
21
|
+
element_color=0x1971C2, element_fill=0xA5D8FF,
|
|
22
|
+
signal_color=0xF08C00, signal_fill=0xFFEC99,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
with gq.canvas("Example") as diagram:
|
|
26
|
+
with gq.canvas("Acquisition", column=2, row=1,
|
|
27
|
+
background=0xEDF2FF, margin_left=20, margin_right=20,
|
|
28
|
+
margin_top=20, margin_bottom=20):
|
|
29
|
+
adc = gq.element("ADC", column=0, top=0, length=4)
|
|
30
|
+
fpga = gq.element("FPGA", column=4, top=0, length=4)
|
|
31
|
+
samples = gq.signal("Samples", row=adc.port(2), components=[adc])
|
|
32
|
+
samples.components.append(fpga)
|
|
33
|
+
gq.connector(fpga, samples, inout="in")
|
|
34
|
+
gq.caption("Acquisition", row=-1, column=0)
|
|
35
|
+
|
|
36
|
+
diagram.save("example.excalidraw.md")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Coordinates and ownership
|
|
40
|
+
|
|
41
|
+
Literal coordinates are local to their canvas. On save, coordinate references
|
|
42
|
+
resolve to positions in the drawing, including all parent offsets. A captured
|
|
43
|
+
reference such as `fpga.column` follows later moves and assignments. Sizes and
|
|
44
|
+
lengths are distances; coordinate formulas return drawing coordinates.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
fpga.column = lambda: adc.column + 5
|
|
48
|
+
diagram.resolve() # resolve without writing a file
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Geometry fields and span entries accept numbers, reactive arithmetic or
|
|
52
|
+
zero-argument functions. Each save evaluates them in passes. Pending dependencies
|
|
53
|
+
read as NaN; stalled resolution raises `ReactiveResolutionError` naming the
|
|
54
|
+
unresolved fields. Functions must be pure. `float(value)` reads a snapshot of the
|
|
55
|
+
last resolved value. Failed resolution leaves an existing output file untouched.
|
|
56
|
+
|
|
57
|
+
Creating a canvas opens it. Subsequent objects attach automatically; `pop()`
|
|
58
|
+
returns to the previous canvas. `with canvas(...)` restores the previous scope
|
|
59
|
+
on exit. Use `canvas(activate=False)` for explicit ownership and `append()`.
|
|
60
|
+
|
|
61
|
+
## Objects
|
|
62
|
+
|
|
63
|
+
- `element(label, column=..., top=..., bottom=... / length=...)`: vertical box.
|
|
64
|
+
Alternatively use `span=[top, bottom]`. `element.port(index)` returns a reactive
|
|
65
|
+
row, with 0.5 grid-unit spacing; negative indices count from the bottom.
|
|
66
|
+
- `signal(label, row=..., left=..., right=... / length=...)`: horizontal box.
|
|
67
|
+
Alternatively use `span=[left, right]` or a mutable `components` list whose
|
|
68
|
+
outermost columns are connected. Endpoint formulas are prepared at save time.
|
|
69
|
+
- `caption(label, row=..., column=..., fill=..., length=...)`: automatically sized
|
|
70
|
+
label box; omit `length` to fit the text on each save.
|
|
71
|
+
- `connector(element_or_column, signal_or_row, position="auto", inout="out")`:
|
|
72
|
+
chevron marker. With a signal, the default chooses `left` or `right` when its
|
|
73
|
+
span lies entirely on that side of the element's column, including a signal
|
|
74
|
+
ending at the element. The side and arrow direction follow layout changes on
|
|
75
|
+
every save. A signal crossing both sides, or a numeric row, defaults to `left`.
|
|
76
|
+
Set `position="left"` or `"right"` to override. Flow is `in`, `out` or `inout`.
|
|
77
|
+
- `chevron(...)`: individual direction stencil.
|
|
78
|
+
- `canvas(...)`: group with an optional background, border style and per-side
|
|
79
|
+
margins in pixels. Bounds follow its members automatically.
|
|
80
|
+
|
|
81
|
+
Default grid size is 80 pixels. `set_color_scheme()` changes global defaults;
|
|
82
|
+
explicit object colors take precedence. Assign `None` to inherit again.
|
|
83
|
+
`get_color_scheme()` returns a copy; `reset_color_scheme()` restores defaults.
|
|
84
|
+
|
|
85
|
+
`draw_diagram()` provides the lower-level renderer. To normalize existing
|
|
86
|
+
drawings, run `gridquill-normalize drawing.excalidraw.md` (edits the input file).
|
|
87
|
+
|
|
88
|
+
## Drawing order
|
|
89
|
+
|
|
90
|
+
Set `z_index` to control overlaps. Higher values appear in front, and each box
|
|
91
|
+
and its label stay together:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
with gq.canvas() as diagram:
|
|
95
|
+
block = gq.element("FPGA", column=2, span=[0, 4])
|
|
96
|
+
bus = gq.signal("Bus", row=2, span=[0, 4], z_index=1)
|
|
97
|
+
|
|
98
|
+
bus.z_index = 2 # editable after creation; applied on the next save
|
|
99
|
+
diagram.save("layers.excalidraw")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Signals, elements, chevrons, captions, connectors and canvases accept `z_index`
|
|
103
|
+
(default `0`). Negative and fractional values are allowed, as are reactive
|
|
104
|
+
formulas. A canvas adds its `z_index` to its descendants' values, so a connector
|
|
105
|
+
or a nested canvas can shift all its members together. Layers compare across
|
|
106
|
+
canvas boundaries. Canvas backgrounds remain behind all drawing objects.
|
|
107
|
+
|
|
108
|
+
Equal values preserve the existing order: signals (including captions), then
|
|
109
|
+
elements, then chevrons, in traversal order within each kind. Existing diagrams
|
|
110
|
+
therefore keep their drawing order. For `draw_diagram()`, set `z_index` in each
|
|
111
|
+
horizontal or vertical item dictionary; connection arrows remain behind boxes.
|
|
112
|
+
|
|
113
|
+
## Examples and development
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
python -m pip install -e ".[dev]"
|
|
117
|
+
python -m unittest discover -s tests
|
|
118
|
+
python examples/canvas_scope_example.py
|
|
119
|
+
python examples/recreate_udt_overview.py --output overview.excalidraw.md
|
|
120
|
+
python -m build
|
|
121
|
+
python -m twine check dist/*
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Source lives in `src/gridquill`; tests and runnable examples live in `tests` and
|
|
125
|
+
`examples`. The firmware-specific CSV layout exporter remains in the firmware
|
|
126
|
+
repository because it depends on the separate `fmake` toolchain.
|
|
127
|
+
|
|
128
|
+
## Publishing
|
|
129
|
+
|
|
130
|
+
Add author details and actual project URLs to `pyproject.toml`.
|
|
131
|
+
Set `__version__` in `src/gridquill/__init__.py` for a release.
|
|
132
|
+
Build and validate the distributions using the commands above. When ready,
|
|
133
|
+
upload explicitly with `python -m twine upload dist/*` using your PyPI account.
|
|
134
|
+
No publishing credentials or upload automation are included.
|
|
135
|
+
|
|
136
|
+
## License
|
|
137
|
+
|
|
138
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Runnable Gridquill examples; install the package before running them."""
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Run python canvas_background_example.py to generate an editable example."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import gridquill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
# The root stays invisible. Each nested canvas opts into its own style.
|
|
10
|
+
diagram = gridquill.canvas(activate=False)
|
|
11
|
+
acquisition = diagram.append(gridquill.canvas(
|
|
12
|
+
"acquisition", column=1, row=1,
|
|
13
|
+
background=0xEDF2FF, border_color=0x1971C2, border_style="dashed",
|
|
14
|
+
margin_left=30, margin_right=40, margin_top=20, margin_bottom=25,
|
|
15
|
+
activate=False,
|
|
16
|
+
))
|
|
17
|
+
acquisition.append(gridquill.element("ADC", column=0, span=[0, 4]))
|
|
18
|
+
acquisition.append(gridquill.signal("Samples", row=2, span=[0, 4]))
|
|
19
|
+
acquisition.append(gridquill.element("Processing", column=4, span=[0, 4]))
|
|
20
|
+
|
|
21
|
+
group = acquisition.append(gridquill.canvas(
|
|
22
|
+
"control", column=0, row=6,
|
|
23
|
+
background=0xFFF9DB, border_color=0xE67700, border_style="dotted",
|
|
24
|
+
margin_left=15, margin_right=15, margin_top=15, margin_bottom=15,
|
|
25
|
+
activate=False,
|
|
26
|
+
))
|
|
27
|
+
group.append(gridquill.signal("Configuration", row=0, span=[0, 4]))
|
|
28
|
+
|
|
29
|
+
# Resizing, moving or removing members changes the bounds on the next save.
|
|
30
|
+
output = Path(__file__).parent / "build" / "canvas_background_example.excalidraw"
|
|
31
|
+
output.parent.mkdir(exist_ok=True)
|
|
32
|
+
print(diagram.save(output))
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Run py canvas_scope_example.py for automatic canvas ownership examples."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import gridquill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def build_diagram():
|
|
9
|
+
with gridquill.canvas("diagram") as diagram:
|
|
10
|
+
# A constructor opens a child canvas; pop returns to diagram.
|
|
11
|
+
acquisition = gridquill.canvas(
|
|
12
|
+
"acquisition", column=1, row=1, background=0xEDF2FF,
|
|
13
|
+
border_color=0x1971C2, margin_left=20, margin_right=20,
|
|
14
|
+
margin_top=20, margin_bottom=20,
|
|
15
|
+
)
|
|
16
|
+
gridquill.element("ADC", column=0, span=[0, 4])
|
|
17
|
+
gridquill.signal("Samples", row=2, span=[0, 4])
|
|
18
|
+
gridquill.connector(column=4, row=2, position="left", inout="in")
|
|
19
|
+
gridquill.element("Processing", column=4, span=[0, 4])
|
|
20
|
+
gridquill.pop()
|
|
21
|
+
|
|
22
|
+
# A with block automatically pops, including on an exception.
|
|
23
|
+
with gridquill.canvas(
|
|
24
|
+
"control", column=1, row=7, background=0xFFF9DB,
|
|
25
|
+
border_color=0xE67700, border_style="dashed",
|
|
26
|
+
margin_left=20, margin_right=20, margin_top=20, margin_bottom=20,
|
|
27
|
+
) as control:
|
|
28
|
+
gridquill.signal("Configuration", row=0, span=[0, 4])
|
|
29
|
+
|
|
30
|
+
assert gridquill.current_canvas() is diagram
|
|
31
|
+
assert acquisition.parent is diagram and control.parent is diagram
|
|
32
|
+
|
|
33
|
+
# No active canvas now: this object remains unassigned and is not exported.
|
|
34
|
+
unassigned = gridquill.element("not in the diagram")
|
|
35
|
+
assert unassigned.parent is None
|
|
36
|
+
return diagram
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
output = Path(__file__).parent / "build" / "canvas_scope_example.excalidraw"
|
|
41
|
+
output.parent.mkdir(exist_ok=True)
|
|
42
|
+
print(build_diagram().save(output))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Run py draw_chevrons_example.py to preview the UDT chevron stencil."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import gridquill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
diagram = gridquill.canvas(activate=False)
|
|
10
|
+
section = diagram.append(gridquill.canvas("chevron examples", column=1, row=1, activate=False))
|
|
11
|
+
for row, direction in enumerate(("right", "left", "up", "down")):
|
|
12
|
+
section.append(gridquill.signal(direction, row=row, span=[0, 3]))
|
|
13
|
+
section.append(gridquill.chevron(
|
|
14
|
+
direction, column=3.25, row=row, direction=direction))
|
|
15
|
+
print(diagram.save(Path(__file__).with_name("chevrons_example.excalidraw.md")))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Run py draw_connectors_example.py to preview left/right in/out/inout ports."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import gridquill
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
diagram = gridquill.canvas(activate=False)
|
|
10
|
+
diagram.append(gridquill.element("element at column 4", column=4, span=[0, 4]))
|
|
11
|
+
for row, flow in enumerate(("in", "out", "inout"), start=1):
|
|
12
|
+
diagram.append(gridquill.signal("left: " + flow, row=row, span=[0, 4]))
|
|
13
|
+
diagram.append(gridquill.signal("right: " + flow, row=row, span=[4, 8]))
|
|
14
|
+
for side in ("left", "right"):
|
|
15
|
+
diagram.append(gridquill.connector(column=4, row=row,
|
|
16
|
+
position=side, inout=flow))
|
|
17
|
+
print(diagram.save(Path(__file__).with_name("connectors_example.excalidraw.md")))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Run `python draw_diagram_example.py`, then open the generated drawing."""
|
|
2
|
+
|
|
3
|
+
from gridquill.render import draw_diagram
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__":
|
|
7
|
+
output = draw_diagram(
|
|
8
|
+
"udt_example.excalidraw.md",
|
|
9
|
+
horizontal=[
|
|
10
|
+
{"text": "Lemo", "x": 20, "y": 60, "length": 240},
|
|
11
|
+
{"text": "RF waveform", "x": 300, "y": 60, "length": 240},
|
|
12
|
+
{"text": "HV Pulse", "x": 300, "y": 200, "length": 240},
|
|
13
|
+
],
|
|
14
|
+
vertical=[
|
|
15
|
+
{"text": "Ultrasound Probe", "x": 0, "y": 0, "length": 300},
|
|
16
|
+
{"text": "IO decoupler", "x": 280, "y": 0, "length": 300},
|
|
17
|
+
{"text": "ADC", "x": 560, "y": 0, "length": 140},
|
|
18
|
+
{"text": "HV pulse generator", "x": 560, "y": 160, "length": 280},
|
|
19
|
+
],
|
|
20
|
+
)
|
|
21
|
+
print(output)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Run python draw_objects_example.py to generate an editable nested diagram."""
|
|
2
|
+
|
|
3
|
+
import gridquill
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
if __name__ == "__main__":
|
|
7
|
+
canvas = gridquill.canvas(activate=False)
|
|
8
|
+
canvas.signal_gap = 4
|
|
9
|
+
signal = gridquill.signal()
|
|
10
|
+
signal.row = 5
|
|
11
|
+
signal.span = [2, 4]
|
|
12
|
+
signal.label = "signal 1"
|
|
13
|
+
canvas.append(signal)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
channel = gridquill.canvas("channel", activate=False)
|
|
17
|
+
channel.append(gridquill.element("ADC", column=0, span=[0, 4]))
|
|
18
|
+
channel.append(gridquill.signal("RF waveform", row=1, span=[0, 3]))
|
|
19
|
+
channel.row = 5
|
|
20
|
+
channel.column = 2
|
|
21
|
+
canvas.append(channel)
|
|
22
|
+
|
|
23
|
+
# Children stay relative to channel.column and channel.row.
|
|
24
|
+
print(canvas.save("objects_example.excalidraw.md"))
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Recreate UDT_overview using only the public Gridquill library objects.
|
|
2
|
+
|
|
3
|
+
Run: py recreate_udt_overview.py
|
|
4
|
+
The original drawing is not read or modified by this generator. The layout
|
|
5
|
+
preserves its labels (including original spelling), two staggered planes,
|
|
6
|
+
probe banks, shared PC/backplane and signal directions. Standalone captions
|
|
7
|
+
become signal boxes because the library currently has no text-only object.
|
|
8
|
+
|
|
9
|
+
Canvas ownership uses with blocks and automatic attachment; helpers do not
|
|
10
|
+
need a parent parameter or append calls. Each scope restores its parent.
|
|
11
|
+
All positions/spans are grid units. Background margins are pixels.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import gridquill
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
COLOR_SCHEME = dict(
|
|
20
|
+
element_color=0x1971C2,
|
|
21
|
+
element_fill=0xA5D8FF,
|
|
22
|
+
signal_color=0xF08C00,
|
|
23
|
+
signal_fill=0xFFEC99,
|
|
24
|
+
chevron_color=0x1971C2,
|
|
25
|
+
chevron_fill=0xA5D8FF,
|
|
26
|
+
text_color=0x1E1E1E,
|
|
27
|
+
)
|
|
28
|
+
PANEL_BORDER = 0x228BE6
|
|
29
|
+
PINK = 0xFFC9C9
|
|
30
|
+
GREEN = 0xB2F2BB
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def probe_bank(label, column, row, decoupler_column):
|
|
34
|
+
"""Four staggered symbols represent the original bank labelled 8 Probes."""
|
|
35
|
+
with gridquill.canvas(label, column=column, row=row) as bank:
|
|
36
|
+
gridquill.caption("8 Probes", row=-1.55, column=-10.7, fill=0xFFFFFF)
|
|
37
|
+
for i in range(4):
|
|
38
|
+
slope = 0.35
|
|
39
|
+
x = gridquill.element("Ultra sound Probe", column=-10 + i*slope,
|
|
40
|
+
top=-1.10 + i*slope, length=4)
|
|
41
|
+
gridquill.signal("Lemo", left= x.column, right= decoupler_column, row=0.27 + i*slope)
|
|
42
|
+
return bank
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def plane(label, column, row, fill, fpga = None) -> gridquill.canvas:
|
|
47
|
+
margins=(50, 50, 20, 20)
|
|
48
|
+
fpga = [0, 0] if fpga is None else fpga
|
|
49
|
+
with gridquill.canvas(
|
|
50
|
+
label, column=column, row=row, background=fill,
|
|
51
|
+
border_color=PANEL_BORDER, border_style="solid",
|
|
52
|
+
margin_left=margins[0], margin_right=margins[1],
|
|
53
|
+
margin_top=margins[2], margin_bottom=margins[3],
|
|
54
|
+
) as result:
|
|
55
|
+
gridquill.caption(label, row=-1.5, column=-5., fill=fill)
|
|
56
|
+
IO_decopler = gridquill.element("IO decopler", column=-4.5, top=-1.0, bottom=2)
|
|
57
|
+
gain = gridquill.element("Gain", column=-1.5, top=-1.5, bottom=0)
|
|
58
|
+
ADC = gridquill.element("ADC", column=2, top=-.5, bottom=1.5)
|
|
59
|
+
HV = gridquill.element("HV pusle generator", column=1, top=0.5, length=3)
|
|
60
|
+
temp = gridquill.element("Temp. sensor", column=0., top=2.5, bottom=4.5)
|
|
61
|
+
fpga = gridquill.element("FPGA", column=5, top=gain.span[0]+fpga[0],
|
|
62
|
+
bottom=temp.span[1]+fpga[1])
|
|
63
|
+
|
|
64
|
+
gridquill.signal( "Gain Control", row=gain.port(0), components=[gain , fpga] )
|
|
65
|
+
gridquill.signal( "RF waveform", row=IO_decopler.port(0), components=[ IO_decopler , gain])
|
|
66
|
+
gridquill.signal( "RF waveform", row=gain.port(2), components=[ gain , ADC])
|
|
67
|
+
gridquill.signal( "RF waveform", row=ADC.port(-1), components=[ ADC , fpga ])
|
|
68
|
+
|
|
69
|
+
gridquill.signal( "HV Pulse", row=HV.port(-4), components=[ IO_decopler , HV ])
|
|
70
|
+
gridquill.signal( "Pulse", row=HV.port(-1), components=[ HV , fpga ])
|
|
71
|
+
gridquill.signal( "Temperatur", row=temp.port(-1), components=[ temp , fpga ])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
def build_overview():
|
|
77
|
+
# A standalone root keeps the caller's active canvas and drawing separate.
|
|
78
|
+
# The with block opens it; all nested helpers attach to the active scope.
|
|
79
|
+
with gridquill.canvas(
|
|
80
|
+
"UDT overview", grid_size=80, height=40, font_size=20,
|
|
81
|
+
signal_gap=2, activate=False,
|
|
82
|
+
) as diagram:
|
|
83
|
+
pl0 = plane(
|
|
84
|
+
"Top Plane",
|
|
85
|
+
column=0,
|
|
86
|
+
row=0,
|
|
87
|
+
fill=PINK,
|
|
88
|
+
fpga= [ 0, 4]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
pl1 = plane(
|
|
92
|
+
"Bottom Plane",
|
|
93
|
+
column=-2,
|
|
94
|
+
row=7.8,
|
|
95
|
+
fill=GREEN,
|
|
96
|
+
fpga= [-1, 0]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
probe_bank("Top probe bank", column=0, row=-0.5, decoupler_column=pl0["IO decopler"].column)
|
|
101
|
+
probe_bank("Bottom probe bank", column=-2, row=7.5, decoupler_column=pl1["IO decopler"].column)
|
|
102
|
+
|
|
103
|
+
# Shared components remain outside the plane canvases, as in the reference.
|
|
104
|
+
pc = gridquill.element("Embedded PC", column=10, top=2, bottom=5.8)
|
|
105
|
+
Backplane = gridquill.element("Backplane", column=8, top=6.5, bottom=12.30)
|
|
106
|
+
gridquill.signal("DMA", row=pl1["FPGA"].port(0), components=[pc,pl1["FPGA"] , pl0["FPGA"]])
|
|
107
|
+
|
|
108
|
+
eth = gridquill.signal("Ethernet", left= lambda:pc.column, right= 15.0, row= 2.25)
|
|
109
|
+
gridquill.connector(15.65, eth, "left", "inout")
|
|
110
|
+
|
|
111
|
+
# PIN has one direction marker at each FPGA, preserving both directions.
|
|
112
|
+
PIN = gridquill.signal("PIN", row=pl0["FPGA"].port(-1), components= [pl1["FPGA"] , pl0["FPGA"] ] )
|
|
113
|
+
gridquill.connector(pl1["FPGA"], PIN, "right", "in")
|
|
114
|
+
gridquill.connector(pl0["FPGA"] , PIN, "left", "in")
|
|
115
|
+
|
|
116
|
+
gridquill.signal("Trigger IO", row=Backplane.port(0), components= [Backplane ,pl0["FPGA"] ] )
|
|
117
|
+
gridquill.signal("Position Encoder", row=Backplane.port(1.5), components= [Backplane ,pl0["FPGA"] ] )
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
gridquill.signal("Trigger IO", row=Backplane.port(5.7), components= [Backplane ,pl1["FPGA"] ] )
|
|
121
|
+
|
|
122
|
+
gridquill.signal("Position Encoder", row=Backplane.port(7.2), components= [Backplane ,pl1["FPGA"] ] )
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
gridquill.signal("UDT_OptEingang", row=Backplane.port(-3), left= Backplane.column, right= 15.15 )
|
|
126
|
+
|
|
127
|
+
gridquill.signal("UDT_Output", row=Backplane.port(-1), left= Backplane.column, right= 15.15 )
|
|
128
|
+
|
|
129
|
+
return diagram
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
134
|
+
parser.add_argument("--output", type=Path,
|
|
135
|
+
default=Path("UDT_overview_recreated.excalidraw.md"))
|
|
136
|
+
args = parser.parse_args()
|
|
137
|
+
gridquill.set_color_scheme(**COLOR_SCHEME)
|
|
138
|
+
print(build_overview().save(args.output))
|
|
139
|
+
|