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.
Files changed (50) hide show
  1. thermodraw-1.0.0/LICENSE +21 -0
  2. thermodraw-1.0.0/NOTICE +44 -0
  3. thermodraw-1.0.0/PKG-INFO +204 -0
  4. thermodraw-1.0.0/README.md +177 -0
  5. thermodraw-1.0.0/pyproject.toml +58 -0
  6. thermodraw-1.0.0/setup.cfg +4 -0
  7. thermodraw-1.0.0/src/thermodraw/__init__.py +55 -0
  8. thermodraw-1.0.0/src/thermodraw/__main__.py +262 -0
  9. thermodraw-1.0.0/src/thermodraw/_check.py +1198 -0
  10. thermodraw-1.0.0/src/thermodraw/_describe.py +424 -0
  11. thermodraw-1.0.0/src/thermodraw/_layout.py +561 -0
  12. thermodraw-1.0.0/src/thermodraw/_metrics.py +642 -0
  13. thermodraw-1.0.0/src/thermodraw/_page.py +164 -0
  14. thermodraw-1.0.0/src/thermodraw/_physics.py +269 -0
  15. thermodraw-1.0.0/src/thermodraw/_render.py +463 -0
  16. thermodraw-1.0.0/src/thermodraw/_solve.py +358 -0
  17. thermodraw-1.0.0/src/thermodraw/builder.py +118 -0
  18. thermodraw-1.0.0/src/thermodraw/core.py +616 -0
  19. thermodraw-1.0.0/src/thermodraw/fonts/OFL.txt +93 -0
  20. thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-400.woff2 +0 -0
  21. thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-600.woff2 +0 -0
  22. thermodraw-1.0.0/src/thermodraw/fonts/thermodraw-sans-italic.woff2 +0 -0
  23. thermodraw-1.0.0/src/thermodraw/io.py +38 -0
  24. thermodraw-1.0.0/src/thermodraw/model.py +992 -0
  25. thermodraw-1.0.0/src/thermodraw/py.typed +0 -0
  26. thermodraw-1.0.0/src/thermodraw/symbols.py +608 -0
  27. thermodraw-1.0.0/src/thermodraw/theme.py +147 -0
  28. thermodraw-1.0.0/src/thermodraw.egg-info/PKG-INFO +204 -0
  29. thermodraw-1.0.0/src/thermodraw.egg-info/SOURCES.txt +48 -0
  30. thermodraw-1.0.0/src/thermodraw.egg-info/dependency_links.txt +1 -0
  31. thermodraw-1.0.0/src/thermodraw.egg-info/entry_points.txt +2 -0
  32. thermodraw-1.0.0/src/thermodraw.egg-info/requires.txt +6 -0
  33. thermodraw-1.0.0/src/thermodraw.egg-info/top_level.txt +1 -0
  34. thermodraw-1.0.0/tests/test_check.py +1429 -0
  35. thermodraw-1.0.0/tests/test_clean_room.py +1078 -0
  36. thermodraw-1.0.0/tests/test_cli.py +213 -0
  37. thermodraw-1.0.0/tests/test_describe.py +259 -0
  38. thermodraw-1.0.0/tests/test_docs.py +350 -0
  39. thermodraw-1.0.0/tests/test_fonts.py +63 -0
  40. thermodraw-1.0.0/tests/test_frame.py +292 -0
  41. thermodraw-1.0.0/tests/test_golden.py +29 -0
  42. thermodraw-1.0.0/tests/test_labels.py +269 -0
  43. thermodraw-1.0.0/tests/test_model.py +260 -0
  44. thermodraw-1.0.0/tests/test_page.py +130 -0
  45. thermodraw-1.0.0/tests/test_physics.py +198 -0
  46. thermodraw-1.0.0/tests/test_release.py +43 -0
  47. thermodraw-1.0.0/tests/test_render.py +183 -0
  48. thermodraw-1.0.0/tests/test_solve.py +445 -0
  49. thermodraw-1.0.0/tests/test_stability.py +84 -0
  50. thermodraw-1.0.0/tests/test_validation.py +265 -0
@@ -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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]