docs(wiki): full 5-page wiki matching the rotatingMachine reference format
Replaces the prior stub/partial wiki with a Home + Reference-{Architecture,
Contracts,Examples,Limitations} + _Sidebar structure. Topic-contract and
data-model sections wrapped in AUTOGEN markers for the future wiki-gen tool.
Source-vs-spec contradictions surfaced and flagged inline (not silently
fixed). Pending-review notes mark sections that need a full node review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
301
wiki/Home.md
301
wiki/Home.md
@@ -1,231 +1,146 @@
|
||||
# diffuser
|
||||
|
||||
> **Reflects code as of `8cc02ee` · regenerated `2026-05-11` via `npm run wiki:all`**
|
||||
> If this banner is stale, the page may be out of date. Treat as informative, not authoritative.
|
||||
  
|
||||
|
||||
## 1. What this node is
|
||||
A `diffuser` models a single aeration-diffuser zone — the gas-side dynamics of a fine-bubble grid sitting under a reactor's water column. Given a header pressure, water height, alpha factor, element count and incoming air flow, it normalises the air to Nm³/h, interpolates a supplier OTR curve (oxygen transfer rate vs specific flux) plus a ΔP curve, and emits oxygen-transfer power (kg O₂/h), efficiency, per-element flow, and a reactor-zone OTR. Used as a leaf Equipment Module under a `reactor` (or any aeration train).
|
||||
|
||||
**diffuser** models an aeration-diffuser zone. Given header pressure, water-column height, alpha factor, element count and airflow, it interpolates a supplier OTR curve, normalises airflow to Nm³/h, and emits oxygen-transfer rate plus a reactor-zone OTR for the downstream parent. Used as a leaf Equipment Module under a `reactor`.
|
||||
> [!NOTE]
|
||||
> Pending full node review (2026-05). Content reflects `CONTRACT.md`, `src/commands/index.js`, `src/specificClass.js` and `generalFunctions/src/configs/diffuser.json` only. Test scaffolding under `test/basic/`, `test/integration/`, `test/edge/` is still placeholder-level — a domain-test pass remains TODO.
|
||||
|
||||
## 2. Position in the platform
|
||||
---
|
||||
|
||||
## At a glance
|
||||
|
||||
| Thing | Value |
|
||||
|:---|:---|
|
||||
| What it represents | One aeration-diffuser zone — the gas-side OTR + ΔP model of a fine-bubble grid |
|
||||
| S88 level | Equipment Module |
|
||||
| Use it when | You have a supplier OTR / ΔP curve and need oxygen-transfer + head-loss telemetry from a measured air flow |
|
||||
| Don't use it for | Coarse-bubble / jet aeration without a fine-bubble curve, or when the blower already publishes OTR (you'd duplicate the calc) |
|
||||
| Children it accepts | None — diffuser is a leaf |
|
||||
| Parents it talks to | `reactor` (typical), or any node that consumes `child.register` from Port 2 |
|
||||
|
||||
---
|
||||
|
||||
## How it fits
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
src[blower / MGC / dashboard]:::unit -->|data.flow| diff[diffuser<br/>Equipment]:::equip
|
||||
setters[dashboard setters]:::ctrl -->|set.density / set.water-height /<br/>set.elements / set.alfa-factor /<br/>set.header-pressure| diff
|
||||
diff -->|child.register| reactor[reactor<br/>Unit]:::unit
|
||||
blower[blower / MGC /<br/>dashboard slider]:::unit -->|data.flow<br/>Nm³/h| diff[diffuser<br/>Equipment]:::equip
|
||||
setters[dashboard setters /<br/>setup tab]:::ctrl -->|set.density<br/>set.water-height<br/>set.header-pressure<br/>set.elements<br/>set.alfa-factor| diff
|
||||
diff -->|child.register<br/>positionVsParent=atEquipment| reactor[reactor / parent<br/>Unit]:::unit
|
||||
diff -.->|Port 0: oOtr,<br/>oKgo2H, oZoneOtr,<br/>efficiency, slope| reactor
|
||||
classDef unit fill:#50a8d9,color:#000
|
||||
classDef equip fill:#86bbdd,color:#000
|
||||
classDef ctrl fill:#a9daee,color:#000
|
||||
```
|
||||
|
||||
S88 colours: Unit `#50a8d9`, Equipment `#86bbdd`, Control Module `#a9daee`. Source of truth: `.claude/rules/node-red-flow-layout.md`.
|
||||
S88 colours are anchored in `.claude/rules/node-red-flow-layout.md`. Per the layout rule, diffuser lives on lane **L3** of the Process Plant tab, wrapped in a `#86bbdd` Node-RED group.
|
||||
|
||||
## 3. Capability matrix
|
||||
---
|
||||
|
||||
| Capability | Status | Notes |
|
||||
|---|---|---|
|
||||
| Supplier OTR curve interpolation | ✅ | Density-keyed; falls back to single key when only one available. |
|
||||
| Air-density correction (header + atm) | ✅ | `_calcAirDensityMbar` per ideal-gas mix. |
|
||||
| Per-element flow tracking | ✅ | `o_flowElement = nFlow / nElements`. |
|
||||
| Static head loss from water column | ✅ | `_heightToPressureMbar`. |
|
||||
| Warning / alarm bands on flow-per-element | ✅ | Hysteresis 2 % (warn) / 10 % (alarm). |
|
||||
| Reactor-zone OTR for parent | ✅ | `oZoneOtr` derived from `diffuser.zoneVolume`. |
|
||||
| Idle handling at flow ≤ 0 | ✅ | Resets all derived outputs to zero. |
|
||||
| Typed `MeasurementContainer` emission | ❌ | All output flows via `getOutput()`; no typed series yet. |
|
||||
## Try it — 3-minute demo
|
||||
|
||||
## 4. Code map
|
||||
Import the basic example flow, deploy, and drive a single diffuser zone through the OTR curve.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph nodeRED["nodeClass.js — adapter (BaseNodeAdapter)"]
|
||||
nc["buildDomainConfig()<br/>static DomainClass, commands"]
|
||||
end
|
||||
subgraph domain["specificClass.js — orchestrator (BaseDomain)"]
|
||||
sc["Diffuser.configure()<br/>loads supplier specs<br/>setters → _recalculate()"]
|
||||
end
|
||||
subgraph concerns["src/ concern modules"]
|
||||
cmds["commands/<br/>topic registry + handlers"]
|
||||
end
|
||||
nc --> sc
|
||||
nc --> cmds
|
||||
```bash
|
||||
curl -X POST -H 'Content-Type: application/json' \
|
||||
--data @nodes/diffuser/examples/basic.flow.json \
|
||||
http://localhost:1880/flow
|
||||
```
|
||||
|
||||
| Module | Owns | Read first if you're changing… |
|
||||
|---|---|---|
|
||||
| `commands/` | Input-topic registry + per-topic handlers | Topic naming, payload validation. |
|
||||
| `specificClass.js` (single file) | Setters, OTR/ΔP curve interpolation, alarms, output composition | Anything domain-side. |
|
||||
> [!NOTE]
|
||||
> Example flows currently shipped: `examples/basic.flow.json`, `examples/integration.flow.json`, `examples/edge.flow.json`. The `examples/README.md` is a one-line placeholder ("Placeholder structure"); a proper per-tier README following the rotatingMachine template is TODO.
|
||||
|
||||
The diffuser was a small node so the P6.4 refactor did not split it into per-concern directories. Future work may extract `curves/` and `alarms/` if the file grows past ~250 lines.
|
||||
What to send after deploy (each inject maps one-to-one to a topic in [Reference — Contracts](Reference-Contracts#topic-contract)):
|
||||
|
||||
## 5. Topic contract
|
||||
1. `set.water-height = 5` — sets a 5 m column above the diffuser. Static head pressure is recomputed; `oPLoss` updates.
|
||||
2. `set.elements = 100` — declares 100 elements in this zone. Per-element flow + specific flux now scale by this denominator.
|
||||
3. `set.density = 15` — bottom-coverage percentage; selects which curve family is interpolated.
|
||||
4. `set.header-pressure = 600` — gauge pressure above atmospheric (mbar). Air-density correction kicks in.
|
||||
5. `set.alfa-factor = 0.7` — the α correction used in the kg O₂/h calculation.
|
||||
6. `data.flow = 200` — push 200 Nm³/h into the model. Watch Port 0: `oOtr`, `oKgo2H`, `oFluxPerM2`, `efficiency`, `slope` all populate. Setting `data.flow = 0` flips `idle` back to true and resets the derived outputs.
|
||||
|
||||
> **Auto-generated** from `src/commands/index.js`. Do NOT hand-edit between the markers. Re-run `npm run wiki:contract`.
|
||||
> [!IMPORTANT]
|
||||
> **GIF needed.** Demo recording of steps 1–6 with the live status badge. Save as `wiki/_partial-gifs/diffuser/01-basic-demo.gif`, target ≤ 1 MB after `gifsicle -O3 --lossy=80`.
|
||||
|
||||
<!-- BEGIN AUTOGEN: topic-contract -->
|
||||
---
|
||||
|
||||
| Canonical topic | Aliases | Payload | Unit | Effect |
|
||||
|---|---|---|---|---|
|
||||
| `data.flow` | `air_flow` | `number` | `volumeFlowRate` (default `m3/h`) | Push the measured air flow into the diffuser model. |
|
||||
| `set.density` | `density` | `number` | — | Update the air density used in OTR / SOTR calculations. |
|
||||
| `set.water-height` | `height_water` | `number` | — | Update the water column height above the diffusers (m). |
|
||||
| `set.header-pressure` | `header_pressure` | `number` | — | Update the header (supply) pressure feeding the diffusers (mbar). |
|
||||
| `set.elements` | `elements` | `number` | — | Update the count of active diffuser elements. |
|
||||
| `set.alfa-factor` | `alfaFactor` | `number` | — | Update the alfa factor used in oxygen-transfer correction. |
|
||||
## The six things you'll send
|
||||
|
||||
<!-- END AUTOGEN: topic-contract -->
|
||||
| Topic | Aliases | Payload | What it does |
|
||||
|:---|:---|:---|:---|
|
||||
| `data.flow` | `air_flow` | `number` — Nm³/h | Pushes the measured air flow into the model. Clamped to ≥ 0. Triggers a full recompute. |
|
||||
| `set.density` | `density` | `number` — bottom-coverage % | Selects the curve family used for OTR interpolation. Curves multi-keyed by coverage get linearly interpolated; single-key curves are clamped. |
|
||||
| `set.water-height` | `height_water` | `number` — m | Sets the water column above the diffuser. Recomputes static head + total ΔP. Clamped to ≥ 0. |
|
||||
| `set.header-pressure` | `header_pressure` | `number` — mbar gauge | Header (supply) pressure above atmospheric. Feeds air-density correction. |
|
||||
| `set.elements` | `elements` | `number` — integer > 0 | Active element count. Drives per-element flow + total membrane area used for specific flux. |
|
||||
| `set.alfa-factor` | `alfaFactor` | `number` (typically 0–1) | Alpha correction used in the kg O₂/h calculation. |
|
||||
|
||||
## 6. Child registration
|
||||
Aliases log a one-time deprecation warning the first time they fire. There are no query topics today (the entire state is on Port 0).
|
||||
|
||||
diffuser is a leaf node — it accepts no children. It registers itself with its upstream parent (typically a reactor) at startup via the standard Port-2 handshake.
|
||||
---
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
diff[diffuser]:::equip -->|child.register payload=node.id<br/>positionVsParent=atEquipment| reactor[reactor / parent]:::unit
|
||||
classDef equip fill:#86bbdd,color:#000
|
||||
classDef unit fill:#50a8d9,color:#000
|
||||
## What you'll see come out
|
||||
|
||||
Sample Port 0 message (delta-compressed, while operational with flow = 200 Nm³/h, 100 elements, water height 5 m):
|
||||
|
||||
```json
|
||||
{
|
||||
"topic": "diffuser_1",
|
||||
"payload": {
|
||||
"iFlow": 200,
|
||||
"iPressure": 600,
|
||||
"iMWater": 5,
|
||||
"nFlow": 218.42,
|
||||
"oFlowElement": 2.18,
|
||||
"oFluxPerM2": 12.13,
|
||||
"oOtr": 16.4,
|
||||
"oPLoss": 540.7,
|
||||
"oKgo2H": 12.51,
|
||||
"oZoneOtr": 6.0,
|
||||
"efficiency": 73.2,
|
||||
"slope": 0.044,
|
||||
"idle": false,
|
||||
"warning": [],
|
||||
"alarm": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Direction | Counterparty | Side-effect |
|
||||
|---|---|---|
|
||||
| outbound at startup | upstream reactor | sends `child.register` on Port 2 with `positionVsParent` default `atEquipment` |
|
||||
| inbound | — | none accepted |
|
||||
> [!NOTE]
|
||||
> The numbers above are illustrative — the exact values depend on the loaded supplier curve (`gva-elastox-r` by default). Run the basic example flow to see real values for your asset.
|
||||
|
||||
## 7. Lifecycle — what one event does
|
||||
| Field | Meaning |
|
||||
|:---|:---|
|
||||
| `iFlow` / `iPressure` / `iMWater` | Echo of the current setter inputs (Nm³/h, mbar, m). |
|
||||
| `nFlow` | Air flow normalised to standard conditions (Nm³/h, T=20 °C, p=1.01325 bar, RH=0). |
|
||||
| `oFlowElement` | `nFlow / elements` — flow per element. |
|
||||
| `oFluxPerM2` | Specific flux through the membrane (Nm³/(h·m²)) — the canonical x-axis of every supplier curve. |
|
||||
| `oOtr` | Interpolated oxygen transfer rate (g O₂ / Nm³). |
|
||||
| `oPLoss` | Total head loss in mbar — static head from the water column plus diffuser ΔP. |
|
||||
| `oKgo2H` | kg O₂ per hour at the current operating point. Uses α-factor and water height. |
|
||||
| `oZoneOtr` | Reactor-zone OTR in kg O₂ / m³ / day. Computed from `oKgo2H` against `diffuser.zoneVolume`; zero when zone volume is unset. |
|
||||
| `efficiency` | Combined OTR / ΔP efficiency (0–100). High OTR + low ΔP → high score. |
|
||||
| `slope` | Local OTR-vs-flux slope (g O₂/Nm³ per Nm³/(h·m²)). Useful as a "we're near the curve knee" indicator. |
|
||||
| `idle` | `true` when `iFlow ≤ 0` — derived predicates only, no FSM. |
|
||||
| `warning` / `alarm` | String arrays describing flow-per-element band excursions (`±2 %` warn / `±10 %` alarm hysteresis, hardcoded). |
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant src as upstream source
|
||||
participant diff as diffuser
|
||||
participant curve as supplier specs
|
||||
participant out as Port-0
|
||||
---
|
||||
|
||||
src->>diff: data.flow (Nm³/h)
|
||||
diff->>diff: setFlow → _recalculate
|
||||
alt flow ≤ 0
|
||||
diff->>diff: idle = true, reset derived outputs
|
||||
else flow > 0
|
||||
diff->>diff: air-density correction (atm + header)
|
||||
diff->>curve: interpolate OTR by density + flow/element
|
||||
diff->>curve: interpolate ΔP curve by flow/element
|
||||
diff->>diff: kg O₂/h, combined efficiency, slope
|
||||
diff->>diff: _checkLimits (warn / alarm bands)
|
||||
end
|
||||
diff->>diff: notifyOutputChanged()
|
||||
diff->>out: msg{topic, payload (delta-compressed)}
|
||||
```
|
||||
## Not the same shape as rotatingMachine / measurement
|
||||
|
||||
## 8. Data model — `getOutput()`
|
||||
Where rotatingMachine emits per-measurement keys (`<type>.<variant>.<position>.<childId>`), diffuser uses a flat camelCase schema (`oOtr`, `oKgo2H`, `oFlowElement`, …). The diffuser does **not** currently publish typed `MeasurementContainer` series — parents must consume Port 0 directly. Promoting `oOtr` / `oZoneOtr` to typed measurement series is tracked in [Reference — Limitations](Reference-Limitations#known-limitations).
|
||||
|
||||
What lands on Port 0. Composed in `Diffuser.getOutput()`, then delta-compressed by `outputUtils.formatMsg`.
|
||||
---
|
||||
|
||||
<!-- BEGIN AUTOGEN: data-model -->
|
||||
## Need more?
|
||||
|
||||
| Key | Type | Unit | Sample |
|
||||
|---|---|---|---|
|
||||
| `alarm` | array | — | `[…]` |
|
||||
| `efficiency` | number | — | `0` |
|
||||
| `iFlow` | number | — | `0` |
|
||||
| `iMWater` | number | — | `0` |
|
||||
| `iPressure` | number | — | `0` |
|
||||
| `idle` | boolean | — | `true` |
|
||||
| `nFlow` | number | — | `0` |
|
||||
| `oFlowElement` | number | — | `0` |
|
||||
| `oKgo2H` | number | — | `0` |
|
||||
| `oOtr` | number | — | `0` |
|
||||
| `oPLoss` | number | — | `0` |
|
||||
| `oZoneOtr` | number | — | `0` |
|
||||
| `slope` | number | — | `0` |
|
||||
| `warning` | array | — | `[…]` |
|
||||
| Page | What you'll find |
|
||||
|:---|:---|
|
||||
| [Reference — Contracts](Reference-Contracts) | Full topic contract, config schema, parent registration handshake |
|
||||
| [Reference — Architecture](Reference-Architecture) | Code map, OTR / ΔP pipeline, idle behaviour, output ports |
|
||||
| [Reference — Examples](Reference-Examples) | Shipped example flows + debug recipes |
|
||||
| [Reference — Limitations](Reference-Limitations) | When not to use, known limitations, open questions |
|
||||
|
||||
<!-- END AUTOGEN: data-model -->
|
||||
|
||||
`oZoneOtr` is `kg O₂ / m³ / day`; it is `0` when `diffuser.zoneVolume` is unset.
|
||||
|
||||
## 9. Configuration — editor form ↔ config keys
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph editor["Node-RED editor form"]
|
||||
f0[Zone number]
|
||||
f1[Element count]
|
||||
f2[Diffuser density]
|
||||
f3[Water height]
|
||||
f4[Header pressure]
|
||||
f5[Alpha factor]
|
||||
f6[Zone volume]
|
||||
end
|
||||
subgraph config["Domain config slice"]
|
||||
c0[diffuser.number]
|
||||
c1[diffuser.elements]
|
||||
c2[diffuser.density]
|
||||
c3[diffuser.waterHeight]
|
||||
c4[diffuser.headerPressure]
|
||||
c5[diffuser.alfaFactor]
|
||||
c6[diffuser.zoneVolume]
|
||||
c7["diffuser.localAtmPressure (hidden)"]
|
||||
c8["diffuser.waterDensity (hidden)"]
|
||||
end
|
||||
f0 --> c0
|
||||
f1 --> c1
|
||||
f2 --> c2
|
||||
f3 --> c3
|
||||
f4 --> c4
|
||||
f5 --> c5
|
||||
f6 --> c6
|
||||
```
|
||||
|
||||
| Form field | Config key | Default | Range | Where used |
|
||||
|---|---|---|---|---|
|
||||
| Zone number | `diffuser.number` | `1` | int ≥ 1 | node label (`name_N`) |
|
||||
| Element count | `diffuser.elements` | `1` | int ≥ 1 | per-element flow |
|
||||
| Diffuser density | `diffuser.density` | `2.4` | > 0 | OTR curve key |
|
||||
| Water height | `diffuser.waterHeight` | `0` | ≥ 0 (m) | static head + kg O₂/h |
|
||||
| Header pressure | `diffuser.headerPressure` | `0` | ≥ 0 (mbar) | air density correction |
|
||||
| Alpha factor | `diffuser.alfaFactor` | `0.7` | 0–1 | oxygen-transfer correction |
|
||||
| _(hidden default)_ | `diffuser.localAtmPressure` | `1013.25` | > 0 (mbar) | density baseline |
|
||||
| _(hidden default)_ | `diffuser.waterDensity` | `997` | > 0 (kg/m³) | static head |
|
||||
| Zone volume | `diffuser.zoneVolume` | `0` | ≥ 0 (m³) | `oZoneOtr` |
|
||||
|
||||
## 10. State chart
|
||||
|
||||
Skipped — diffuser is stateless. Every input setter recomputes the full output snapshot; there are no transitions to track. The `idle` flag is a derived predicate (`i_flow ≤ 0`), not a state.
|
||||
|
||||
## 11. Examples
|
||||
|
||||
| Tier | File | What it shows | Mandatory? |
|
||||
|---|---|---|---|
|
||||
| Basic | `examples/01-Basic.flow.json` | Inject `data.flow` + dashboard, no parent | ✅ |
|
||||
| Integration | `examples/02-Integration.flow.json` | diffuser registered under a reactor zone | ✅ |
|
||||
| Dashboard | `examples/03-Dashboard.flow.json` | Live FlowFuse charts (OTR, kg O₂/h, efficiency) | ⭕ |
|
||||
|
||||
Screenshots under `wiki/_partial-screenshots/diffuser/` when produced. Docker compose snippet under `examples/README.md`.
|
||||
|
||||
## 12. Debug recipes
|
||||
|
||||
| Symptom | First thing to check | Where to look |
|
||||
|---|---|---|
|
||||
| `oOtr` stuck at zero | `i_flow` is zero or negative → `idle = true`. | `_recalculate` early-return |
|
||||
| Warning / alarm always firing | Flow-per-element outside curve `minX` / `maxX` ± hysteresis. | `_checkLimits` |
|
||||
| `oZoneOtr` is zero despite valid OTR | `diffuser.zoneVolume` is unset or non-positive. | `getReactorOtr` |
|
||||
| `nFlow` differs from `iFlow` at non-zero flow | Air-density correction — header pressure or atm differ from reference. | `_calcAirDensityMbar` |
|
||||
| `efficiency` flat at 0 | OTR or ΔP curve span is zero in the operating band. | `_combineEff` |
|
||||
|
||||
> Never ship `enableLog: 'debug'` in a demo — fills the container log within seconds and obscures real errors. Use only for live debugging.
|
||||
|
||||
## 13. When you would NOT use this node
|
||||
|
||||
- Use diffuser for a **fine-bubble aeration zone** with a supplier OTR curve. For coarse-bubble or jet aeration, model OTR externally.
|
||||
- Don't use diffuser when the upstream blower already publishes oxygen-transfer telemetry — diffuser duplicates the calculation.
|
||||
- Skip diffuser if you only need flow-per-element warning bands without OTR — a `measurement` node with bands is lighter.
|
||||
|
||||
## 14. Known limitations / current issues
|
||||
|
||||
| # | Issue | Tracked in |
|
||||
|---|---|---|
|
||||
| 1 | **Port-count change (Phase 6):** pre-refactor the diffuser exposed 4 outputs (process, dbase, reactor control with `topic: 'OTR'`, parent registration). The reactor-control message merged into Port 0 as `oZoneOtr`; consumers reading the dedicated control port must migrate to `payload.oZoneOtr`. No alias is provided — the shape differs (single value vs full process payload). | CONTRACT.md `## Port-count change` |
|
||||
| 2 | Supplier specs are hard-coded inside `_loadSpecs()` (GVA / ELASTOX-R). A configurable supplier registry is pending. | `specificClass.js _loadSpecs` |
|
||||
| 3 | No typed `MeasurementContainer` emission — `oOtr` / `oZoneOtr` cannot be subscribed via the generic `ChildRouter` handshake. Parents must read Port 0 messages. | CONTRACT.md `## Events emitted` |
|
||||
| 4 | Warning / alarm thresholds are fixed (2 % / 10 % hysteresis); not yet config-driven. | `configure()` literals |
|
||||
| 5 | **Node category mismatch:** `diffuser.html` registers under `category: 'wbd typical'` instead of `'EVOLV'`. All other platform nodes target `'EVOLV'` for consistent palette grouping in the editor. | `diffuser.html` line 3 |
|
||||
| 6 | **S88 colour — resolved:** `diffuser.html` already declares `color: '#86bbdd'` (Equipment Module). The WIKI_TEMPLATE §16 note listing diffuser as having "no colour set" is stale and can be removed in the next template refresh. | `diffuser.html` line 4 |
|
||||
[EVOLV master wiki](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Home) · [Topology Patterns](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topology-Patterns) · [Topic Conventions](https://gitea.wbd-rd.nl/RnD/EVOLV/wiki/Topic-Conventions)
|
||||
|
||||
Reference in New Issue
Block a user