@openpowershift/logic-diagram-language 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/dist/examples.d.ts +2 -0
- package/dist/examples.js +469 -0
- package/dist/export-image.d.ts +10 -0
- package/dist/export-image.js +47 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.html +13 -0
- package/dist/index.js +18 -0
- package/dist/parser/ast.d.ts +118 -0
- package/dist/parser/ast.js +79 -0
- package/dist/parser/index.d.ts +3 -0
- package/dist/parser/index.js +2 -0
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +635 -0
- package/dist/renderer/astar-router.d.ts +16 -0
- package/dist/renderer/astar-router.js +532 -0
- package/dist/renderer/gates.d.ts +19 -0
- package/dist/renderer/gates.js +92 -0
- package/dist/renderer/graph.d.ts +31 -0
- package/dist/renderer/graph.js +403 -0
- package/dist/renderer/layout.d.ts +76 -0
- package/dist/renderer/layout.js +3121 -0
- package/dist/renderer/math-renderer.d.ts +16 -0
- package/dist/renderer/math-renderer.js +119 -0
- package/dist/renderer/svg-renderer.d.ts +3 -0
- package/dist/renderer/svg-renderer.js +599 -0
- package/dist/renderer/wires.d.ts +5 -0
- package/dist/renderer/wires.js +12 -0
- package/dist/theme/themes.d.ts +58 -0
- package/dist/theme/themes.js +104 -0
- package/docs/api.adoc +196 -0
- package/docs/ldl-for-llms.md +163 -0
- package/docs/user-guide.adoc +318 -0
- package/package.json +78 -0
- package/spec/render.sh +22 -0
- package/spec/sections/attributes.adoc +80 -0
- package/spec/sections/connections.adoc +39 -0
- package/spec/sections/examples.adoc +212 -0
- package/spec/sections/expressions.adoc +182 -0
- package/spec/sections/file-extension.adoc +5 -0
- package/spec/sections/function-blocks.adoc +120 -0
- package/spec/sections/grammar.adoc +64 -0
- package/spec/sections/hyperlinks.adoc +18 -0
- package/spec/sections/introduction.adoc +16 -0
- package/spec/sections/layout-rules.adoc +491 -0
- package/spec/sections/lexical-conventions.adoc +68 -0
- package/spec/sections/objects.adoc +31 -0
- package/spec/sections/options.adoc +146 -0
- package/spec/sections/ports.adoc +77 -0
- package/spec/sections/rendering-contract.adoc +11 -0
- package/spec/sections/revision-history.adoc +9 -0
- package/spec/sections/styling.adoc +83 -0
- package/spec/sections/svg-symbol-specification.adoc +149 -0
- package/spec/sections/symbol-definitions.adoc +143 -0
- package/spec/sections/templates.adoc +31 -0
- package/spec/spec.adoc +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Mulholland
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# LDL — Logic Diagram Language
|
|
2
|
+
|
|
3
|
+
LDL is a small text language and renderer for **logic and protection diagrams**. You
|
|
4
|
+
write boolean equations; the renderer draws the gates, routes the wires, and lays the
|
|
5
|
+
whole diagram out automatically — producing clean, publication-quality SVG (and PNG/PDF
|
|
6
|
+
in the browser).
|
|
7
|
+
|
|
8
|
+
> **Try it in the browser: [openpowershift.github.io/logic-diagram-language](https://openpowershift.github.io/logic-diagram-language/)**
|
|
9
|
+
> — a live playground: type LDL on the left, see the diagram on the right.
|
|
10
|
+
>
|
|
11
|
+
> Published as **`@openpowershift/logic-diagram-language`**. See the **[API Reference](docs/api.adoc)**
|
|
12
|
+
> for programmatic use.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
TRIP = OVERCURRENT AND NOT BLOCK
|
|
16
|
+
OR (EARTH AND MANUAL)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That single expression yields a complete diagram: boundary inputs on the left, gates in
|
|
20
|
+
the middle, the `TRIP` output on the right, with wires routed and crossings minimised —
|
|
21
|
+
no coordinates, no manual placement.
|
|
22
|
+
|
|
23
|
+
## Why
|
|
24
|
+
|
|
25
|
+
Protection and control schematics are tedious to draw by hand and awkward to keep in
|
|
26
|
+
sync with the logic they represent. LDL treats the **logic as the source of truth**: the
|
|
27
|
+
diagram is a rendering of an expression, so it is diffable, reviewable, and regenerated
|
|
28
|
+
automatically whenever the logic changes.
|
|
29
|
+
|
|
30
|
+
## Goals & concepts
|
|
31
|
+
|
|
32
|
+
- **Expression-first.** A simple boolean expression produces a complete diagram. Names
|
|
33
|
+
that are consumed elsewhere become shared internal signals; names that aren't become
|
|
34
|
+
outputs.
|
|
35
|
+
- **Declarative — the layout is the renderer's job.** You describe _what_ is connected,
|
|
36
|
+
not _where_ things go. A Sugiyama-style layout engine assigns layers and coordinates
|
|
37
|
+
and actively minimises wire crossings; every reshaping pass validates a single
|
|
38
|
+
wire-separation contract so wires never overlap or crowd.
|
|
39
|
+
- **Protection-domain aware.** Beyond `AND`/`OR`/`NOT`, LDL has SEL-style function
|
|
40
|
+
blocks — `TIMER`, `SR` latch, `RISING`/`FALLING` edge triggers, `COMPARE` comparator,
|
|
41
|
+
and a generic `FB` block — plus seal-in/feedback loops drawn as loop-back wires.
|
|
42
|
+
- **Labelled & styled.** Inputs, outputs and gates carry `.Name`/`.Description` labels
|
|
43
|
+
(with inline TeX math via MathJax), and every element has a stable id/class for CSS.
|
|
44
|
+
- **Readable output.** Options control inversion bubbles vs. NOT gates, input bars,
|
|
45
|
+
compactness, adaptive column spacing, and more.
|
|
46
|
+
|
|
47
|
+
## Use as a library
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm install @openpowershift/logic-diagram-language
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import {
|
|
55
|
+
parse,
|
|
56
|
+
renderDiagram,
|
|
57
|
+
resolveOptions,
|
|
58
|
+
} from "@openpowershift/logic-diagram-language";
|
|
59
|
+
|
|
60
|
+
const { diagram } = parse("O1 = I1 AND NOT I2");
|
|
61
|
+
const svg = renderDiagram(
|
|
62
|
+
diagram,
|
|
63
|
+
diagram.portMeta,
|
|
64
|
+
true,
|
|
65
|
+
false,
|
|
66
|
+
resolveOptions(diagram.options),
|
|
67
|
+
); // → a complete <svg>…</svg> string
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
SVG rendering is isomorphic (Node + browser). In the browser, `svgToPngBlob(svg)` and
|
|
71
|
+
`svgToPdfBlob(svg)` rasterise it to a PNG/PDF `Blob`. Full API, options table and PNG/PDF
|
|
72
|
+
recipes are in the **[API Reference](docs/api.adoc)**.
|
|
73
|
+
|
|
74
|
+
## Playground / development
|
|
75
|
+
|
|
76
|
+
Requires Node 20+ (the dev playground uses Node 24 — see `.nvmrc`).
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npm run dev # live playground at the printed URL — type LDL, see SVG update live
|
|
81
|
+
npm run build # type-check + production build of the playground app to dist/
|
|
82
|
+
npm run build:lib # build the publishable library to dist/ (index.js + type declarations)
|
|
83
|
+
npm test # run the test suite (vitest)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The playground has a source editor on the left and a live diagram on the right, with a
|
|
87
|
+
dropdown of built-in examples covering every language feature. Toggle labels/ids,
|
|
88
|
+
junction dots, zoom, and export to SVG/PNG/PDF.
|
|
89
|
+
|
|
90
|
+
## A taste of the language
|
|
91
|
+
|
|
92
|
+
```ldl
|
|
93
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
94
|
+
|
|
95
|
+
// SEL-style: a comparator feeds an SR latch, sealed in, into a pickup timer
|
|
96
|
+
A = SR(COMPARE(IA, IPICKUP), RESET)
|
|
97
|
+
TRIP = TIMER(A, 0, 30cyc)
|
|
98
|
+
ALARM = RISING(COMPARE(IA, IPICKUP))
|
|
99
|
+
|
|
100
|
+
IA.Name = "$I_a$"
|
|
101
|
+
IPICKUP.Name = "$I_{pickup}$"
|
|
102
|
+
TRIP.Name = "Trip"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
See the **[User Guide](docs/user-guide.adoc)** for the full working syntax with
|
|
106
|
+
examples, and the **[LDL Specification](spec/spec.adoc)** for the formal grammar and the
|
|
107
|
+
rendering contract. Current implementation status is tracked in
|
|
108
|
+
[IMPLEMENTATION.md](IMPLEMENTATION.md).
|
|
109
|
+
|
|
110
|
+
**Generating LDL with an AI agent?** Point it at
|
|
111
|
+
**[docs/ldl-for-llms.md](docs/ldl-for-llms.md)** — a compact, implemented-only authoring guide (the
|
|
112
|
+
full spec includes reserved features that don't render yet, which trips agents up).
|
|
113
|
+
|
|
114
|
+
## Project layout
|
|
115
|
+
|
|
116
|
+
| Path | What |
|
|
117
|
+
| --------------------- | ------------------------------------------------------------------------------------- |
|
|
118
|
+
| `src/index.ts` | Public library API (`parse`, `renderDiagram`, `layoutDiagram`, options, types) |
|
|
119
|
+
| `src/export-image.ts` | Browser-only PNG/PDF helpers (`svgToPngBlob`, `svgToPdfBlob`) |
|
|
120
|
+
| `src/parser/` | Tokeniser, parser, and AST for LDL source |
|
|
121
|
+
| `src/renderer/` | Layout engine (`layout.ts`, `graph.ts`), A\* wire router, SVG renderer, gate symbols |
|
|
122
|
+
| `src/components/` | Lit web components — the playground app, editor and viewer |
|
|
123
|
+
| `src/worker/` | Off-thread parse → layout → render worker |
|
|
124
|
+
| `docs/` | [User guide](docs/user-guide.adoc) and [API reference](docs/api.adoc) |
|
|
125
|
+
| `spec/` | AsciiDoc language specification (`spec/spec.adoc`) |
|
|
126
|
+
| `tests/` | Vitest unit tests, layout invariants, and golden geometry/visual-regression snapshots |
|
|
127
|
+
|
|
128
|
+
## Status
|
|
129
|
+
|
|
130
|
+
LDL is under active development. The core language (boolean expressions, intermediates,
|
|
131
|
+
feedback, SEL function blocks, labels, TeX math, styling, and the layout options) is
|
|
132
|
+
implemented and tested. Some specified constructs are reserved but not yet implemented —
|
|
133
|
+
the `NAND`/`NOR`/`XOR`/`XNOR` operators, `CONNECT`, custom `SYMBOL` definitions,
|
|
134
|
+
`IMPORT`/`STYLESHEET`, and hyperlinks. See the guide and `IMPLEMENTATION.md` for the
|
|
135
|
+
current boundary.
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
[MIT](LICENSE) © 2026 Daniel Mulholland
|
package/dist/examples.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
export const EXAMPLES = {
|
|
2
|
+
'Breaker Failure (SEL-751A)': `// Feedback seal-in: BFT appears inside its OWN definition, so rather than
|
|
3
|
+
// becoming a second input it is drawn as a loop-back wire (a latch holding
|
|
4
|
+
// itself in). Labels use inline TeX ($...$). OUTPUT_ORDER / INPUT_ORDER = AUTO
|
|
5
|
+
// reorder ports to minimise crossings (INPUT_ORDER is AUTO by default anyway).
|
|
6
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
7
|
+
OPTION INPUT_ORDER = AUTO
|
|
8
|
+
|
|
9
|
+
BFT = BFI OR BFT AND ((CB52A AND CB52ABFY) OR (I1I2 AND INOM))
|
|
10
|
+
|
|
11
|
+
BFI.Description = "Relay Word Bit"
|
|
12
|
+
|
|
13
|
+
CB52A.Name = "52A"
|
|
14
|
+
CB52ABFY.Name = "52ABF = Y"
|
|
15
|
+
CB52ABFY.Description = "Setting"
|
|
16
|
+
|
|
17
|
+
I1I2.Name = "$\\mathrm{|I1|+|I2|}$"
|
|
18
|
+
INOM.Name = "$\\mathrm{0.02 \\cdot I_{NOM}}$"
|
|
19
|
+
|
|
20
|
+
// SEL-751A Instruction Manual Figure 4.55 Breaker Failure Logic`,
|
|
21
|
+
'SEL Function Blocks': `// SEL-style function blocks compose like any sub-expression:
|
|
22
|
+
// TIMER(in, pu, do) - pickup/dropout delays. A bare number is cycles; ms/s/m
|
|
23
|
+
// also work, or name them PU=/DO=. 0 = no delay.
|
|
24
|
+
// SR(set, reset).Q - set/reset latch (.NQ for Q-bar; DOMINANT=SET, see "SR Latch").
|
|
25
|
+
// COMPARE(+, -) - analog comparator. RISING/FALLING(in) - edge one-shots.
|
|
26
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
27
|
+
A = SR(COMPARE(IA, IPICKUP), RESET)
|
|
28
|
+
TRIP = TIMER(A, 0, 30cyc)
|
|
29
|
+
ALARM = RISING(COMPARE(IA, IPICKUP))
|
|
30
|
+
RESTART = FALLING(BLOCK)`,
|
|
31
|
+
'SR Latch (Q and NQ)': `// SR latch with both outputs: .Q (default) and .NQ (the inverted output, Q-bar).
|
|
32
|
+
// Referencing SR#L1 twice reuses the SAME latch; DOMINANT=SET makes 'set' win
|
|
33
|
+
// when both inputs are asserted (reset-dominant by default). Each output port
|
|
34
|
+
// is drawn only when it is referenced.
|
|
35
|
+
PERMIT = SR#L1(START, STOP, DOMINANT=SET).Q
|
|
36
|
+
BLOCKED = SR#L1(START, STOP).NQ
|
|
37
|
+
L1.Name = "Enable Latch"`,
|
|
38
|
+
'Layout Options': `// Layout controls: ADAPTIVE column spacing packs each column to its content
|
|
39
|
+
// (default UNIFORM), COMPACT_V tightens row spacing (also COMPACT_H, COMPACT,
|
|
40
|
+
// SPACIOUS, or explicit factors like [70,70]), and an expression may span
|
|
41
|
+
// multiple lines — a continuation line simply begins with whitespace.
|
|
42
|
+
OPTION COLUMN_SPACING = ADAPTIVE
|
|
43
|
+
OPTION COMPACTNESS = COMPACT_V
|
|
44
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
45
|
+
|
|
46
|
+
TRIP = PHASE AND ENABLE
|
|
47
|
+
OR (EARTH AND NOT BLOCK)
|
|
48
|
+
OR MANUAL
|
|
49
|
+
|
|
50
|
+
PHASE.Name = "Phase OC"
|
|
51
|
+
EARTH.Name = "Earth Fault"
|
|
52
|
+
BLOCK.Name = "Harmonic Block"`,
|
|
53
|
+
'Labelled Gates': `// Name a gate by assigning it to an intermediate, then label that name — the
|
|
54
|
+
// name/description appear at the gate's output and wires route around them.
|
|
55
|
+
// (For the same result without the pass-through name, see "Named Gates".)
|
|
56
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
57
|
+
PH = O51 OR O50 OR NEGSEQ
|
|
58
|
+
EF = E51N OR E50N
|
|
59
|
+
HS = O502 OR E50N2
|
|
60
|
+
SUP = PH AND NOT HBLK
|
|
61
|
+
TRIP = SUP OR EF OR HS
|
|
62
|
+
PH.Name = "Phase OC"
|
|
63
|
+
PH.Description = "51/50/46"
|
|
64
|
+
EF.Name = "Earth Fault"
|
|
65
|
+
EF.Description = "51N/50N"
|
|
66
|
+
HS.Name = "High-Set"
|
|
67
|
+
HS.Description = "50-2"
|
|
68
|
+
SUP.Name = "Supervised"
|
|
69
|
+
SUP.Description = "harmonic block"`,
|
|
70
|
+
'Complex Protection (SEL)': `// Every block type, nested and mixed with gates. A block may take an instance
|
|
71
|
+
// id (TIMER#TD, COMPARE#C50) so it can carry a .Name/.Description, and blocks
|
|
72
|
+
// nest freely — here a COMPARE feeds an SR feeds a TIMER. Bare cycle numbers
|
|
73
|
+
// (2cyc, 12cyc) are positional PU/DO settings.
|
|
74
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
75
|
+
|
|
76
|
+
OC_TRIP = TIMER#TD(SR#TL(COMPARE#C50(IA, IPICKUP) AND NOT BLOCK, RESET), 2cyc, 0)
|
|
77
|
+
BF_TRIP = TIMER#BFT(COMPARE#C87(IDIFF, IREST) AND CB52A, 0, 12cyc)
|
|
78
|
+
ALARM = RISING(START) OR EXT_ALARM
|
|
79
|
+
RECLOSE = FALLING(CB52A) AND NOT LOCKOUT
|
|
80
|
+
|
|
81
|
+
C50.Name = "50P1"
|
|
82
|
+
C50.Description = "Phase OC"
|
|
83
|
+
C87.Name = "87"
|
|
84
|
+
C87.Description = "Differential"
|
|
85
|
+
TL.Name = "Trip Latch"
|
|
86
|
+
TD.Name = "62T"
|
|
87
|
+
TD.Description = "Trip delay"
|
|
88
|
+
BFT.Name = "62BF"
|
|
89
|
+
BFT.Description = "BF delay"`,
|
|
90
|
+
'Shared Intermediates': `// Consumed intermediates: A is internal but labelled at its junction;
|
|
91
|
+
// B is forced to an output with .OUT = TRUE
|
|
92
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
93
|
+
B = COMPARE(IA, IPICKUP)
|
|
94
|
+
A = SR(B, RESET) OR B OR C
|
|
95
|
+
TRIP = TIMER(A, 0, 30cyc)
|
|
96
|
+
ALARM = RISING(COMPARE(IA, IPICKUP))
|
|
97
|
+
A.Name = "Trip Permit"
|
|
98
|
+
A.Description = "Seal-in"
|
|
99
|
+
B.OUT = TRUE`,
|
|
100
|
+
'Generic Block (FB)': `// A generic user block (FB): a box with whatever ports you need, for a function
|
|
101
|
+
// whose internals aren't drawn. Call arguments are input ports (NAME=expr labels
|
|
102
|
+
// them); each .PORT you reference is an output port. Reusing one #id (FB#PROT)
|
|
103
|
+
// binds several outputs to a SINGLE box.
|
|
104
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
105
|
+
TRIP = FB#PROT(PHASE=IA, EARTH=IN, EN=ENABLE).TRIP
|
|
106
|
+
ALARM = FB#PROT.ALARM
|
|
107
|
+
CLOSE = FB#PROT.CLOSE
|
|
108
|
+
PROT.Name = "Feeder Protection"
|
|
109
|
+
PROT.Description = "SEL-751A"`,
|
|
110
|
+
'Named Gates': `// Direct gate naming with AND#ID(...) / OR#ID(...) — no pass-through intermediate needed
|
|
111
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
112
|
+
OC = OR#OC1(I51, I50, I46)
|
|
113
|
+
OC1.Name = "Overcurrent"
|
|
114
|
+
OC1.Description = "51/50N"
|
|
115
|
+
TRIP = AND#TRIP1(OC, NOT BLOCK)
|
|
116
|
+
TRIP1.Name = "Trip AND"
|
|
117
|
+
TRIP1.Description = "permissive"
|
|
118
|
+
O1 = AND#GEN(A, B)
|
|
119
|
+
GEN.Name = "Combo Gate"
|
|
120
|
+
GEN.Description = "stage 1"`,
|
|
121
|
+
'Simple AND Gate': `// The smallest diagram: one assignment. Bare identifiers that are never
|
|
122
|
+
// assigned (A, B) become boundary inputs on the left; the assigned name (OUT)
|
|
123
|
+
// becomes the output on the right. The operators are AND, OR and NOT.
|
|
124
|
+
OUT = A AND B`,
|
|
125
|
+
'Simple OR Gate': `// An OR gate. Its ">=1" glyph is the IEC 60617 symbol for OR (AND shows "&",
|
|
126
|
+
// NOT a triangle). Combine AND / OR / NOT to build any boolean expression.
|
|
127
|
+
OUT = A OR B`,
|
|
128
|
+
'NOT Gate': `// A NOT gate (inverter), drawn as a triangle with a bubble. With
|
|
129
|
+
// OPTION INVERSION = BUBBLES the inversion is drawn as just a bubble on the
|
|
130
|
+
// downstream port instead of a separate gate (see "Inversion Bubbles").
|
|
131
|
+
OUT = NOT A`,
|
|
132
|
+
'Combined Logic (CBFPS)': `// Operator precedence is NOT > AND > OR, so this parses as
|
|
133
|
+
// (AB AND DC) OR ((NOT DC) AND GF). Add parentheses to override precedence.
|
|
134
|
+
CBFPS = AB AND DC OR (NOT DC AND GF)`,
|
|
135
|
+
'Trip Logic': `// TRIP is used by MAIN_TRIP, so it is a consumed intermediate — drawn as an
|
|
136
|
+
// internal gate, not a boundary output. Add "TRIP.OUT = TRUE" to also expose it
|
|
137
|
+
// as an output (see "Shared Intermediates").
|
|
138
|
+
TRIP = OVERCURRENT OR (NOT EARTH_FAULT)
|
|
139
|
+
MAIN_TRIP = TRIP AND (MANUAL_TRIP OR REMOTE_TRIP)`,
|
|
140
|
+
'Three-Input AND': `// A multi-input gate. By default (GATE_INPUT_STYLE = EXPAND) the gate grows
|
|
141
|
+
// vertically to fit every input; GATE_INPUT_STYLE = BARS keeps it 2-input-sized
|
|
142
|
+
// and taps the extra inputs onto vertical bars instead (see "Input Bars").
|
|
143
|
+
OUT = A AND B AND C`,
|
|
144
|
+
'Triple OR': `// Three inputs into one OR gate. INPUT_ORDER = AUTO (the default) may reorder
|
|
145
|
+
// a gate's inputs to reduce wire crossings; INPUT_ORDER = DECLARATION keeps the
|
|
146
|
+
// order as written.
|
|
147
|
+
ALARM = TEMP OR PRESSURE OR FLOW`,
|
|
148
|
+
'Nested NOT': `// Double negation cancels: NOT NOT A = A, so no inverter is drawn. An odd
|
|
149
|
+
// number of NOTs reduces to a single inversion — try adding one more NOT.
|
|
150
|
+
OUT = NOT NOT A`,
|
|
151
|
+
'Complex Protection': `// TRIP_1 and TRIP_2 are referenced by MAIN_TRIP, so they are consumed
|
|
152
|
+
// intermediates: drawn as internal gates that fan out, not as boundary outputs.
|
|
153
|
+
// Only MAIN_TRIP (referenced by nothing) becomes an output. OUTPUT_ORDER = AUTO
|
|
154
|
+
// stacks outputs by their driver to reduce crossings (default: DECLARATION order).
|
|
155
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
156
|
+
TRIP_1 = OVERCURRENT_A OR OVERCURRENT_B
|
|
157
|
+
TRIP_2 = EARTH_FAULT AND NOT BLOCK
|
|
158
|
+
MAIN_TRIP = TRIP_1 AND TRIP_2`,
|
|
159
|
+
'Mixed Logic': `// Mixed AND/OR with negation: RESULT is consumed (not shown), OUTPUT is the result
|
|
160
|
+
RESULT = (A AND B) OR (C AND NOT D)
|
|
161
|
+
OUTPUT = RESULT AND (ENABLE OR FORCE)`,
|
|
162
|
+
'Launch Interlock': `// Launch interlock for Pad 39A: all safety permissives, no hold,
|
|
163
|
+
// AND either the automatic sequence OR a manual firing enable ignition
|
|
164
|
+
|
|
165
|
+
I1.Name = "Pad Cleared"
|
|
166
|
+
I1.Description = "(LP 3.1)"
|
|
167
|
+
|
|
168
|
+
I2.Name = "Umbilicals Retracted"
|
|
169
|
+
I2.Description = "(LP 3.24)"
|
|
170
|
+
|
|
171
|
+
I3.Name = "Tanks Pressurised"
|
|
172
|
+
I3.Description = "(LP 3.3)"
|
|
173
|
+
|
|
174
|
+
I4.Name = "Guidance Aligned"
|
|
175
|
+
I4.Description = "(GNC 3.5)"
|
|
176
|
+
|
|
177
|
+
I5.Name = "Hold Fired"
|
|
178
|
+
I5.Description = "(RSO 3.15)"
|
|
179
|
+
|
|
180
|
+
I6.Name = "Auto Sequence"
|
|
181
|
+
I6.Description = "(SEQ 3.11)"
|
|
182
|
+
|
|
183
|
+
I7.Name = "Range Safety Armed"
|
|
184
|
+
I7.Description = "(RSO 3.23)"
|
|
185
|
+
|
|
186
|
+
I8.Name = "GO from LCC"
|
|
187
|
+
I8.Description = "(via LCC)"
|
|
188
|
+
|
|
189
|
+
I9.Name = "Manual Enable"
|
|
190
|
+
I9.Description = "(LCC 3.12)"
|
|
191
|
+
|
|
192
|
+
I10.Name = "Fire Button"
|
|
193
|
+
I10.Description = "(LCC 3.20)"
|
|
194
|
+
|
|
195
|
+
O1 = (I1 AND I2) AND (I3 AND I4 AND NOT I5) AND ((I6 AND I7 AND I8) OR (I9 AND I10))
|
|
196
|
+
|
|
197
|
+
O1.Name = "Launch Command"
|
|
198
|
+
O1.Description = "(BO 3.2)"`,
|
|
199
|
+
'Inversion Bubbles': `// INVERSION = BUBBLES draws every NOT as a small bubble on the downstream
|
|
200
|
+
// input/output port instead of a separate NOT gate (the default is
|
|
201
|
+
// INVERSION = GATES). O4 shows double-inversion cancellation (NOT NOT I3 = I3).
|
|
202
|
+
OPTION INVERSION = BUBBLES
|
|
203
|
+
|
|
204
|
+
I1.Name = "Start Permitted"
|
|
205
|
+
I1.Description = "(BI 1.1)"
|
|
206
|
+
I2.Name = "Stop Condition"
|
|
207
|
+
I2.Description = "(BI 1.2)"
|
|
208
|
+
I3.Name = "Reset"
|
|
209
|
+
I3.Description = "(BI 1.3)"
|
|
210
|
+
|
|
211
|
+
O1 = I1 AND NOT I2 AND I3
|
|
212
|
+
O2 = NOT (I1 AND I3)
|
|
213
|
+
O3 = NOT I2
|
|
214
|
+
O4 = NOT NOT I3
|
|
215
|
+
|
|
216
|
+
O1.Name = "Run"
|
|
217
|
+
O1.Description = "(BO 1.1)"
|
|
218
|
+
O2.Name = "NAND Result"
|
|
219
|
+
O2.Description = "(BO 1.2)"
|
|
220
|
+
O3.Name = "Inverted Stop"
|
|
221
|
+
O3.Description = "(BO 1.3)"
|
|
222
|
+
O4.Name = "Pass-Through"
|
|
223
|
+
O4.Description = "(BO 1.4)"`,
|
|
224
|
+
'Input Bars': `// GATE_INPUT_STYLE = BARS keeps a multi-input gate at its base 2-input size and
|
|
225
|
+
// taps the extra inputs onto vertical bars. The default, EXPAND, grows the gate
|
|
226
|
+
// vertically instead (compare "Three-Input AND").
|
|
227
|
+
OPTION GATE_INPUT_STYLE = BARS
|
|
228
|
+
|
|
229
|
+
I1.Name = "Start"
|
|
230
|
+
I2.Name = "Stop"
|
|
231
|
+
I3.Name = "Reset"
|
|
232
|
+
I4.Name = "Enable"
|
|
233
|
+
I5.Name = "Supervision"
|
|
234
|
+
|
|
235
|
+
O1 = I1 AND I2 AND I3 AND I4 AND I5
|
|
236
|
+
O1.Name = "Output"`,
|
|
237
|
+
'Square Ports': `// PORT_STYLE = SQUARE draws port markers as small squares; the default is
|
|
238
|
+
// CIRCLE. (Other rendering options: INVERSION, GATE_INPUT_STYLE, LABEL_STYLE.)
|
|
239
|
+
OPTION PORT_STYLE = SQUARE
|
|
240
|
+
|
|
241
|
+
A.Name = "Signal A"
|
|
242
|
+
B.Name = "Signal B"
|
|
243
|
+
O1 = A AND B
|
|
244
|
+
O1.Name = "Result"`,
|
|
245
|
+
'Combined Options': `// Options combine freely. Here three at once — bubbles for NOT, bars for the
|
|
246
|
+
// multi-input gate, and square port markers — each overriding its own default
|
|
247
|
+
// (GATES / EXPAND / CIRCLE). Options must precede the logic they affect.
|
|
248
|
+
OPTION INVERSION = BUBBLES
|
|
249
|
+
OPTION GATE_INPUT_STYLE = BARS
|
|
250
|
+
OPTION PORT_STYLE = SQUARE
|
|
251
|
+
|
|
252
|
+
I1.Name = "Start"
|
|
253
|
+
I2.Name = "Stop"
|
|
254
|
+
I3.Name = "Reset"
|
|
255
|
+
I4.Name = "Enable"
|
|
256
|
+
I5.Name = "Supervision"
|
|
257
|
+
I6.Name = "Override"
|
|
258
|
+
|
|
259
|
+
O1 = (I1 AND NOT I2) AND (I3 AND I4 AND I5 AND I6)
|
|
260
|
+
O1.Name = "Master Trip"
|
|
261
|
+
O1.Description = "(BO 1.1)"`,
|
|
262
|
+
'Overcurrent Protection': `// Inline TeX math in labels: wrap it in $...$ (e.g. $I_a$, $I_{set}$). Plain
|
|
263
|
+
// text and math may be mixed, and a literal dollar is written \\$. I1 feeds both
|
|
264
|
+
// outputs — a name used more than once is one shared input that fans out.
|
|
265
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
266
|
+
I1.Name = "$I_a$"
|
|
267
|
+
I1.Description = "Phase A current"
|
|
268
|
+
|
|
269
|
+
I2.Name = "$I_b$"
|
|
270
|
+
I2.Description = "Phase B current"
|
|
271
|
+
|
|
272
|
+
I3.Name = "$I_c$"
|
|
273
|
+
I3.Description = "Phase C current"
|
|
274
|
+
|
|
275
|
+
I4.Name = "$I_{set}$"
|
|
276
|
+
I4.Description = "Setting $= 5.0 \\$ A$"
|
|
277
|
+
|
|
278
|
+
O1.Name = "$I > I_{set}$"
|
|
279
|
+
O1.Description = "Overcurrent trip"
|
|
280
|
+
|
|
281
|
+
O2.Name = "$I_a + I_b + I_c$"
|
|
282
|
+
O2.Description = "Residual current"
|
|
283
|
+
|
|
284
|
+
O1 = I1 AND I4
|
|
285
|
+
O2 = I1 OR I2 OR I3`,
|
|
286
|
+
'Differential Protection': `// More TeX-labelled I/O — subscripts ($I_{in}$), inequalities ($>$) and mixed
|
|
287
|
+
// text/math. I3 (Block) is used by both outputs, so it fans out to each.
|
|
288
|
+
// Differential protection: $I_{diff} = I_{in} - I_{out}$
|
|
289
|
+
|
|
290
|
+
I1.Name = "$I_{in}$"
|
|
291
|
+
I1.Description = "CT primary current"
|
|
292
|
+
|
|
293
|
+
I2.Name = "$I_{out}$"
|
|
294
|
+
I2.Description = "CT secondary current"
|
|
295
|
+
|
|
296
|
+
I3.Name = "Block"
|
|
297
|
+
I3.Description = "Blocking signal"
|
|
298
|
+
|
|
299
|
+
O1.Name = "$I_{diff} > I_{bias}$"
|
|
300
|
+
O1.Description = "Differential trip"
|
|
301
|
+
|
|
302
|
+
O2.Name = "Restricted Earth Fault"
|
|
303
|
+
O2.Description = "$I_0 > I_{0\\_set}$"
|
|
304
|
+
|
|
305
|
+
O1 = (I1 AND NOT I2) AND NOT I3
|
|
306
|
+
O2 = NOT I3`,
|
|
307
|
+
'Boolean Algebra': `// The four basic functions. There is no NAND operator — write it as
|
|
308
|
+
// NOT (A AND B), as O4 does. The overline in the labels is TeX
|
|
309
|
+
// ($\\overline{A \\cdot B}$), not a language construct.
|
|
310
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
311
|
+
|
|
312
|
+
A.Name = "$A$"
|
|
313
|
+
A.Description = "Input A"
|
|
314
|
+
|
|
315
|
+
B.Name = "$B$"
|
|
316
|
+
B.Description = "Input B"
|
|
317
|
+
|
|
318
|
+
C.Name = "$C$"
|
|
319
|
+
C.Description = "Input C"
|
|
320
|
+
|
|
321
|
+
O1.Name = "$A \\cdot B$"
|
|
322
|
+
O1.Description = "Logical AND"
|
|
323
|
+
|
|
324
|
+
O2.Name = "$A + B$"
|
|
325
|
+
O2.Description = "Logical OR"
|
|
326
|
+
|
|
327
|
+
O3.Name = "$\\overline{A}$"
|
|
328
|
+
O3.Description = "Logical NOT"
|
|
329
|
+
|
|
330
|
+
O4.Name = "$\\overline{A \\cdot B}$"
|
|
331
|
+
O4.Description = "NAND: $\\overline{AB}$"
|
|
332
|
+
|
|
333
|
+
O1 = A AND B
|
|
334
|
+
O2 = A OR B
|
|
335
|
+
O3 = NOT A
|
|
336
|
+
O4 = NOT (A AND B)`,
|
|
337
|
+
'Motor Control Circuit': `// A motor starter: TeX-labelled I/O plus INVERSION = BUBBLES for the NOT
|
|
338
|
+
// inputs. Note the OPTION line can sit anywhere before the expressions that use
|
|
339
|
+
// it (here it follows the label declarations).
|
|
340
|
+
|
|
341
|
+
I1.Name = "Start PB"
|
|
342
|
+
I1.Description = "$NO\\ contact$"
|
|
343
|
+
|
|
344
|
+
I2.Name = "Stop PB"
|
|
345
|
+
I2.Description = "$NC\\ contact$"
|
|
346
|
+
|
|
347
|
+
I3.Name = "OL Trip"
|
|
348
|
+
I3.Description = "$I^2 > I_{rated}^2$"
|
|
349
|
+
|
|
350
|
+
I4.Name = "Thermal"
|
|
351
|
+
I4.Description = "$\\Theta > \\Theta_{trip}$"
|
|
352
|
+
|
|
353
|
+
I5.Name = "$I_s$"
|
|
354
|
+
I5.Description = "Start current"
|
|
355
|
+
|
|
356
|
+
O1.Name = "Contactor"
|
|
357
|
+
O1.Description = "$K_1$"
|
|
358
|
+
|
|
359
|
+
O2.Name = "Trip"
|
|
360
|
+
O2.Description = "$\\Delta I > \\Delta I_{set}$"
|
|
361
|
+
|
|
362
|
+
OPTION INVERSION = BUBBLES
|
|
363
|
+
|
|
364
|
+
O1 = I1 AND NOT I2 AND NOT I3 AND NOT I4
|
|
365
|
+
O2 = I3 OR I4 OR NOT I5`,
|
|
366
|
+
'Styled by ID': `// Reveal IDs in the toolbar to see the SVG id on each gate, block and port.
|
|
367
|
+
//
|
|
368
|
+
// A gate/block group carries its instance id: AND#G1 -> id "G1", FB#P -> id "P".
|
|
369
|
+
// The gate body is a <path> and the block body a <rect>, each carrying its OWN fill
|
|
370
|
+
// and stroke, so target the shape, not the group: "#G1 path" styles the gate body and
|
|
371
|
+
// "#P rect" styles the block body. Setting fill/stroke on the group ("#G1" / "#P") does
|
|
372
|
+
// not reach the body — the shape's own attribute wins — it only reaches inheriting
|
|
373
|
+
// children such as the block's text.
|
|
374
|
+
// Every wire carries data-from / data-to with its net endpoints, so
|
|
375
|
+
// '.ldl-wire[data-to="TRIP"]' colours the wire feeding the TRIP output.
|
|
376
|
+
//
|
|
377
|
+
// What each rule in the STYLE block below targets:
|
|
378
|
+
// #G1 path -> the AND gate's body <path> (fill).
|
|
379
|
+
// #P rect -> the block's body <rect> (fill + stroke).
|
|
380
|
+
// .ldl-wire[data-to="TRIP"] -> the signal wire whose destination net is TRIP.
|
|
381
|
+
// .ldl-wire[data-to="ALARM"] -> the signal wire whose destination net is ALARM.
|
|
382
|
+
// Note: a bare "#TRIP" matches only the output node's short id-stub, NOT the wire that
|
|
383
|
+
// feeds it — colour the wire itself via its data-to net.
|
|
384
|
+
//
|
|
385
|
+
// Toggle the Dots toolbar button to hide junction tie-points (item 11).
|
|
386
|
+
// Use the PNG dropdown for selectable-DPI raster export (item 13).
|
|
387
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
388
|
+
OPTION HIDE_JUNCTIONS = FALSE
|
|
389
|
+
|
|
390
|
+
TRIP = AND#G1(START, INHIBIT, FB#P(BAND=IA, EARTH=IN, EN=BLK).TRIP)
|
|
391
|
+
ALARM = FB#P.ALARM
|
|
392
|
+
|
|
393
|
+
G1.Name = "Trip AND"
|
|
394
|
+
P.Name = "Feeder Protection"
|
|
395
|
+
P.Description = "SEL-751A"
|
|
396
|
+
|
|
397
|
+
STYLE
|
|
398
|
+
#G1 path { fill: #ff003333; }
|
|
399
|
+
#P rect { fill: #e8f5e9; stroke: #1b5e20; }
|
|
400
|
+
.ldl-wire[data-to="TRIP"] { stroke: #c62828; stroke-width: 4; }
|
|
401
|
+
.ldl-wire[data-to="ALARM"] { stroke: #ef6c00; }
|
|
402
|
+
END STYLE`,
|
|
403
|
+
'Hidden Dots': `// OPTION HIDE_JUNCTIONS hides every junction dot on the diagram (the toolbar
|
|
404
|
+
// Dots button flips the same flag on top of the source option). Useful when
|
|
405
|
+
// printing or when the wiring/junction pattern is visually noisy.
|
|
406
|
+
OPTION HIDE_JUNCTIONS = TRUE
|
|
407
|
+
OPTION INVERSION = BUBBLES
|
|
408
|
+
A = A1 OR A2 OR A3
|
|
409
|
+
B = B1 AND B2 AND B3
|
|
410
|
+
O = A AND B AND C`,
|
|
411
|
+
'Reactor Scram Matrix': `// Real-world scram matrix: a large (18-input) OR gathers every reactor
|
|
412
|
+
// protection trip, an SR latch seals it in, and the result fans out to
|
|
413
|
+
// annunciators, the plant computer, transient records and rod-drive trips.
|
|
414
|
+
// Exercises large fan-in and reconvergence (SCR01 drives several outputs and
|
|
415
|
+
// the RTS OR; RTS drives more).
|
|
416
|
+
OPTION OUTPUT_ORDER = AUTO
|
|
417
|
+
|
|
418
|
+
E24U2T1.Name = "NEUTRON FLUX HIGH"
|
|
419
|
+
EWT.Name = "CORE OUTLET TEMP HIGH (RT301)"
|
|
420
|
+
E50TP2.Name = "RCS PRESSURE HIGH (PT502)"
|
|
421
|
+
EPSV17.Name = "RCS PRESSURE LOW (PT517)"
|
|
422
|
+
E51T01.Name = "SG-1 LEVEL LOW (LT101)"
|
|
423
|
+
E51T02.Name = "SG-2 LEVEL LOW (LT102)"
|
|
424
|
+
E593P1T.Name = "CONTAINMENT PRESS HI-1 (PT591)"
|
|
425
|
+
E593P2T.Name = "CONTAINMENT PRESS HI-2 (PT592)"
|
|
426
|
+
EBUCH.Name = "RCP UNDERSPEED (ST307)"
|
|
427
|
+
ETP.Name = "STEAM LINE PRESSURE"
|
|
428
|
+
EREFF2.Name = "REACTOR COOLANT FLOW LOW (FT2)"
|
|
429
|
+
E87Z2.Name = "PRESSURISER LEVEL HIGH (LT2)"
|
|
430
|
+
E87Q2.Name = "FEED/STEAM MISMATCH (FQ2)"
|
|
431
|
+
EIN202.Name = "LOSS OF COOLANT FLOW (IN202)"
|
|
432
|
+
EPLQ_X_IT.Name = "MANUAL SCRAM A (IN408)"
|
|
433
|
+
EPLQ_X_CBF.Name = "MANUAL SCRAM B (IN407)"
|
|
434
|
+
EGCB_CBF.Name = "TURBINE TRIP (TTFS)"
|
|
435
|
+
SWA_CON_MON.Name = "DIV A DC BUS MONITOR (PSV20)"
|
|
436
|
+
SWB_CON_MON.Name = "DIV B DC BUS MONITOR (PSV20)"
|
|
437
|
+
CTR.Name = "From Div II Scram (IN203)"
|
|
438
|
+
|
|
439
|
+
TR01 = E24U2T1 OR EWT OR E50TP2 OR EPSV17 OR E51T01 OR E51T02 OR (E593P1T OR E593P2T) OR EBUCH OR ETP OR EREFF2 OR E87Z2 OR E87Q2 OR EIN202 OR EPLQ_X_IT OR EPLQ_X_CBF OR EGCB_CBF OR SWA_CON_MON OR SWB_CON_MON
|
|
440
|
+
TR01.Name = "REACTOR SCRAM"
|
|
441
|
+
|
|
442
|
+
PSV01 = SR(TR01, ULTR01).Q OR CTR
|
|
443
|
+
TRS = PSV01 OR TRIP3 OR TRIP5
|
|
444
|
+
|
|
445
|
+
LED_01 = PSV01
|
|
446
|
+
LED_01.Out = True
|
|
447
|
+
LED_01.Name = "TO SCRAM LED"
|
|
448
|
+
|
|
449
|
+
ICMS_PSV01 = PSV01
|
|
450
|
+
ICMS_PSV01.Out = True
|
|
451
|
+
ICMS_PSV01.Name = "Reactor Scram [TO SPDS] & [TO PPC]"
|
|
452
|
+
|
|
453
|
+
OCT = PSV01
|
|
454
|
+
OCT.Out = True
|
|
455
|
+
OCT.Name = "To Transient Recorder (OUT204)"
|
|
456
|
+
|
|
457
|
+
SWA_CB_TRIP_1 = PSV01
|
|
458
|
+
SWA_CB_TRIP_1.Name = "TO DIV A ROD DRIVE TRIP"
|
|
459
|
+
|
|
460
|
+
SWB_CB_TRIP_1 = PSV01
|
|
461
|
+
SWB_CB_TRIP_1.Name = "TO DIV B ROD DRIVE TRIP"
|
|
462
|
+
|
|
463
|
+
GCB_TRIP_1 = TRS
|
|
464
|
+
GCB_TRIP_1.Name = "TO REACTOR TRIP BKR (OUT401)"
|
|
465
|
+
|
|
466
|
+
GCB_CBF = TRS
|
|
467
|
+
GCB_CBF.Name = "TO SCRAM BKR FAIL"`,
|
|
468
|
+
};
|
|
469
|
+
export const EXAMPLE_NAMES = Object.keys(EXAMPLES);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rasterise an SVG string to a PNG Blob at `scale`× the diagram's intrinsic pixel size (default 2×).
|
|
3
|
+
* Browser only.
|
|
4
|
+
*/
|
|
5
|
+
export declare function svgToPngBlob(svg: string, scale?: number): Promise<Blob>;
|
|
6
|
+
/**
|
|
7
|
+
* Rasterise an SVG string into a single-page PDF Blob sized to the diagram (orientation follows the
|
|
8
|
+
* aspect ratio). Loads `jspdf` on demand. Browser only.
|
|
9
|
+
*/
|
|
10
|
+
export declare function svgToPdfBlob(svg: string, scale?: number): Promise<Blob>;
|