You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Each toolbox provides a set of block types (and optionally event types) that PathView discovers, extracts metadata from, and presents in the Block Library panel. The integration requires three things:
44
44
45
-
1.**A Python package** with blocks that implement the `Block.info()` classmethod (and optionally an `events` submodule).
45
+
1.**A Python package** with block classes that inherit from PathSim's base `Block` class (and optionally an `events` submodule).
46
46
2.**A config directory** at `scripts/config/<toolbox-name>/` containing `blocks.json` (and optionally `events.json`).
47
47
3.**An entry** in `scripts/config/requirements-pyodide.txt` so both the Pyodide and Flask backends install the package at runtime.
48
48
@@ -54,32 +54,45 @@ The extraction pipeline (`npm run extract`) reads the config files, imports the
54
54
55
55
### 2.1 Block Classes
56
56
57
-
The toolbox Python package must have an importable module containing block classes. For example, a package named `pathsim-controls` (installed as `pathsim_controls`) would expose blocks via `pathsim_controls.blocks`.
57
+
The toolbox Python package must have an importable module containing block classes that inherit from PathSim's base `Block` class. For example, a package named `pathsim-controls` (installed as `pathsim_controls`) would expose blocks via `pathsim_controls.blocks`.
58
58
59
-
Each block class must implement the `info()` classmethod returning a dict with the following keys:
59
+
The `info()` classmethod is **inherited from the base `Block` class** — individual block classes do **not** need to implement it. The base implementation automatically:
60
+
61
+
1. Reads `cls.input_port_labels` and `cls.output_port_labels` class attributes
62
+
2. Introspects `cls.__init__` via `inspect.signature()` to discover parameters and their defaults
63
+
3. Uses `cls.__doc__` as the description
64
+
65
+
Block classes only need to:
66
+
-**Set class attributes**`input_port_labels` and `output_port_labels` (defaults to `None` if not set, meaning variable ports)
67
+
-**Define `__init__` parameters** with appropriate defaults
68
+
-**Write a docstring** (RST-formatted, used for documentation)
60
69
61
70
```python
62
-
@classmethod
63
-
definfo(cls):
64
-
return {
65
-
"input_port_labels": {"x": 0, "y": 1}, # or None or {}
66
-
"output_port_labels": {"out": 0}, # or None or {}
67
-
"parameters": {
68
-
"gain": {"default": 1.0},
69
-
"mode": {"default": "linear"}
70
-
},
71
-
"description": "A proportional controller block."
72
-
}
71
+
classMyBlock(Block):
72
+
"""A proportional controller block.
73
+
74
+
:param gain: Proportional gain factor.
75
+
:param mode: Operating mode.
76
+
"""
77
+
78
+
input_port_labels = {"x": 0, "y": 1}
79
+
output_port_labels = {"out": 0}
80
+
81
+
def__init__(self, gain=1.0, mode="linear"):
82
+
super().__init__()
83
+
# ... block initialization
73
84
```
74
85
86
+
The inherited `info()` method returns a dict with the following keys:
87
+
75
88
| Key | Type | Description |
76
89
|-----|------|-------------|
77
90
|`input_port_labels`|`dict`, `None`, or `{}`| Defines input port names and indices. See [Port Label Semantics](#91-port-label-semantics). |
78
91
|`output_port_labels`|`dict`, `None`, or `{}`| Defines output port names and indices. See [Port Label Semantics](#91-port-label-semantics). |
79
-
|`parameters`|`dict`| Map of parameter names to dicts containing at minimum a `"default"` key. |
92
+
|`parameters`|`dict`| Map of parameter names to dicts containing at minimum a `"default"` key. Derived from `__init__` signature. |
80
93
|`description`|`str`| RST-formatted docstring. The first line/sentence is used as the short description. |
81
94
82
-
If a block does not implement `info()`, the extractor falls back to `__init__` signature introspection, but this is less reliable. All new toolbox blocks should implement `info()`.
95
+
If a block does not inherit from `Block` (or the `info()` call fails), the extractor falls back to direct `__init__` signature introspection.
83
96
84
97
### 2.2 Event Classes
85
98
@@ -421,22 +434,43 @@ This file also exports `PYODIDE_VERSION`, `PYODIDE_CDN_URL`, `PYODIDE_PRELOAD`,
421
434
422
435
## 9. Block Metadata Contract (Block.info())
423
436
424
-
The `info()` classmethod is the primary interface between a toolbox's Python code and PathView's extraction pipeline.
437
+
The `info()` classmethod is the primary interface between a toolbox's Python code and PathView's extraction pipeline. It is **defined on the base `Block` class** and inherited by all subclasses — block authors do not need to override it.
438
+
439
+
The base implementation (in `pathsim.blocks._block.Block`) works as follows:
425
440
426
441
```python
427
442
@classmethod
443
+
@lru_cache()
428
444
definfo(cls):
445
+
sig = inspect.signature(cls.__init__)
446
+
params = {
447
+
name: {"default": Noneif param.default is inspect.Parameter.empty else param.default}
448
+
for name, param in sig.parameters.items()
449
+
if name notin ("self", "kwargs", "args")
450
+
}
429
451
return {
430
-
"input_port_labels": {"x": 0, "y": 1},
431
-
"output_port_labels": {"out": 0},
432
-
"parameters": {
433
-
"gain": {"default": 1.0},
434
-
"mode": {"default": "linear"}
435
-
},
436
-
"description": "A proportional controller block."
452
+
"type": cls.__name__,
453
+
"description": cls.__doc__,
454
+
"input_port_labels": cls.input_port_labels,
455
+
"output_port_labels": cls.output_port_labels,
456
+
"parameters": params,
437
457
}
438
458
```
439
459
460
+
Block classes control their metadata by setting class attributes and `__init__` parameters:
The values of `input_port_labels` and `output_port_labels` have precise semantics that control both extraction and UI behavior:
@@ -553,11 +587,11 @@ The extraction script needs to import the package. Install it in your developmen
553
587
pip install pathsim-controls
554
588
```
555
589
556
-
Verify the blocks module is importable and `info()`works:
590
+
Verify the blocks module is importable and the inherited `info()`returns correct metadata:
557
591
558
592
```python
559
593
from pathsim_controls.blocks import PIDController
560
-
print(PIDController.info())
594
+
print(PIDController.info())# inherited from Block base class
561
595
```
562
596
563
597
### Step 4: Run the extraction
@@ -686,7 +720,7 @@ Categories not in the map use the `default` shape.
686
720
687
721
## Notes for Toolbox Authors
688
722
689
-
1.**`info()` is the contract.**Implement it on every block class. The extractor falls back to `__init__` introspection, but `info()` gives you explicit control over port definitions and parameter metadata.
723
+
1.**`info()` is inherited.**The base `Block` class provides the `info()` classmethod — you do not need to override it. It automatically discovers ports from class attributes (`input_port_labels`, `output_port_labels`) and parameters from the `__init__` signature. Set these correctly and the extraction pipeline will pick them up.
690
724
691
725
2.**Port labels must be consistent.** The index values in port label dicts must be zero-based and contiguous. The extractor sorts by index, so `{"y": 1, "x": 0}` correctly produces `["x", "y"]`.
0 commit comments