thermodraw 1.0.0__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.
- thermodraw-1.0.0/LICENSE +21 -0
- thermodraw-1.0.0/NOTICE +44 -0
- thermodraw-1.0.0/PKG-INFO +204 -0
- thermodraw-1.0.0/README.md +177 -0
- thermodraw-1.0.0/pyproject.toml +58 -0
- thermodraw-1.0.0/setup.cfg +4 -0
- thermodraw-1.0.0/src/thermodraw/__init__.py +55 -0
- thermodraw-1.0.0/src/thermodraw/__main__.py +262 -0
- thermodraw-1.0.0/src/thermodraw/_check.py +1198 -0
- thermodraw-1.0.0/src/thermodraw/_describe.py +424 -0
- thermodraw-1.0.0/src/thermodraw/_layout.py +561 -0
- thermodraw-1.0.0/src/thermodraw/_metrics.py +642 -0
- thermodraw-1.0.0/src/thermodraw/_page.py +164 -0
- thermodraw-1.0.0/src/thermodraw/_physics.py +269 -0
- thermodraw-1.0.0/src/thermodraw/_render.py +463 -0
- thermodraw-1.0.0/src/thermodraw/_solve.py +358 -0
- thermodraw-1.0.0/src/thermodraw/builder.py +118 -0
- thermodraw-1.0.0/src/thermodraw/core.py +616 -0
- thermodraw-1.0.0/src/thermodraw/fonts/OFL.txt +93 -0
- thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-400.woff2 +0 -0
- thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-600.woff2 +0 -0
- thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-italic.woff2 +0 -0
- thermodraw-1.0.0/src/thermodraw/io.py +38 -0
- thermodraw-1.0.0/src/thermodraw/model.py +992 -0
- thermodraw-1.0.0/src/thermodraw/py.typed +0 -0
- thermodraw-1.0.0/src/thermodraw/symbols.py +608 -0
- thermodraw-1.0.0/src/thermodraw/theme.py +147 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/PKG-INFO +204 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/SOURCES.txt +48 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/dependency_links.txt +1 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/entry_points.txt +2 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/requires.txt +6 -0
- thermodraw-1.0.0/src/thermodraw.egg-info/top_level.txt +1 -0
- thermodraw-1.0.0/tests/test_check.py +1429 -0
- thermodraw-1.0.0/tests/test_clean_room.py +1078 -0
- thermodraw-1.0.0/tests/test_cli.py +213 -0
- thermodraw-1.0.0/tests/test_describe.py +259 -0
- thermodraw-1.0.0/tests/test_docs.py +350 -0
- thermodraw-1.0.0/tests/test_fonts.py +63 -0
- thermodraw-1.0.0/tests/test_frame.py +292 -0
- thermodraw-1.0.0/tests/test_golden.py +29 -0
- thermodraw-1.0.0/tests/test_labels.py +269 -0
- thermodraw-1.0.0/tests/test_model.py +260 -0
- thermodraw-1.0.0/tests/test_page.py +130 -0
- thermodraw-1.0.0/tests/test_physics.py +198 -0
- thermodraw-1.0.0/tests/test_release.py +43 -0
- thermodraw-1.0.0/tests/test_render.py +183 -0
- thermodraw-1.0.0/tests/test_solve.py +445 -0
- thermodraw-1.0.0/tests/test_stability.py +84 -0
- thermodraw-1.0.0/tests/test_validation.py +265 -0
thermodraw-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Cetner
|
|
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.
|
thermodraw-1.0.0/NOTICE
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
ThermoDraw
|
|
2
|
+
Copyright (c) 2026 Peter Cetner
|
|
3
|
+
|
|
4
|
+
ThermoDraw's source code, documentation and generated diagrams are licensed
|
|
5
|
+
under the MIT License. The full text is in LICENSE, beside this file.
|
|
6
|
+
|
|
7
|
+
ThermoDraw has no runtime dependencies. It does, however, bundle three font
|
|
8
|
+
files, and those files are not MIT. This notice says what they are and under
|
|
9
|
+
what terms they travel.
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
--------------------------------------------------------------------------
|
|
13
|
+
Bundled fonts — SIL Open Font License 1.1
|
|
14
|
+
--------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
src/thermodraw/fonts/thermodraw-sans-400.woff2
|
|
17
|
+
src/thermodraw/fonts/thermodraw-sans-600.woff2
|
|
18
|
+
src/thermodraw/fonts/thermodraw-sans-italic.woff2
|
|
19
|
+
|
|
20
|
+
These are subsets of IBM Plex Sans:
|
|
21
|
+
|
|
22
|
+
Copyright (c) 2017 IBM Corp. with Reserved Font Name "Plex"
|
|
23
|
+
|
|
24
|
+
licensed under the SIL Open Font License, Version 1.1. The full licence text
|
|
25
|
+
ships with the fonts, at src/thermodraw/fonts/OFL.txt, and is also included in
|
|
26
|
+
the distribution metadata of every wheel and sdist.
|
|
27
|
+
|
|
28
|
+
Each face carries only the glyphs the library can emit, and kerning is
|
|
29
|
+
dropped, so a subset is a Modified Version under OFL clause 3. That clause
|
|
30
|
+
forbids a Modified Version from using the Reserved Font Name, so the faces are
|
|
31
|
+
renamed: they are "ThermoDraw Sans", not IBM Plex Sans, and must not be
|
|
32
|
+
represented as IBM Plex Sans. tools/subset_font.py performs the rename, and
|
|
33
|
+
tests/test_fonts.py asserts it.
|
|
34
|
+
|
|
35
|
+
The OFL applies to these three font files alone. It does not apply to
|
|
36
|
+
ThermoDraw's code, which is MIT, and — by the OFL's own terms — it does not
|
|
37
|
+
apply to documents produced with the fonts.
|
|
38
|
+
|
|
39
|
+
ThermoDraw can embed a face directly into the SVG it emits, for targets such
|
|
40
|
+
as Word, PowerPoint and standalone rasterisers that fetch nothing. Such an SVG
|
|
41
|
+
contains a copy of the OFL font. The OFL permits this: the fonts "can be
|
|
42
|
+
bundled, embedded, redistributed and/or sold with any software" provided the
|
|
43
|
+
Reserved Font Name is not used and the licence travels with them. If you
|
|
44
|
+
redistribute an SVG with an embedded face, carry this notice with it.
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thermodraw
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Thermal network diagrams for Python
|
|
5
|
+
Author: Peter Cetner
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: thermal,heat transfer,diagram,svg,schematic
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
License-File: NOTICE
|
|
20
|
+
License-File: src/thermodraw/fonts/OFL.txt
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest; extra == "dev"
|
|
23
|
+
Requires-Dist: mypy; extra == "dev"
|
|
24
|
+
Requires-Dist: fonttools; extra == "dev"
|
|
25
|
+
Requires-Dist: brotli; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<h1 align="center">ThermoDraw</h1>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
Thermal network diagrams for Python.<br>
|
|
32
|
+
Emits SVG. No runtime dependencies.
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
<p align="center">
|
|
36
|
+
<picture>
|
|
37
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/hero-dark.svg">
|
|
38
|
+
<img src="docs/assets/hero-light.svg" alt="A power device from junction to still air: conduction, contact and a parallel convection and radiation path, with two capacitances on the reference rail" width="960">
|
|
39
|
+
</picture>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center"><sub>Junction to still air — conduction, contact, then convection and radiation in parallel.</sub></p>
|
|
43
|
+
|
|
44
|
+
<br>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<picture>
|
|
48
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/vocabulary-dark.svg">
|
|
49
|
+
<img src="docs/assets/vocabulary-light.svg" alt="The eighteen symbols" width="800">
|
|
50
|
+
</picture>
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<p align="center"><sub>Eighteen symbols. Mechanism is carried by the interior texture, not by the outline.</sub></p>
|
|
54
|
+
|
|
55
|
+
<br>
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<picture>
|
|
59
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/rosette-dark.svg">
|
|
60
|
+
<img src="docs/assets/rosette-light.svg" alt="One node losing heat by twelve parallel paths, each drawn at a different angle" width="460">
|
|
61
|
+
</picture>
|
|
62
|
+
</p>
|
|
63
|
+
|
|
64
|
+
<p align="center"><sub>The four box textures at twelve angles. A texture belongs to its block and turns with it.</sub></p>
|
|
65
|
+
|
|
66
|
+
<br>
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install thermodraw
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Python 3.10 or later, no dependencies. From a checkout, `pip install -e .`.
|
|
75
|
+
|
|
76
|
+
## Use
|
|
77
|
+
|
|
78
|
+
A diagram is data. Write it, or have a model write it, and render it:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from thermodraw import Diagram, save
|
|
82
|
+
|
|
83
|
+
d = Diagram.from_json(open("hero.json", encoding="utf-8").read())
|
|
84
|
+
|
|
85
|
+
save(d.svg(), "web.svg") # follows the reader's light/dark
|
|
86
|
+
save(d.svg("light"), "word.svg") # colours and font resolved, for Word and slides
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The stages are there if you want them — `render(layout(d))` is the SVG
|
|
90
|
+
before the theme, `theme.with_variables` and `theme.bake` are the two ways
|
|
91
|
+
to finish it — and a notebook shows a `Diagram` as its drawing. If your
|
|
92
|
+
readers know circuit notation, `d.svg(notation="zigzags")` draws every
|
|
93
|
+
resistance as a zigzag instead of a textured box; nothing else moves. A PNG needs a
|
|
94
|
+
rasteriser, which the library does not carry:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
import cairosvg
|
|
98
|
+
cairosvg.svg2png(bytestring=d.svg("light").encode("utf-8"), write_to="out.png")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or build it in Python:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from thermodraw import DiagramBuilder
|
|
105
|
+
|
|
106
|
+
d = (DiagramBuilder(R="K/W", T="°C", P="W")
|
|
107
|
+
.node("j", "Junction", 112, at=(200, 150), sub="j")
|
|
108
|
+
.node("c", "Case", 78, at=(424, 150), sub="c")
|
|
109
|
+
.branch("j", "c", "cond", "Die attach", "0.35")
|
|
110
|
+
.source("j", "diss", "Switching loss", 45, sub="d"))
|
|
111
|
+
open("out.svg", "w", encoding="utf-8").write(d.svg("light"))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Then find out whether it is any good, without opening it:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
thermodraw check hero.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
hero.json: 11 labels placed, 0 errors, 0 warnings, 1 note
|
|
122
|
+
note: [parallel-pair-same-side] branch 2 s->amb and branch 3 s->amb run
|
|
123
|
+
between the same two nodes and both labels went to the same side
|
|
124
|
+
-> set `side` to "down" on the lower of the two
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Twelve checks on how the drawing reads — text over text, a label shoved out
|
|
128
|
+
past the thing it names, a wire through a symbol, ink off the page. Exit 0
|
|
129
|
+
clean, 1 on a warning or an error, 2 when the file could not be read. A note
|
|
130
|
+
is advice and does not fail the run — the report above exits 0 — unless you
|
|
131
|
+
pass `--strict`. Every finding names the schema field that fixes it.
|
|
132
|
+
|
|
133
|
+
`--physics` asks a different question: whether the numbers agree with each
|
|
134
|
+
other — what arrives at each node against what its temperatures and
|
|
135
|
+
resistances say leaves. It is opt-in, because a sketch with placeholder
|
|
136
|
+
numbers is a diagram too; ask for it when you believe the numbers.
|
|
137
|
+
|
|
138
|
+
A clean report is not the same as the right diagram, so there is a second
|
|
139
|
+
question:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
thermodraw describe hero.json
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
hero.json: canvas 1042 x 431, 11 labels
|
|
147
|
+
|
|
148
|
+
placements: ground x1, node x4, symbol/cap x2, symbol/cond x1,
|
|
149
|
+
symbol/contact x1, symbol/conv x1, symbol/diss x1, symbol/rad x1,
|
|
150
|
+
wire x15
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
...then every node with its kind and place, and every label with the direction
|
|
154
|
+
it went. `check` grades the drawing; this says what is in it.
|
|
155
|
+
`thermodraw render` writes the SVG. `thermodraw page` writes the same drawing
|
|
156
|
+
as a self-contained HTML page with its controls — a repeated group of sixteen
|
|
157
|
+
draws two and an ellipsis, and the page lets a reader expand it without
|
|
158
|
+
anything being rebuilt. `thermodraw solve` writes a diagram back with every
|
|
159
|
+
node placed, to edit from: write the network without coordinates, solve it,
|
|
160
|
+
move what you would have put elsewhere. All five work as `python -m
|
|
161
|
+
thermodraw` from a checkout.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
python examples/render_demo.py # the three images above
|
|
165
|
+
python examples/render_reference.py # every symbol at every 45°
|
|
166
|
+
pytest
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Where this sits
|
|
170
|
+
|
|
171
|
+
Drawing schematics from Python is not an empty field, and laying out a graph
|
|
172
|
+
is a solved problem — Graphviz, D2 and Mermaid will place an arbitrary network
|
|
173
|
+
for you, and schemdraw will draw it in circuit notation with a resistor
|
|
174
|
+
zigzag for every path. What none of them does is the thing this exists for:
|
|
175
|
+
say *which mechanism* each path is, in a notation a thermal engineer reads,
|
|
176
|
+
and then say whether the drawing reads well and whether its numbers agree
|
|
177
|
+
with each other.
|
|
178
|
+
|
|
179
|
+
So the parts that are ThermoDraw's own are the eighteen-symbol vocabulary and
|
|
180
|
+
the rule behind it, the label solver, `check`, `describe` and `--physics`.
|
|
181
|
+
Node coordinates are solved for a chain of nodes, which is what nearly every
|
|
182
|
+
network in this notation is. A general placer for anything else is the part
|
|
183
|
+
most likely to be someone else's solved problem, and the design record says
|
|
184
|
+
which of the twelve checks it must satisfy, which it minimises, and which it
|
|
185
|
+
makes redundant.
|
|
186
|
+
|
|
187
|
+
If you want circuit notation, use schemdraw. If you want a graph laid out and
|
|
188
|
+
do not care what the boxes mean, use Graphviz. If you want a thermal network
|
|
189
|
+
that a reviewer can read from the picture, this.
|
|
190
|
+
|
|
191
|
+
## More
|
|
192
|
+
|
|
193
|
+
[`docs/schema.md`](docs/schema.md) — the whole format, written to be pasted into a prompt.
|
|
194
|
+
[`docs/stability.md`](docs/stability.md) — what 1.0 promises to keep, and what it does not.
|
|
195
|
+
[`docs/symbol-reference.html`](docs/symbol-reference.html) — every symbol at eight orientations, with the reasoning.
|
|
196
|
+
[`CLAUDE.md`](CLAUDE.md) — the decisions, one line each.
|
|
197
|
+
[`docs/design-record.md`](docs/design-record.md) — the argument behind each one.
|
|
198
|
+
|
|
199
|
+
The symbol vocabulary is settled; labels, wire runs, canvas size and, for a
|
|
200
|
+
chain of nodes, the coordinates are solved for you, and `thermodraw check`
|
|
201
|
+
reports what a reader would notice. A network that is not a chain still takes
|
|
202
|
+
its coordinates from you, and says so by name.
|
|
203
|
+
|
|
204
|
+
MIT. The bundled subset of IBM Plex Sans is [OFL-1.1](src/thermodraw/fonts/OFL.txt).
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<h1 align="center">ThermoDraw</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
Thermal network diagrams for Python.<br>
|
|
5
|
+
Emits SVG. No runtime dependencies.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<picture>
|
|
10
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/hero-dark.svg">
|
|
11
|
+
<img src="docs/assets/hero-light.svg" alt="A power device from junction to still air: conduction, contact and a parallel convection and radiation path, with two capacitances on the reference rail" width="960">
|
|
12
|
+
</picture>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center"><sub>Junction to still air — conduction, contact, then convection and radiation in parallel.</sub></p>
|
|
16
|
+
|
|
17
|
+
<br>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<picture>
|
|
21
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/vocabulary-dark.svg">
|
|
22
|
+
<img src="docs/assets/vocabulary-light.svg" alt="The eighteen symbols" width="800">
|
|
23
|
+
</picture>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center"><sub>Eighteen symbols. Mechanism is carried by the interior texture, not by the outline.</sub></p>
|
|
27
|
+
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<picture>
|
|
32
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/rosette-dark.svg">
|
|
33
|
+
<img src="docs/assets/rosette-light.svg" alt="One node losing heat by twelve parallel paths, each drawn at a different angle" width="460">
|
|
34
|
+
</picture>
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
<p align="center"><sub>The four box textures at twelve angles. A texture belongs to its block and turns with it.</sub></p>
|
|
38
|
+
|
|
39
|
+
<br>
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install thermodraw
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Python 3.10 or later, no dependencies. From a checkout, `pip install -e .`.
|
|
48
|
+
|
|
49
|
+
## Use
|
|
50
|
+
|
|
51
|
+
A diagram is data. Write it, or have a model write it, and render it:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from thermodraw import Diagram, save
|
|
55
|
+
|
|
56
|
+
d = Diagram.from_json(open("hero.json", encoding="utf-8").read())
|
|
57
|
+
|
|
58
|
+
save(d.svg(), "web.svg") # follows the reader's light/dark
|
|
59
|
+
save(d.svg("light"), "word.svg") # colours and font resolved, for Word and slides
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The stages are there if you want them — `render(layout(d))` is the SVG
|
|
63
|
+
before the theme, `theme.with_variables` and `theme.bake` are the two ways
|
|
64
|
+
to finish it — and a notebook shows a `Diagram` as its drawing. If your
|
|
65
|
+
readers know circuit notation, `d.svg(notation="zigzags")` draws every
|
|
66
|
+
resistance as a zigzag instead of a textured box; nothing else moves. A PNG needs a
|
|
67
|
+
rasteriser, which the library does not carry:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import cairosvg
|
|
71
|
+
cairosvg.svg2png(bytestring=d.svg("light").encode("utf-8"), write_to="out.png")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or build it in Python:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from thermodraw import DiagramBuilder
|
|
78
|
+
|
|
79
|
+
d = (DiagramBuilder(R="K/W", T="°C", P="W")
|
|
80
|
+
.node("j", "Junction", 112, at=(200, 150), sub="j")
|
|
81
|
+
.node("c", "Case", 78, at=(424, 150), sub="c")
|
|
82
|
+
.branch("j", "c", "cond", "Die attach", "0.35")
|
|
83
|
+
.source("j", "diss", "Switching loss", 45, sub="d"))
|
|
84
|
+
open("out.svg", "w", encoding="utf-8").write(d.svg("light"))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then find out whether it is any good, without opening it:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
thermodraw check hero.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
hero.json: 11 labels placed, 0 errors, 0 warnings, 1 note
|
|
95
|
+
note: [parallel-pair-same-side] branch 2 s->amb and branch 3 s->amb run
|
|
96
|
+
between the same two nodes and both labels went to the same side
|
|
97
|
+
-> set `side` to "down" on the lower of the two
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Twelve checks on how the drawing reads — text over text, a label shoved out
|
|
101
|
+
past the thing it names, a wire through a symbol, ink off the page. Exit 0
|
|
102
|
+
clean, 1 on a warning or an error, 2 when the file could not be read. A note
|
|
103
|
+
is advice and does not fail the run — the report above exits 0 — unless you
|
|
104
|
+
pass `--strict`. Every finding names the schema field that fixes it.
|
|
105
|
+
|
|
106
|
+
`--physics` asks a different question: whether the numbers agree with each
|
|
107
|
+
other — what arrives at each node against what its temperatures and
|
|
108
|
+
resistances say leaves. It is opt-in, because a sketch with placeholder
|
|
109
|
+
numbers is a diagram too; ask for it when you believe the numbers.
|
|
110
|
+
|
|
111
|
+
A clean report is not the same as the right diagram, so there is a second
|
|
112
|
+
question:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
thermodraw describe hero.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
hero.json: canvas 1042 x 431, 11 labels
|
|
120
|
+
|
|
121
|
+
placements: ground x1, node x4, symbol/cap x2, symbol/cond x1,
|
|
122
|
+
symbol/contact x1, symbol/conv x1, symbol/diss x1, symbol/rad x1,
|
|
123
|
+
wire x15
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
...then every node with its kind and place, and every label with the direction
|
|
127
|
+
it went. `check` grades the drawing; this says what is in it.
|
|
128
|
+
`thermodraw render` writes the SVG. `thermodraw page` writes the same drawing
|
|
129
|
+
as a self-contained HTML page with its controls — a repeated group of sixteen
|
|
130
|
+
draws two and an ellipsis, and the page lets a reader expand it without
|
|
131
|
+
anything being rebuilt. `thermodraw solve` writes a diagram back with every
|
|
132
|
+
node placed, to edit from: write the network without coordinates, solve it,
|
|
133
|
+
move what you would have put elsewhere. All five work as `python -m
|
|
134
|
+
thermodraw` from a checkout.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
python examples/render_demo.py # the three images above
|
|
138
|
+
python examples/render_reference.py # every symbol at every 45°
|
|
139
|
+
pytest
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Where this sits
|
|
143
|
+
|
|
144
|
+
Drawing schematics from Python is not an empty field, and laying out a graph
|
|
145
|
+
is a solved problem — Graphviz, D2 and Mermaid will place an arbitrary network
|
|
146
|
+
for you, and schemdraw will draw it in circuit notation with a resistor
|
|
147
|
+
zigzag for every path. What none of them does is the thing this exists for:
|
|
148
|
+
say *which mechanism* each path is, in a notation a thermal engineer reads,
|
|
149
|
+
and then say whether the drawing reads well and whether its numbers agree
|
|
150
|
+
with each other.
|
|
151
|
+
|
|
152
|
+
So the parts that are ThermoDraw's own are the eighteen-symbol vocabulary and
|
|
153
|
+
the rule behind it, the label solver, `check`, `describe` and `--physics`.
|
|
154
|
+
Node coordinates are solved for a chain of nodes, which is what nearly every
|
|
155
|
+
network in this notation is. A general placer for anything else is the part
|
|
156
|
+
most likely to be someone else's solved problem, and the design record says
|
|
157
|
+
which of the twelve checks it must satisfy, which it minimises, and which it
|
|
158
|
+
makes redundant.
|
|
159
|
+
|
|
160
|
+
If you want circuit notation, use schemdraw. If you want a graph laid out and
|
|
161
|
+
do not care what the boxes mean, use Graphviz. If you want a thermal network
|
|
162
|
+
that a reviewer can read from the picture, this.
|
|
163
|
+
|
|
164
|
+
## More
|
|
165
|
+
|
|
166
|
+
[`docs/schema.md`](docs/schema.md) — the whole format, written to be pasted into a prompt.
|
|
167
|
+
[`docs/stability.md`](docs/stability.md) — what 1.0 promises to keep, and what it does not.
|
|
168
|
+
[`docs/symbol-reference.html`](docs/symbol-reference.html) — every symbol at eight orientations, with the reasoning.
|
|
169
|
+
[`CLAUDE.md`](CLAUDE.md) — the decisions, one line each.
|
|
170
|
+
[`docs/design-record.md`](docs/design-record.md) — the argument behind each one.
|
|
171
|
+
|
|
172
|
+
The symbol vocabulary is settled; labels, wire runs, canvas size and, for a
|
|
173
|
+
chain of nodes, the coordinates are solved for you, and `thermodraw check`
|
|
174
|
+
reports what a reader would notice. A network that is not a chain still takes
|
|
175
|
+
its coordinates from you, and says so by name.
|
|
176
|
+
|
|
177
|
+
MIT. The bundled subset of IBM Plex Sans is [OFL-1.1](src/thermodraw/fonts/OFL.txt).
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# 77 is the floor for PEP 639 (`license` as an SPDX expression, `license-files`).
|
|
3
|
+
requires = ["setuptools>=77"]
|
|
4
|
+
build-backend = "setuptools.build_meta"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "thermodraw"
|
|
8
|
+
version = "1.0.0"
|
|
9
|
+
description = "Thermal network diagrams for Python"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
# The code is MIT. The three bundled font faces are subsets of IBM Plex Sans
|
|
14
|
+
# under OFL-1.1, which requires its licence to travel with them: NOTICE says
|
|
15
|
+
# which files those are, and OFL.txt is the full text. See NOTICE.
|
|
16
|
+
license-files = ["LICENSE", "NOTICE", "src/thermodraw/fonts/OFL.txt"]
|
|
17
|
+
authors = [{ name = "Peter Cetner" }]
|
|
18
|
+
keywords = ["thermal", "heat transfer", "diagram", "svg", "schematic"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 5 - Production/Stable",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Scientific/Engineering",
|
|
28
|
+
]
|
|
29
|
+
dependencies = []
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
# `thermodraw check` is the point of having a script at all: it turns
|
|
33
|
+
# "render it, serve it, open a browser, screenshot it, look" into one command
|
|
34
|
+
# whose output is text and whose exit status is the answer.
|
|
35
|
+
thermodraw = "thermodraw.__main__:main"
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
# fonttools and brotli are for tools/subset_font.py and gen_metrics.py; the
|
|
39
|
+
# suite itself needs only pytest. Nothing here rasterises: cairosvg and
|
|
40
|
+
# pillow were declared for a while, imported nowhere, and kept alive by a
|
|
41
|
+
# CI job whose only purpose was to notice if they stopped installing.
|
|
42
|
+
dev = ["pytest", "mypy", "fonttools", "brotli"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
pythonpath = ["src", "examples", "tests"]
|
|
47
|
+
# A deprecation raised from inside the package fails the run. Scoped to the
|
|
48
|
+
# package on purpose: an unscoped `-W error` would break CI on a third-party
|
|
49
|
+
# warning the day a new Python lands, for a reason that is nobody's here.
|
|
50
|
+
filterwarnings = ["error::DeprecationWarning:thermodraw"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.packages.find]
|
|
53
|
+
where = ["src"]
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
# py.typed is the PEP 561 marker: without it type checkers ignore the package
|
|
57
|
+
# entirely, however fully annotated it is.
|
|
58
|
+
thermodraw = ["py.typed", "fonts/*.woff2", "fonts/OFL.txt"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""ThermoDraw — thermal network diagrams for Python.
|
|
2
|
+
|
|
3
|
+
A diagram is data. You write it, or an LLM writes it, and three pure stages
|
|
4
|
+
turn it into a document:
|
|
5
|
+
|
|
6
|
+
dict / JSON -> Diagram -> placements -> SVG
|
|
7
|
+
model layout render
|
|
8
|
+
(solve fills in the `at` you left out)
|
|
9
|
+
|
|
10
|
+
from thermodraw import Diagram, layout, render, save, theme
|
|
11
|
+
|
|
12
|
+
d = Diagram.from_json(open("diagram.json").read())
|
|
13
|
+
save(theme.with_variables(render(layout(d))), "out.svg")
|
|
14
|
+
|
|
15
|
+
`render` emits CSS custom properties with no fallback, so its output goes
|
|
16
|
+
through `theme` before it is saved: `with_variables` for the web, `bake` for
|
|
17
|
+
Word, slides and rasterisers. Without one of them the file draws nothing.
|
|
18
|
+
|
|
19
|
+
`DiagramBuilder` is sugar over the same data, and `symbols` still places one
|
|
20
|
+
symbol at a time for anything the model does not yet cover.
|
|
21
|
+
|
|
22
|
+
The schema is docs/schema.md. Design notes and the reasoning behind each
|
|
23
|
+
symbol are in CLAUDE.md.
|
|
24
|
+
"""
|
|
25
|
+
from . import core, io, model, symbols, theme
|
|
26
|
+
from ._check import Finding, Report, check
|
|
27
|
+
from ._describe import Description, describe
|
|
28
|
+
from ._layout import Placement, layout
|
|
29
|
+
from ._page import page
|
|
30
|
+
from ._render import render
|
|
31
|
+
from ._solve import solve
|
|
32
|
+
from .builder import DiagramBuilder
|
|
33
|
+
from .io import save
|
|
34
|
+
from .model import Branch, Diagram, DiagramError, Node, Rail, Source
|
|
35
|
+
from .symbols import SYMBOLS, Symbol
|
|
36
|
+
|
|
37
|
+
__version__ = "1.0.0"
|
|
38
|
+
__all__ = [
|
|
39
|
+
# the data
|
|
40
|
+
"Diagram", "Node", "Branch", "Source", "Rail", "DiagramError",
|
|
41
|
+
# the pipeline
|
|
42
|
+
"solve", "layout", "render", "Placement", "DiagramBuilder",
|
|
43
|
+
# is it any good?
|
|
44
|
+
"check", "Report", "Finding",
|
|
45
|
+
# is it the one you meant?
|
|
46
|
+
"describe", "Description",
|
|
47
|
+
# the same drawing, as a page you can interact with
|
|
48
|
+
"page",
|
|
49
|
+
# the vocabulary
|
|
50
|
+
"Symbol", "SYMBOLS",
|
|
51
|
+
# output
|
|
52
|
+
"save", "theme",
|
|
53
|
+
# still public, for placing one symbol at a time
|
|
54
|
+
"symbols", "core", "model", "io",
|
|
55
|
+
]
|