pySimBlocks 0.1.0__py3-none-any.whl
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.
- examples/advanced/hardware/block_diagram.py +162 -0
- examples/advanced/hardware/camera.py +93 -0
- examples/advanced/hardware/gui_emio.py +231 -0
- examples/advanced/hardware/lmi.py +69 -0
- examples/advanced/hardware/main.py +57 -0
- examples/advanced/hardware/parameters.py +54 -0
- examples/advanced/qp_problem/params.py +10 -0
- examples/advanced/qp_problem/run.py +14 -0
- examples/advanced/sofa/gui/finger/Finger.py +108 -0
- examples/advanced/sofa/gui/finger/FingerController.py +53 -0
- examples/advanced/sofa/gui/finger/parameters_auto.py +22 -0
- examples/advanced/sofa/gui/sofa_plant/run.py +14 -0
- examples/advanced/sofa/python/Finger.py +107 -0
- examples/advanced/sofa/python/FingerController.py +48 -0
- examples/advanced/sofa/python/closedloop.py +64 -0
- examples/advanced/sofa/python/openloop.py +58 -0
- examples/basics/algebraic_function/g.py +7 -0
- examples/basics/algebraic_function/run.py +14 -0
- examples/basics/dc_motor/gui/run.py +14 -0
- examples/basics/dc_motor/multiple_order.py +91 -0
- examples/basics/dc_motor/openloop.py +62 -0
- examples/basics/dc_motor/pi.py +99 -0
- examples/basics/dc_motor/pid.py +118 -0
- examples/basics/dc_motor/pid_block.py +82 -0
- examples/basics/dc_motor/pid_block_vs_manual.py +143 -0
- examples/basics/external/parameters.py +4 -0
- examples/basics/external/run.py +14 -0
- examples/basics/file_sources/temp.py +16 -0
- examples/basics/observer.py +76 -0
- examples/basics/state_feedback/gui/parameters.py +9 -0
- examples/basics/state_feedback/gui/run.py +14 -0
- examples/basics/state_feedback/state_feedback.py +102 -0
- examples/basics/state_space/linear/gui/run.py +14 -0
- examples/basics/state_space/linear/ss_block.py +89 -0
- examples/basics/state_space/linear/ss_manual.py +105 -0
- examples/basics/state_space/non_linear/nlss.py +47 -0
- examples/basics/state_space/non_linear/run.py +14 -0
- examples/basics/state_space/polytope/params.py +39 -0
- examples/basics/state_space/polytope/weights.py +12 -0
- examples/comparison/simulink/convert.py +29 -0
- examples/comparison/simulink/openloop.py +127 -0
- examples/comparison/simulink/pi.py +148 -0
- examples/comparison/simulink/pid.py +154 -0
- examples/comparison/simulink/pid_backward.py +155 -0
- examples/quick_start/filter.py +35 -0
- examples/quick_start/gui/run.py +14 -0
- examples/tutorials/tutorial_1_python/main.py +99 -0
- examples/tutorials/tutorial_3_sofa/finger/Finger.py +120 -0
- examples/tutorials/tutorial_3_sofa/finger/FingerController.py +37 -0
- pySimBlocks/__init__.py +35 -0
- pySimBlocks/blocks/__init__.py +68 -0
- pySimBlocks/blocks/controllers/__init__.py +27 -0
- pySimBlocks/blocks/controllers/pid.py +250 -0
- pySimBlocks/blocks/controllers/state_feedback.py +141 -0
- pySimBlocks/blocks/interfaces/__init__.py +27 -0
- pySimBlocks/blocks/interfaces/external_input.py +107 -0
- pySimBlocks/blocks/interfaces/external_output.py +113 -0
- pySimBlocks/blocks/observers/__init__.py +23 -0
- pySimBlocks/blocks/observers/luenberger.py +175 -0
- pySimBlocks/blocks/operators/__init__.py +47 -0
- pySimBlocks/blocks/operators/algebraic_function.py +258 -0
- pySimBlocks/blocks/operators/dead_zone.py +186 -0
- pySimBlocks/blocks/operators/delay.py +254 -0
- pySimBlocks/blocks/operators/demux.py +123 -0
- pySimBlocks/blocks/operators/discrete_derivator.py +237 -0
- pySimBlocks/blocks/operators/discrete_integrator.py +265 -0
- pySimBlocks/blocks/operators/gain.py +294 -0
- pySimBlocks/blocks/operators/mux.py +131 -0
- pySimBlocks/blocks/operators/product.py +194 -0
- pySimBlocks/blocks/operators/rate_limiter.py +214 -0
- pySimBlocks/blocks/operators/saturation.py +178 -0
- pySimBlocks/blocks/operators/sum.py +188 -0
- pySimBlocks/blocks/operators/zero_order_hold.py +141 -0
- pySimBlocks/blocks/optimizers/__init__.py +23 -0
- pySimBlocks/blocks/optimizers/quadratic_program.py +326 -0
- pySimBlocks/blocks/sources/__init__.py +37 -0
- pySimBlocks/blocks/sources/chirp.py +168 -0
- pySimBlocks/blocks/sources/constant.py +82 -0
- pySimBlocks/blocks/sources/file_source.py +294 -0
- pySimBlocks/blocks/sources/function_source.py +197 -0
- pySimBlocks/blocks/sources/ramp.py +99 -0
- pySimBlocks/blocks/sources/sinusoidal.py +113 -0
- pySimBlocks/blocks/sources/step.py +138 -0
- pySimBlocks/blocks/sources/white_noise.py +107 -0
- pySimBlocks/blocks/systems/__init__.py +27 -0
- pySimBlocks/blocks/systems/linear_state_space.py +180 -0
- pySimBlocks/blocks/systems/non_linear_state_space.py +309 -0
- pySimBlocks/blocks/systems/polytopic_state_space.py +193 -0
- pySimBlocks/blocks/systems/sofa/__init__.py +30 -0
- pySimBlocks/blocks/systems/sofa/sofa_controller.py +472 -0
- pySimBlocks/blocks/systems/sofa/sofa_exchange_i_o.py +120 -0
- pySimBlocks/blocks/systems/sofa/sofa_plant.py +306 -0
- pySimBlocks/cli.py +127 -0
- pySimBlocks/core/__init__.py +32 -0
- pySimBlocks/core/block.py +167 -0
- pySimBlocks/core/block_source.py +94 -0
- pySimBlocks/core/config.py +90 -0
- pySimBlocks/core/fixed_time_manager.py +46 -0
- pySimBlocks/core/model.py +250 -0
- pySimBlocks/core/scheduler.py +30 -0
- pySimBlocks/core/simulator.py +329 -0
- pySimBlocks/core/task.py +73 -0
- pySimBlocks/docs/blocks/controllers/pid.md +78 -0
- pySimBlocks/docs/blocks/controllers/state_feedback.md +60 -0
- pySimBlocks/docs/blocks/interfaces/external_input.md +39 -0
- pySimBlocks/docs/blocks/interfaces/external_output.md +39 -0
- pySimBlocks/docs/blocks/observers/luenberger.md +70 -0
- pySimBlocks/docs/blocks/operators/algebraic_function.md +76 -0
- pySimBlocks/docs/blocks/operators/dead_zone.md +67 -0
- pySimBlocks/docs/blocks/operators/delay.md +76 -0
- pySimBlocks/docs/blocks/operators/demux.md +64 -0
- pySimBlocks/docs/blocks/operators/discrete_derivator.md +61 -0
- pySimBlocks/docs/blocks/operators/discrete_integrator.md +87 -0
- pySimBlocks/docs/blocks/operators/gain.md +97 -0
- pySimBlocks/docs/blocks/operators/mux.md +64 -0
- pySimBlocks/docs/blocks/operators/product.md +51 -0
- pySimBlocks/docs/blocks/operators/rate_limiter.md +70 -0
- pySimBlocks/docs/blocks/operators/saturation.md +62 -0
- pySimBlocks/docs/blocks/operators/sum.md +49 -0
- pySimBlocks/docs/blocks/operators/zero_order_hold.md +61 -0
- pySimBlocks/docs/blocks/optimizers/quadratic_program.md +98 -0
- pySimBlocks/docs/blocks/sources/chirp.md +102 -0
- pySimBlocks/docs/blocks/sources/constant.md +53 -0
- pySimBlocks/docs/blocks/sources/file_source.md +51 -0
- pySimBlocks/docs/blocks/sources/function_source.md +52 -0
- pySimBlocks/docs/blocks/sources/ramp.md +58 -0
- pySimBlocks/docs/blocks/sources/sinusoidal.md +60 -0
- pySimBlocks/docs/blocks/sources/step.md +63 -0
- pySimBlocks/docs/blocks/sources/white_noise.md +58 -0
- pySimBlocks/docs/blocks/systems/linear_state_space.md +66 -0
- pySimBlocks/docs/blocks/systems/non_linear_state_space.md +90 -0
- pySimBlocks/docs/blocks/systems/polytopic_state_space.md +72 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_exchange_i_o.md +63 -0
- pySimBlocks/docs/blocks/systems/sofa/sofa_plant.md +62 -0
- pySimBlocks/gui/__init__.py +20 -0
- pySimBlocks/gui/addons/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/__init__.py +19 -0
- pySimBlocks/gui/addons/sofa/sofa_dialog.py +190 -0
- pySimBlocks/gui/addons/sofa/sofa_service.py +209 -0
- pySimBlocks/gui/blocks/block_dialog_session.py +47 -0
- pySimBlocks/gui/blocks/block_meta.py +408 -0
- pySimBlocks/gui/blocks/controllers/pid.py +116 -0
- pySimBlocks/gui/blocks/controllers/state_feedback.py +80 -0
- pySimBlocks/gui/blocks/interfaces/external_input.py +64 -0
- pySimBlocks/gui/blocks/interfaces/external_output.py +63 -0
- pySimBlocks/gui/blocks/observers/luenberger.py +108 -0
- pySimBlocks/gui/blocks/operators/algebraic_function.py +232 -0
- pySimBlocks/gui/blocks/operators/dead_zone.py +79 -0
- pySimBlocks/gui/blocks/operators/delay.py +79 -0
- pySimBlocks/gui/blocks/operators/demux.py +98 -0
- pySimBlocks/gui/blocks/operators/discrete_derivator.py +67 -0
- pySimBlocks/gui/blocks/operators/discrete_integrator.py +80 -0
- pySimBlocks/gui/blocks/operators/gain.py +79 -0
- pySimBlocks/gui/blocks/operators/mux.py +100 -0
- pySimBlocks/gui/blocks/operators/product.py +109 -0
- pySimBlocks/gui/blocks/operators/rate_limiter.py +79 -0
- pySimBlocks/gui/blocks/operators/saturation.py +72 -0
- pySimBlocks/gui/blocks/operators/sum.py +91 -0
- pySimBlocks/gui/blocks/operators/zero_order_hold.py +64 -0
- pySimBlocks/gui/blocks/optimizers/quadratic_program.py +128 -0
- pySimBlocks/gui/blocks/parameter_meta.py +34 -0
- pySimBlocks/gui/blocks/port_meta.py +31 -0
- pySimBlocks/gui/blocks/sources/chirp.py +146 -0
- pySimBlocks/gui/blocks/sources/constant.py +63 -0
- pySimBlocks/gui/blocks/sources/file_source.py +196 -0
- pySimBlocks/gui/blocks/sources/function_source.py +202 -0
- pySimBlocks/gui/blocks/sources/ramp.py +73 -0
- pySimBlocks/gui/blocks/sources/sinusoidal.py +81 -0
- pySimBlocks/gui/blocks/sources/step.py +83 -0
- pySimBlocks/gui/blocks/sources/white_noise.py +70 -0
- pySimBlocks/gui/blocks/systems/linear_state_space.py +102 -0
- pySimBlocks/gui/blocks/systems/non_linear_state_space.py +233 -0
- pySimBlocks/gui/blocks/systems/polytopic_state_space.py +109 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_exchange_i_o.py +221 -0
- pySimBlocks/gui/blocks/systems/sofa/sofa_plant.py +220 -0
- pySimBlocks/gui/dialogs/__init__.py +19 -0
- pySimBlocks/gui/dialogs/block_dialog.py +125 -0
- pySimBlocks/gui/dialogs/display_yaml_dialog.py +89 -0
- pySimBlocks/gui/dialogs/help_dialog.py +39 -0
- pySimBlocks/gui/dialogs/plot_dialog.py +230 -0
- pySimBlocks/gui/dialogs/settings/plots.py +187 -0
- pySimBlocks/gui/dialogs/settings/project.py +146 -0
- pySimBlocks/gui/dialogs/settings/simulation.py +137 -0
- pySimBlocks/gui/dialogs/settings_dialog.py +88 -0
- pySimBlocks/gui/dialogs/unsaved_dialog.py +92 -0
- pySimBlocks/gui/editor.py +49 -0
- pySimBlocks/gui/graphics/__init__.py +29 -0
- pySimBlocks/gui/graphics/block_item.py +289 -0
- pySimBlocks/gui/graphics/connection_item.py +266 -0
- pySimBlocks/gui/graphics/port_item.py +153 -0
- pySimBlocks/gui/graphics/theme.py +119 -0
- pySimBlocks/gui/main_window.py +162 -0
- pySimBlocks/gui/models/__init__.py +31 -0
- pySimBlocks/gui/models/block_instance.py +114 -0
- pySimBlocks/gui/models/connection_instance.py +43 -0
- pySimBlocks/gui/models/port_instance.py +52 -0
- pySimBlocks/gui/models/project_simulation_params.py +50 -0
- pySimBlocks/gui/models/project_state.py +114 -0
- pySimBlocks/gui/project_controller.py +302 -0
- pySimBlocks/gui/services/__init__.py +20 -0
- pySimBlocks/gui/services/project_loader.py +381 -0
- pySimBlocks/gui/services/project_saver.py +68 -0
- pySimBlocks/gui/services/simulation_runner.py +66 -0
- pySimBlocks/gui/services/yaml_tools.py +261 -0
- pySimBlocks/gui/widgets/__init__.py +19 -0
- pySimBlocks/gui/widgets/block_list.py +131 -0
- pySimBlocks/gui/widgets/diagram_view.py +312 -0
- pySimBlocks/gui/widgets/toolbar_view.py +161 -0
- pySimBlocks/project/__init__.py +35 -0
- pySimBlocks/project/build_model.py +92 -0
- pySimBlocks/project/generate_run_script.py +109 -0
- pySimBlocks/project/generate_sofa_controller.py +215 -0
- pySimBlocks/project/load_project_config.py +197 -0
- pySimBlocks/project/load_simulation_config.py +166 -0
- pySimBlocks/project/load_simulator.py +45 -0
- pySimBlocks/project/plot_from_config.py +174 -0
- pySimBlocks/project/pySimBlocks_blocks_index.yaml +106 -0
- pySimBlocks/real_time/__init__.py +23 -0
- pySimBlocks/real_time/real_time_runner.py +148 -0
- pySimBlocks/tools/__init__.py +19 -0
- pySimBlocks/tools/blocks_registry.py +141 -0
- pySimBlocks/tools/generate_blocks_index.py +124 -0
- pysimblocks-0.1.0.dist-info/METADATA +180 -0
- pysimblocks-0.1.0.dist-info/RECORD +262 -0
- pysimblocks-0.1.0.dist-info/WHEEL +5 -0
- pysimblocks-0.1.0.dist-info/entry_points.txt +2 -0
- pysimblocks-0.1.0.dist-info/licenses/LICENSE.md +165 -0
- pysimblocks-0.1.0.dist-info/top_level.txt +3 -0
- tests/blocks/controllers/test_pid.py +143 -0
- tests/blocks/controllers/test_state_feedback.py +193 -0
- tests/blocks/observers/test_luenberger.py +225 -0
- tests/blocks/operators/test_algebraic_function.py +230 -0
- tests/blocks/operators/test_dead_zone.py +106 -0
- tests/blocks/operators/test_delay.py +167 -0
- tests/blocks/operators/test_demux.py +111 -0
- tests/blocks/operators/test_discrete_derivator.py +160 -0
- tests/blocks/operators/test_discrete_integrator.py +149 -0
- tests/blocks/operators/test_gain.py +182 -0
- tests/blocks/operators/test_mux.py +101 -0
- tests/blocks/operators/test_product.py +171 -0
- tests/blocks/operators/test_rate_limiter.py +166 -0
- tests/blocks/operators/test_saturation.py +157 -0
- tests/blocks/operators/test_sum.py +183 -0
- tests/blocks/operators/test_zero_order_hold.py +106 -0
- tests/blocks/sources/test_chirp.py +157 -0
- tests/blocks/sources/test_constant.py +55 -0
- tests/blocks/sources/test_file_source.py +185 -0
- tests/blocks/sources/test_function_source.py +135 -0
- tests/blocks/sources/test_ramp.py +110 -0
- tests/blocks/sources/test_sinusoidal.py +113 -0
- tests/blocks/sources/test_step.py +76 -0
- tests/blocks/sources/test_white_noise.py +108 -0
- tests/blocks/systems/test_linear_state_space.py +135 -0
- tests/blocks/systems/test_polytopic_state_space.py +133 -0
- tests/core/test_core_errors_and_determinism.py +82 -0
- tests/core/test_core_multirate_tasks.py +88 -0
- tests/core/test_core_two_phase_and_propagation.py +132 -0
- tests/gui/main_window_open_test.py +162 -0
- tests/gui/test_block_meta_param_cache.py +16 -0
- tests/gui/test_file_source_meta.py +17 -0
- tests/gui/test_function_source_meta.py +28 -0
- tests/regression/test_simulink_dc_motor.py +217 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ExternalOutputMeta(BlockMeta):
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.name = "ExternalOutput"
|
|
30
|
+
self.category = "interfaces"
|
|
31
|
+
self.type = "external_output"
|
|
32
|
+
self.summary = "Pass-through block for external real-time commands/values."
|
|
33
|
+
self.description = (
|
|
34
|
+
"Pass-through block intended to expose a model command/value to an\n"
|
|
35
|
+
"external real-time application (motors, network, hardware driver, etc.).\n\n"
|
|
36
|
+
"This block does not perform any synchronization. It only enforces the\n"
|
|
37
|
+
"pySimBlocks signal convention (column vectors of shape (n,1))."
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.parameters = [
|
|
41
|
+
ParameterMeta(
|
|
42
|
+
name="sample_time",
|
|
43
|
+
type="float"
|
|
44
|
+
)
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
self.inputs = [
|
|
48
|
+
PortMeta(
|
|
49
|
+
name="in",
|
|
50
|
+
display_as="in",
|
|
51
|
+
shape=["n", 1],
|
|
52
|
+
description="Model command/value signal."
|
|
53
|
+
)
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
self.outputs = [
|
|
57
|
+
PortMeta(
|
|
58
|
+
name="out",
|
|
59
|
+
display_as="out",
|
|
60
|
+
shape=["m", 1],
|
|
61
|
+
description="External output signal."
|
|
62
|
+
)
|
|
63
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LuenbergerMeta(BlockMeta):
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.name = "Luenberger"
|
|
30
|
+
self.category = "observers"
|
|
31
|
+
self.type = "luenberger"
|
|
32
|
+
self.summary = "Discrete-time Luenberger state observer."
|
|
33
|
+
self.description = (
|
|
34
|
+
"Implements the discrete-time observer equations:\n"
|
|
35
|
+
"$$\n"
|
|
36
|
+
"x̂[k+1] = A x̂[k] + B u[k] + L (y[k] - C x̂[k])\n"
|
|
37
|
+
"$$\n"
|
|
38
|
+
"$$\n"
|
|
39
|
+
"ŷ[k] = C x̂[k]\n"
|
|
40
|
+
"$$\n"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.parameters = [
|
|
44
|
+
ParameterMeta(
|
|
45
|
+
name="A",
|
|
46
|
+
type="matrix",
|
|
47
|
+
required=True,
|
|
48
|
+
description="System state matrix."
|
|
49
|
+
),
|
|
50
|
+
ParameterMeta(
|
|
51
|
+
name="B",
|
|
52
|
+
type="matrix",
|
|
53
|
+
required=True,
|
|
54
|
+
description="Input matrix."
|
|
55
|
+
),
|
|
56
|
+
ParameterMeta(
|
|
57
|
+
name="C",
|
|
58
|
+
type="matrix",
|
|
59
|
+
required=True,
|
|
60
|
+
description="Output matrix."
|
|
61
|
+
),
|
|
62
|
+
ParameterMeta(
|
|
63
|
+
name="L",
|
|
64
|
+
type="matrix",
|
|
65
|
+
required=True,
|
|
66
|
+
description="Observer gain matrix."
|
|
67
|
+
),
|
|
68
|
+
ParameterMeta(
|
|
69
|
+
name="x0",
|
|
70
|
+
type="vector",
|
|
71
|
+
description="Initial estimated state."
|
|
72
|
+
),
|
|
73
|
+
ParameterMeta(
|
|
74
|
+
name="sample_time",
|
|
75
|
+
type="float",
|
|
76
|
+
description="Block execution period."
|
|
77
|
+
),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
self.inputs = [
|
|
81
|
+
PortMeta(
|
|
82
|
+
name="u",
|
|
83
|
+
display_as="u",
|
|
84
|
+
shape=["m", 1],
|
|
85
|
+
description="Control input."
|
|
86
|
+
),
|
|
87
|
+
PortMeta(
|
|
88
|
+
name="y",
|
|
89
|
+
display_as="y",
|
|
90
|
+
shape=["p", 1],
|
|
91
|
+
description="Measured output."
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
self.outputs = [
|
|
96
|
+
PortMeta(
|
|
97
|
+
name="x_hat",
|
|
98
|
+
display_as="x_hat",
|
|
99
|
+
shape=["n", 1],
|
|
100
|
+
description="Estimated state vector."
|
|
101
|
+
),
|
|
102
|
+
PortMeta(
|
|
103
|
+
name="y_hat",
|
|
104
|
+
display_as="y_hat",
|
|
105
|
+
shape=["p", 1],
|
|
106
|
+
description="Estimated output."
|
|
107
|
+
),
|
|
108
|
+
]
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Literal
|
|
26
|
+
|
|
27
|
+
from PySide6.QtWidgets import QFormLayout, QLabel, QLineEdit, QPushButton
|
|
28
|
+
|
|
29
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta, ParameterMeta
|
|
30
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
31
|
+
from pySimBlocks.gui.models import BlockInstance, PortInstance
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AlgebraicFunctionMeta(BlockMeta):
|
|
35
|
+
|
|
36
|
+
def __init__(self):
|
|
37
|
+
self.name = "AlgebraicFunction"
|
|
38
|
+
self.category = "operators"
|
|
39
|
+
self.type = "algebraic_function"
|
|
40
|
+
self.summary = "User-defined stateless algebraic function implemented in Python."
|
|
41
|
+
self.description = (
|
|
42
|
+
"This block evaluates a user-provided Python function of the form:\n\n"
|
|
43
|
+
" y = g(t, dt, u1, u2, ...)\n\n"
|
|
44
|
+
"The function is loaded from an external Python file and executed at each\n"
|
|
45
|
+
"simulation step. Input and output ports are explicitly declared via\n"
|
|
46
|
+
"`input_keys` and `output_keys`.\n\n"
|
|
47
|
+
"This block is stateless and has direct feedthrough."
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.parameters = [
|
|
51
|
+
ParameterMeta(
|
|
52
|
+
name="file_path",
|
|
53
|
+
type="string",
|
|
54
|
+
required=True,
|
|
55
|
+
description="Path to the Python file containing the function relative to the project.yaml file."
|
|
56
|
+
),
|
|
57
|
+
ParameterMeta(
|
|
58
|
+
name="function_name",
|
|
59
|
+
type="string",
|
|
60
|
+
required=True,
|
|
61
|
+
description="Name of the function to call inside the Python file."
|
|
62
|
+
),
|
|
63
|
+
ParameterMeta(
|
|
64
|
+
name="input_keys",
|
|
65
|
+
type="list[string]",
|
|
66
|
+
required=True,
|
|
67
|
+
description="List of input port names. These must match the function arguments after (t, dt)."
|
|
68
|
+
),
|
|
69
|
+
ParameterMeta(
|
|
70
|
+
name="output_keys",
|
|
71
|
+
type="list[string]",
|
|
72
|
+
required=True,
|
|
73
|
+
description="List of output port names. The function must return a dict with exactly these keys."
|
|
74
|
+
),
|
|
75
|
+
ParameterMeta(
|
|
76
|
+
name="sample_time",
|
|
77
|
+
type="float",
|
|
78
|
+
description="Optional execution period of the block."
|
|
79
|
+
),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
self.inputs = [
|
|
83
|
+
PortMeta(
|
|
84
|
+
name="in",
|
|
85
|
+
display_as="",
|
|
86
|
+
shape=[],
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
self.outputs = [
|
|
91
|
+
PortMeta(
|
|
92
|
+
name="out",
|
|
93
|
+
display_as="",
|
|
94
|
+
shape=[],
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# --------------------------------------------------------------------------
|
|
100
|
+
# Port resolution
|
|
101
|
+
# --------------------------------------------------------------------------
|
|
102
|
+
def resolve_port_group(
|
|
103
|
+
self,
|
|
104
|
+
port_meta: PortMeta,
|
|
105
|
+
direction: Literal['input', 'output'],
|
|
106
|
+
instance: "BlockInstance"
|
|
107
|
+
) -> list["PortInstance"]:
|
|
108
|
+
|
|
109
|
+
ports = []
|
|
110
|
+
if direction == "input":
|
|
111
|
+
keys = instance.parameters.get("input_keys", [])
|
|
112
|
+
if keys is None:
|
|
113
|
+
return []
|
|
114
|
+
for key in keys:
|
|
115
|
+
ports.append(
|
|
116
|
+
PortInstance(
|
|
117
|
+
name=f"{key}",
|
|
118
|
+
display_as=key,
|
|
119
|
+
direction="input",
|
|
120
|
+
block=instance
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
return ports
|
|
124
|
+
|
|
125
|
+
elif direction == "output":
|
|
126
|
+
keys = instance.parameters.get("output_keys", [])
|
|
127
|
+
if keys is None:
|
|
128
|
+
return []
|
|
129
|
+
for key in keys:
|
|
130
|
+
ports.append(
|
|
131
|
+
PortInstance(
|
|
132
|
+
name=f"{key}",
|
|
133
|
+
display_as=key,
|
|
134
|
+
direction="output",
|
|
135
|
+
block=instance
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
return ports
|
|
139
|
+
|
|
140
|
+
return super().resolve_port_group(port_meta, direction, instance)
|
|
141
|
+
|
|
142
|
+
# --------------------------------------------------------------------------
|
|
143
|
+
# Dialog methods
|
|
144
|
+
# --------------------------------------------------------------------------
|
|
145
|
+
def build_param(
|
|
146
|
+
self,
|
|
147
|
+
session,
|
|
148
|
+
form: QFormLayout,
|
|
149
|
+
readonly: bool = False,
|
|
150
|
+
):
|
|
151
|
+
# --- Block name ---
|
|
152
|
+
name_edit = QLineEdit(session.instance.name)
|
|
153
|
+
name_edit.textChanged.connect(
|
|
154
|
+
lambda val: self._on_param_changed(val, "name", session, readonly)
|
|
155
|
+
)
|
|
156
|
+
form.addRow(QLabel("Block name:"), name_edit)
|
|
157
|
+
if readonly:
|
|
158
|
+
name_edit.setReadOnly(True)
|
|
159
|
+
session.name_edit = name_edit
|
|
160
|
+
|
|
161
|
+
# --- Parameters ---
|
|
162
|
+
for pmeta in self.parameters:
|
|
163
|
+
if pmeta.name == "file_path":
|
|
164
|
+
self.build_file_param_row(
|
|
165
|
+
session,
|
|
166
|
+
form,
|
|
167
|
+
pmeta,
|
|
168
|
+
readonly=readonly,
|
|
169
|
+
file_filter="Python files (*.py);;All files (*)",
|
|
170
|
+
)
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
label, widget = self._create_param_row(session, pmeta, readonly)
|
|
174
|
+
if widget is None:
|
|
175
|
+
continue
|
|
176
|
+
if readonly:
|
|
177
|
+
self._set_readonly_style(widget)
|
|
178
|
+
|
|
179
|
+
form.addRow(label, widget)
|
|
180
|
+
session.param_widgets[pmeta.name] = widget
|
|
181
|
+
session.param_labels[pmeta.name] = label
|
|
182
|
+
|
|
183
|
+
# ------------------------------------------------------
|
|
184
|
+
def build_post_param(self, session, form: QFormLayout, readonly: bool = False):
|
|
185
|
+
open_btn = QPushButton("Open file")
|
|
186
|
+
open_btn.clicked.connect(lambda: self._open_file_from_session(session))
|
|
187
|
+
form.addRow(QLabel(""), open_btn)
|
|
188
|
+
session.open_file_btn = open_btn
|
|
189
|
+
self._refresh_open_button_state(session)
|
|
190
|
+
|
|
191
|
+
# ------------------------------------------------------
|
|
192
|
+
def refresh_form(self, session):
|
|
193
|
+
super().refresh_form(session)
|
|
194
|
+
self._refresh_open_button_state(session)
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------
|
|
197
|
+
def _resolve_file_path(self, session) -> Path | None:
|
|
198
|
+
raw = session.local_params.get("file_path")
|
|
199
|
+
if not raw:
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
path = Path(str(raw)).expanduser()
|
|
203
|
+
if not path.is_absolute() and session.project_dir is not None:
|
|
204
|
+
path = (session.project_dir / path).resolve()
|
|
205
|
+
return path
|
|
206
|
+
|
|
207
|
+
# ------------------------------------------------------
|
|
208
|
+
def _refresh_open_button_state(self, session) -> None:
|
|
209
|
+
btn = getattr(session, "open_file_btn", None)
|
|
210
|
+
if btn is None:
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
target = self._resolve_file_path(session)
|
|
214
|
+
exists = target is not None and target.is_file()
|
|
215
|
+
btn.setEnabled(exists)
|
|
216
|
+
if exists:
|
|
217
|
+
btn.setToolTip(str(target))
|
|
218
|
+
else:
|
|
219
|
+
btn.setToolTip("Set a valid existing file_path to open the file.")
|
|
220
|
+
|
|
221
|
+
# ------------------------------------------------------
|
|
222
|
+
def _open_file_from_session(self, session) -> None:
|
|
223
|
+
target = self._resolve_file_path(session)
|
|
224
|
+
if target is None or not target.is_file():
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
if sys.platform.startswith("darwin"):
|
|
228
|
+
subprocess.Popen(["open", str(target)])
|
|
229
|
+
elif os.name == "nt":
|
|
230
|
+
os.startfile(str(target))
|
|
231
|
+
else:
|
|
232
|
+
subprocess.Popen(["xdg-open", str(target)])
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DeadZoneMeta(BlockMeta):
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.name = "DeadZone"
|
|
30
|
+
self.category = "operators"
|
|
31
|
+
self.type = "dead_zone"
|
|
32
|
+
self.summary = "Discrete-time dead zone operator."
|
|
33
|
+
self.description = (
|
|
34
|
+
"Applies a dead-zone nonlinearity to the input signal:\n"
|
|
35
|
+
"$$\n"
|
|
36
|
+
"y = 0 \\quad \\text{if } \\text{lower\\_bound} \\le u \\le \\text{upper\\_bound}\n"
|
|
37
|
+
"$$\n"
|
|
38
|
+
"$$\n"
|
|
39
|
+
"y = u - \\text{upper\\_bound} \\quad \\text{if } u > \\text{upper\\_bound}\n"
|
|
40
|
+
"$$\n"
|
|
41
|
+
"$$\n"
|
|
42
|
+
"y = u - \\text{lower\\_bound} \\quad \\text{if } u < \\text{lower\\_bound}\n"
|
|
43
|
+
"$$\n"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.parameters = [
|
|
47
|
+
ParameterMeta(
|
|
48
|
+
name="lower_bound",
|
|
49
|
+
type="scalar | vector | matrix",
|
|
50
|
+
default=0.0
|
|
51
|
+
),
|
|
52
|
+
ParameterMeta(
|
|
53
|
+
name="upper_bound",
|
|
54
|
+
type="scalar | vector | matrix",
|
|
55
|
+
default=0.0
|
|
56
|
+
),
|
|
57
|
+
ParameterMeta(
|
|
58
|
+
name="sample_time",
|
|
59
|
+
type="float"
|
|
60
|
+
)
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
self.inputs = [
|
|
64
|
+
PortMeta(
|
|
65
|
+
name="in",
|
|
66
|
+
display_as="in",
|
|
67
|
+
shape=["n", "m"],
|
|
68
|
+
description="Input signal."
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
self.outputs = [
|
|
73
|
+
PortMeta(
|
|
74
|
+
name="out",
|
|
75
|
+
display_as="out",
|
|
76
|
+
shape=["n", "m"],
|
|
77
|
+
description="Output signal after dead-zone operation."
|
|
78
|
+
)
|
|
79
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
22
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
23
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class DelayMeta(BlockMeta):
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.name = "Delay"
|
|
30
|
+
self.category = "operators"
|
|
31
|
+
self.type = "delay"
|
|
32
|
+
self.summary = "N-step discrete-time delay block."
|
|
33
|
+
self.description = (
|
|
34
|
+
"Implements a discrete delay of $N$ simulation steps:\n"
|
|
35
|
+
"$$\n"
|
|
36
|
+
"y[k] = u[k - N]\n"
|
|
37
|
+
"$$\n"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
self.parameters = [
|
|
41
|
+
ParameterMeta(
|
|
42
|
+
name="num_delays",
|
|
43
|
+
type="int",
|
|
44
|
+
autofill=True,
|
|
45
|
+
default=1
|
|
46
|
+
),
|
|
47
|
+
ParameterMeta(
|
|
48
|
+
name="initial_output",
|
|
49
|
+
type="scalar | vector | matrix"
|
|
50
|
+
),
|
|
51
|
+
ParameterMeta(
|
|
52
|
+
name="sample_time",
|
|
53
|
+
type="float"
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
self.inputs = [
|
|
58
|
+
PortMeta(
|
|
59
|
+
name="in",
|
|
60
|
+
display_as="in",
|
|
61
|
+
shape=["n", "m"],
|
|
62
|
+
description="Input signal."
|
|
63
|
+
),
|
|
64
|
+
PortMeta(
|
|
65
|
+
name="reset",
|
|
66
|
+
display_as="reset",
|
|
67
|
+
shape=[],
|
|
68
|
+
description="Reset signal (0/1). When high (1), the delay state is reset to the initial output value."
|
|
69
|
+
)
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
self.outputs = [
|
|
73
|
+
PortMeta(
|
|
74
|
+
name="out",
|
|
75
|
+
display_as="out",
|
|
76
|
+
shape=["n", "m"],
|
|
77
|
+
description="Delayed output signal."
|
|
78
|
+
)
|
|
79
|
+
]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# ******************************************************************************
|
|
2
|
+
# pySimBlocks
|
|
3
|
+
# Copyright (c) 2026 Université de Lille & INRIA
|
|
4
|
+
# ******************************************************************************
|
|
5
|
+
# This program is free software: you can redistribute it and/or modify it
|
|
6
|
+
# under the terms of the GNU Lesser General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or (at your
|
|
8
|
+
# option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
11
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
12
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
|
13
|
+
# for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU Lesser General Public License
|
|
16
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
# ******************************************************************************
|
|
18
|
+
# Authors: see Authors.txt
|
|
19
|
+
# ******************************************************************************
|
|
20
|
+
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
24
|
+
from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
|
|
25
|
+
from pySimBlocks.gui.blocks.port_meta import PortMeta
|
|
26
|
+
from pySimBlocks.gui.models import BlockInstance, PortInstance
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DemuxMeta(BlockMeta):
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self.name = "Demux"
|
|
33
|
+
self.category = "operators"
|
|
34
|
+
self.type = "demux"
|
|
35
|
+
self.summary = "Vector split block (inverse of Mux)."
|
|
36
|
+
self.description = (
|
|
37
|
+
"Splits one input column vector into multiple output segments.\n"
|
|
38
|
+
"For input length $n$ and $p$ outputs:\n"
|
|
39
|
+
"$$\n"
|
|
40
|
+
"q = n // p, \\quad m = n \\% p\n"
|
|
41
|
+
"$$\n"
|
|
42
|
+
"The first $m$ outputs have $(q+1)$ elements and the remaining\n"
|
|
43
|
+
"$(p-m)$ outputs have $q$ elements.\n"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.parameters = [
|
|
47
|
+
ParameterMeta(
|
|
48
|
+
name="num_outputs",
|
|
49
|
+
type="int",
|
|
50
|
+
autofill=True,
|
|
51
|
+
default=2
|
|
52
|
+
),
|
|
53
|
+
ParameterMeta(
|
|
54
|
+
name="sample_time",
|
|
55
|
+
type="float"
|
|
56
|
+
)
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
self.inputs = [
|
|
60
|
+
PortMeta(
|
|
61
|
+
name="in",
|
|
62
|
+
display_as="in",
|
|
63
|
+
shape=["n", 1],
|
|
64
|
+
description="Input column vector to split."
|
|
65
|
+
)
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
self.outputs = [
|
|
69
|
+
PortMeta(
|
|
70
|
+
name="out",
|
|
71
|
+
display_as="",
|
|
72
|
+
shape=["k", 1],
|
|
73
|
+
description="Output vector segments."
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
def resolve_port_group(
|
|
78
|
+
self,
|
|
79
|
+
port_meta: PortMeta,
|
|
80
|
+
direction: Literal["input", "output"],
|
|
81
|
+
instance: "BlockInstance"
|
|
82
|
+
) -> list["PortInstance"]:
|
|
83
|
+
|
|
84
|
+
if direction == "output" and port_meta.name == "out":
|
|
85
|
+
num_outputs = instance.parameters.get("num_outputs", 0)
|
|
86
|
+
ports = []
|
|
87
|
+
for i in range(1, num_outputs + 1):
|
|
88
|
+
ports.append(
|
|
89
|
+
PortInstance(
|
|
90
|
+
name=f"{port_meta.name}{i}",
|
|
91
|
+
display_as=f"{i}",
|
|
92
|
+
direction="output",
|
|
93
|
+
block=instance
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
return ports
|
|
97
|
+
|
|
98
|
+
return super().resolve_port_group(port_meta, direction, instance)
|