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,312 @@
|
|
|
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 TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
from PySide6.QtCore import QPointF, Qt, QTimer
|
|
24
|
+
from PySide6.QtGui import QGuiApplication, QPainter, QPen
|
|
25
|
+
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView
|
|
26
|
+
|
|
27
|
+
from pySimBlocks.gui.graphics.block_item import BlockItem
|
|
28
|
+
from pySimBlocks.gui.graphics.connection_item import ConnectionItem, OrthogonalRoute
|
|
29
|
+
from pySimBlocks.gui.graphics.port_item import PortItem
|
|
30
|
+
from pySimBlocks.gui.graphics.theme import make_theme
|
|
31
|
+
from pySimBlocks.gui.models.block_instance import BlockInstance
|
|
32
|
+
from pySimBlocks.gui.models.connection_instance import ConnectionInstance
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from pySimBlocks.gui.project_controller import ProjectController
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class DiagramView(QGraphicsView):
|
|
39
|
+
def __init__(self):
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.diagram_scene = QGraphicsScene(self)
|
|
42
|
+
self.setScene(self.diagram_scene)
|
|
43
|
+
self.setAcceptDrops(True)
|
|
44
|
+
|
|
45
|
+
self.setRenderHint(QPainter.Antialiasing)
|
|
46
|
+
self.theme = make_theme()
|
|
47
|
+
self.diagram_scene.setBackgroundBrush(self.theme.scene_bg)
|
|
48
|
+
hints = QGuiApplication.styleHints()
|
|
49
|
+
hints.colorSchemeChanged.connect(self._on_color_scheme_changed)
|
|
50
|
+
app = QGuiApplication.instance()
|
|
51
|
+
if hasattr(app, "paletteChanged"):
|
|
52
|
+
app.paletteChanged.connect(lambda *_: QTimer.singleShot(0, self._apply_theme_from_system))
|
|
53
|
+
self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
|
|
54
|
+
|
|
55
|
+
self.pending_port: PortItem | None = None
|
|
56
|
+
self.temp_connection: ConnectionItem | None = None
|
|
57
|
+
self.copied_block: BlockItem | None = None
|
|
58
|
+
self.project_controller: "ProjectController" | None
|
|
59
|
+
self.block_items: dict[str, BlockItem] = {}
|
|
60
|
+
self.connections: dict[ConnectionInstance, ConnectionItem] = {}
|
|
61
|
+
|
|
62
|
+
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
|
|
63
|
+
self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
|
|
64
|
+
self.setDragMode(QGraphicsView.RubberBandDrag)
|
|
65
|
+
|
|
66
|
+
# --------------------------------------------------------------------------
|
|
67
|
+
# View methods
|
|
68
|
+
# --------------------------------------------------------------------------
|
|
69
|
+
def add_block(self, block_instance: BlockInstance,
|
|
70
|
+
block_layout: dict[str, Any] | None = None):
|
|
71
|
+
block_item = BlockItem(block_instance, self.drop_event_pos, self, block_layout)
|
|
72
|
+
self.diagram_scene.addItem(block_item)
|
|
73
|
+
self.block_items[block_instance.uid] = block_item
|
|
74
|
+
|
|
75
|
+
# --------------------------------------------------------------
|
|
76
|
+
def refresh_block_port(self, block_instance: BlockInstance):
|
|
77
|
+
block_item = self.get_block_item_from_instance(block_instance)
|
|
78
|
+
if block_item:
|
|
79
|
+
block_item.refresh_ports()
|
|
80
|
+
|
|
81
|
+
# --------------------------------------------------------------
|
|
82
|
+
def remove_block(self, block_instance: BlockInstance):
|
|
83
|
+
block_item = self.block_items[block_instance.uid]
|
|
84
|
+
self.diagram_scene.removeItem(block_item)
|
|
85
|
+
self.block_items.pop(block_instance.uid, None)
|
|
86
|
+
|
|
87
|
+
# --------------------------------------------------------------
|
|
88
|
+
def add_connection(self,
|
|
89
|
+
connection_instance: ConnectionInstance,
|
|
90
|
+
points: list[QPointF] | None = None
|
|
91
|
+
):
|
|
92
|
+
src_port_item = self.get_block_item_from_instance(connection_instance.src_block()).get_port_item(connection_instance.src_port.name)
|
|
93
|
+
dst_port_item = self.get_block_item_from_instance(connection_instance.dst_block()).get_port_item(connection_instance.dst_port.name)
|
|
94
|
+
connection_item = ConnectionItem(
|
|
95
|
+
src_port_item, dst_port_item, connection_instance, points
|
|
96
|
+
)
|
|
97
|
+
self.connections[connection_instance] = connection_item
|
|
98
|
+
self.diagram_scene.addItem(connection_item)
|
|
99
|
+
|
|
100
|
+
# --------------------------------------------------------------
|
|
101
|
+
def remove_connection(self, connection_instance: ConnectionInstance):
|
|
102
|
+
connection_item = self.connections.pop(connection_instance, None)
|
|
103
|
+
if connection_item:
|
|
104
|
+
self.diagram_scene.removeItem(connection_item)
|
|
105
|
+
|
|
106
|
+
# --------------------------------------------------------------
|
|
107
|
+
def get_block_item_from_instance(self, block_instance: BlockInstance) -> BlockItem | None:
|
|
108
|
+
return self.block_items.get(block_instance.uid)
|
|
109
|
+
|
|
110
|
+
# --------------------------------------------------------------
|
|
111
|
+
def create_connection_event(self, port: PortItem):
|
|
112
|
+
if not self.pending_port:
|
|
113
|
+
self.pending_port = port
|
|
114
|
+
self.temp_connection = ConnectionItem(self.pending_port, None, None)
|
|
115
|
+
self.diagram_scene.addItem(self.temp_connection)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# --------------------------------------------------------------
|
|
119
|
+
def update_block_param_event(self, block_instance: BlockInstance, params: dict[str, Any]):
|
|
120
|
+
self.project_controller.update_block_param(block_instance, params)
|
|
121
|
+
|
|
122
|
+
# --------------------------------------------------------------
|
|
123
|
+
def on_block_moved(self, block_item: BlockItem):
|
|
124
|
+
self.project_controller.make_dirty()
|
|
125
|
+
for conn_inst, conn_item in self.connections.items():
|
|
126
|
+
if conn_inst.is_block_involved(block_item.instance):
|
|
127
|
+
conn_item.invalidate_manual_route()
|
|
128
|
+
conn_item.update_position()
|
|
129
|
+
|
|
130
|
+
# --------------------------------------------------------------
|
|
131
|
+
def on_block_ports_refreshed(self, block_item: BlockItem):
|
|
132
|
+
for conn_inst, conn_item in self.connections.items():
|
|
133
|
+
if conn_inst.is_block_involved(block_item.instance):
|
|
134
|
+
conn_item.update_position()
|
|
135
|
+
|
|
136
|
+
# --------------------------------------------------------------------------
|
|
137
|
+
# Event handlers
|
|
138
|
+
# --------------------------------------------------------------------------
|
|
139
|
+
def dragEnterEvent(self, event):
|
|
140
|
+
if event.mimeData().hasText():
|
|
141
|
+
event.acceptProposedAction()
|
|
142
|
+
|
|
143
|
+
# --------------------------------------------------------------
|
|
144
|
+
def dragMoveEvent(self, event):
|
|
145
|
+
event.acceptProposedAction()
|
|
146
|
+
|
|
147
|
+
# --------------------------------------------------------------
|
|
148
|
+
def dropEvent(self, event):
|
|
149
|
+
self.drop_event_pos = self.mapToScene(event.position().toPoint())
|
|
150
|
+
category, block_type = event.mimeData().text().split(":")
|
|
151
|
+
self.project_controller.add_block(category, block_type)
|
|
152
|
+
event.acceptProposedAction()
|
|
153
|
+
|
|
154
|
+
# --------------------------------------------------------------
|
|
155
|
+
def keyPressEvent(self, event):
|
|
156
|
+
# COPY
|
|
157
|
+
if event.key() == Qt.Key_C and event.modifiers() & Qt.ControlModifier:
|
|
158
|
+
selected = [i for i in self.diagram_scene.selectedItems() if isinstance(i, BlockItem)]
|
|
159
|
+
if selected:
|
|
160
|
+
self.copied_block = selected[0]
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
# PASTE
|
|
164
|
+
if event.key() == Qt.Key_V and event.modifiers() & Qt.ControlModifier:
|
|
165
|
+
if self.copied_block:
|
|
166
|
+
self.drop_event_pos = self.copied_block.pos() + QPointF(30, 30)
|
|
167
|
+
self.project_controller.add_copy_block(self.copied_block.instance)
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
# DELETE
|
|
171
|
+
if event.key() in (Qt.Key_Delete, Qt.Key_Backspace):
|
|
172
|
+
self.delete_selected()
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
# ZOOM IN / OUT
|
|
176
|
+
if event.key() in (Qt.Key_Plus, Qt.Key_Equal) and event.modifiers() & Qt.ControlModifier:
|
|
177
|
+
self.scale_view(1.15)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
if event.key() == Qt.Key_Minus and event.modifiers() & Qt.ControlModifier:
|
|
181
|
+
self.scale_view(1 / 1.15)
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
# ROTATE BLOCK
|
|
185
|
+
if event.key() == Qt.Key_R and event.modifiers() & Qt.ControlModifier:
|
|
186
|
+
selected = [i for i in self.diagram_scene.selectedItems()
|
|
187
|
+
if isinstance(i, BlockItem)]
|
|
188
|
+
for item in selected:
|
|
189
|
+
item.toggle_orientation()
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# CENTER VIEW
|
|
193
|
+
if event.key() == Qt.Key_Space and not event.modifiers():
|
|
194
|
+
self._center_on_diagram()
|
|
195
|
+
event.accept()
|
|
196
|
+
return
|
|
197
|
+
super().keyPressEvent(event)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# --------------------------------------------------------------
|
|
202
|
+
def wheelEvent(self, event):
|
|
203
|
+
if event.modifiers() & Qt.ControlModifier:
|
|
204
|
+
zoom_factor = 1.15
|
|
205
|
+
if event.angleDelta().y() > 0:
|
|
206
|
+
self.scale_view(zoom_factor)
|
|
207
|
+
else:
|
|
208
|
+
self.scale_view(1 / zoom_factor)
|
|
209
|
+
event.accept()
|
|
210
|
+
else:
|
|
211
|
+
super().wheelEvent(event)
|
|
212
|
+
|
|
213
|
+
# --------------------------------------------------------------
|
|
214
|
+
def mouseMoveEvent(self, event):
|
|
215
|
+
if self.temp_connection:
|
|
216
|
+
pos = self.mapToScene(event.position().toPoint())
|
|
217
|
+
self.temp_connection.update_temp_position(pos)
|
|
218
|
+
return
|
|
219
|
+
super().mouseMoveEvent(event)
|
|
220
|
+
|
|
221
|
+
# --------------------------------------------------------------
|
|
222
|
+
def mouseReleaseEvent(self, event):
|
|
223
|
+
if not self.pending_port:
|
|
224
|
+
super().mouseReleaseEvent(event)
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
pos = self.mapToScene(event.position().toPoint())
|
|
228
|
+
items = self.diagram_scene.items(pos)
|
|
229
|
+
port = next((i for i in items if isinstance(i, PortItem)), None)
|
|
230
|
+
if not port:
|
|
231
|
+
self._cancel_temp_connection()
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
self.project_controller.add_connection(self.pending_port.instance, port.instance)
|
|
235
|
+
self._cancel_temp_connection()
|
|
236
|
+
|
|
237
|
+
# --------------------------------------------------------------
|
|
238
|
+
def delete_selected(self):
|
|
239
|
+
for item in self.diagram_scene.selectedItems():
|
|
240
|
+
if isinstance(item, BlockItem):
|
|
241
|
+
self.project_controller.remove_block(item.instance)
|
|
242
|
+
|
|
243
|
+
elif isinstance(item, ConnectionItem):
|
|
244
|
+
self.project_controller.remove_connection(item.instance)
|
|
245
|
+
|
|
246
|
+
# --------------------------------------------------------------
|
|
247
|
+
def clear_scene(self):
|
|
248
|
+
|
|
249
|
+
for block in list(self.block_items.values()):
|
|
250
|
+
self.project_controller.remove_block(block.instance)
|
|
251
|
+
|
|
252
|
+
for connection in list(self.connections.values()):
|
|
253
|
+
self.project_controller.remove_connection(connection.instance)
|
|
254
|
+
|
|
255
|
+
self.pending_port = None
|
|
256
|
+
|
|
257
|
+
# --------------------------------------------------------------------------
|
|
258
|
+
# Private methods
|
|
259
|
+
# --------------------------------------------------------------------------
|
|
260
|
+
def _cancel_temp_connection(self):
|
|
261
|
+
self.diagram_scene.removeItem(self.temp_connection)
|
|
262
|
+
self.temp_connection = None
|
|
263
|
+
self.pending_port = None
|
|
264
|
+
|
|
265
|
+
# --------------------------------------------------------------
|
|
266
|
+
def scale_view(self, factor):
|
|
267
|
+
current_scale = self.transform().m11()
|
|
268
|
+
min_scale, max_scale = 0.2, 5.0
|
|
269
|
+
|
|
270
|
+
new_scale = current_scale * factor
|
|
271
|
+
if min_scale <= new_scale <= max_scale:
|
|
272
|
+
self.scale(factor, factor)
|
|
273
|
+
|
|
274
|
+
# --------------------------------------------------------------
|
|
275
|
+
def _on_color_scheme_changed(self, *_):
|
|
276
|
+
QTimer.singleShot(0, self._apply_theme_from_system)
|
|
277
|
+
|
|
278
|
+
# --------------------------------------------------------------
|
|
279
|
+
def _apply_theme_from_system(self):
|
|
280
|
+
self.theme = make_theme()
|
|
281
|
+
self.diagram_scene.setBackgroundBrush(self.theme.scene_bg)
|
|
282
|
+
self._refresh_theme_items()
|
|
283
|
+
self.viewport().update()
|
|
284
|
+
self.diagram_scene.update()
|
|
285
|
+
|
|
286
|
+
# --------------------------------------------------------------
|
|
287
|
+
def _refresh_theme_items(self):
|
|
288
|
+
for block in self.block_items.values():
|
|
289
|
+
block.update()
|
|
290
|
+
for port in block.port_items:
|
|
291
|
+
port.label.setDefaultTextColor(self.theme.text)
|
|
292
|
+
port.update()
|
|
293
|
+
|
|
294
|
+
for conn in self.connections.values():
|
|
295
|
+
conn.setPen(QPen(self.theme.wire, 2))
|
|
296
|
+
conn.update_position()
|
|
297
|
+
conn.update()
|
|
298
|
+
|
|
299
|
+
# --------------------------------------------------------------
|
|
300
|
+
def _center_on_diagram(self):
|
|
301
|
+
scene = self.diagram_scene
|
|
302
|
+
items_rect = scene.itemsBoundingRect()
|
|
303
|
+
|
|
304
|
+
if items_rect.isNull():
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
# Un peu de marge pour éviter que ça colle aux bords
|
|
308
|
+
margin = 40
|
|
309
|
+
items_rect.adjust(-margin, -margin, margin, margin)
|
|
310
|
+
|
|
311
|
+
scene.setSceneRect(items_rect)
|
|
312
|
+
self.fitInView(items_rect, Qt.KeepAspectRatio)
|
|
@@ -0,0 +1,161 @@
|
|
|
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 PySide6.QtWidgets import QToolBar, QMessageBox, QProgressDialog, QApplication
|
|
22
|
+
from PySide6.QtGui import QAction
|
|
23
|
+
from PySide6.QtCore import Qt
|
|
24
|
+
|
|
25
|
+
from pySimBlocks.gui.dialogs.display_yaml_dialog import DisplayYamlDialog
|
|
26
|
+
from pySimBlocks.gui.dialogs.plot_dialog import PlotDialog
|
|
27
|
+
from pySimBlocks.gui.dialogs.settings_dialog import SettingsDialog
|
|
28
|
+
from pySimBlocks.gui.project_controller import ProjectController
|
|
29
|
+
from pySimBlocks.gui.services.project_saver import ProjectSaver
|
|
30
|
+
from pySimBlocks.gui.services.simulation_runner import SimulationRunner
|
|
31
|
+
|
|
32
|
+
# Add ons
|
|
33
|
+
from pySimBlocks.gui.addons.sofa.sofa_dialog import SofaDialog
|
|
34
|
+
from pySimBlocks.gui.addons.sofa.sofa_service import SofaService
|
|
35
|
+
|
|
36
|
+
class ToolBarView(QToolBar):
|
|
37
|
+
|
|
38
|
+
def __init__(self,
|
|
39
|
+
saver: ProjectSaver,
|
|
40
|
+
runner: SimulationRunner,
|
|
41
|
+
project_controller: ProjectController):
|
|
42
|
+
super().__init__()
|
|
43
|
+
|
|
44
|
+
self.saver = saver
|
|
45
|
+
self.runner = runner
|
|
46
|
+
self.project_controller = project_controller
|
|
47
|
+
|
|
48
|
+
save_action = QAction("Save", self)
|
|
49
|
+
save_action.triggered.connect(self.on_save)
|
|
50
|
+
self.addAction(save_action)
|
|
51
|
+
|
|
52
|
+
export_action = QAction("Export", self)
|
|
53
|
+
export_action.triggered.connect(self.on_export_project)
|
|
54
|
+
self.addAction(export_action)
|
|
55
|
+
|
|
56
|
+
display_action = QAction("Project YAML", self)
|
|
57
|
+
display_action.triggered.connect(self.on_open_display_yaml)
|
|
58
|
+
self.addAction(display_action)
|
|
59
|
+
|
|
60
|
+
sim_settings_action = QAction("Settings", self)
|
|
61
|
+
sim_settings_action.triggered.connect(self.on_open_simulation_settings)
|
|
62
|
+
self.addAction(sim_settings_action)
|
|
63
|
+
|
|
64
|
+
run_action = QAction("Run", self)
|
|
65
|
+
run_action.triggered.connect(self.on_run_sim)
|
|
66
|
+
self.addAction(run_action)
|
|
67
|
+
|
|
68
|
+
plot_action = QAction("Plot", self)
|
|
69
|
+
plot_action.triggered.connect(self.on_plot_logs)
|
|
70
|
+
self.addAction(plot_action)
|
|
71
|
+
|
|
72
|
+
# add ons
|
|
73
|
+
self.sofa_service = SofaService(self.project_controller.project_state, self.project_controller)
|
|
74
|
+
self.sofa_action = QAction("Sofa", self)
|
|
75
|
+
self.sofa_action.triggered.connect(self.on_open_sofa_dialog)
|
|
76
|
+
self.addAction(self.sofa_action)
|
|
77
|
+
|
|
78
|
+
def on_save(self):
|
|
79
|
+
self.saver.save(self.project_controller.project_state, self.project_controller.view.block_items)
|
|
80
|
+
self.project_controller.clear_dirty()
|
|
81
|
+
|
|
82
|
+
def on_export_project(self):
|
|
83
|
+
window = self.parent()
|
|
84
|
+
if window.confirm_discard_or_save("exporting"):
|
|
85
|
+
self.saver.export(self.project_controller.project_state, self.project_controller.view.block_items)
|
|
86
|
+
|
|
87
|
+
def on_open_display_yaml(self):
|
|
88
|
+
dialog = DisplayYamlDialog(self.project_controller.project_state, self.project_controller.view)
|
|
89
|
+
dialog.exec()
|
|
90
|
+
|
|
91
|
+
def on_open_simulation_settings(self):
|
|
92
|
+
dialog = SettingsDialog(self.project_controller.project_state, self.project_controller, self.parent())
|
|
93
|
+
dialog.exec()
|
|
94
|
+
|
|
95
|
+
def on_run_sim(self):
|
|
96
|
+
dlg = QProgressDialog(self)
|
|
97
|
+
dlg.setWindowTitle("Simulation")
|
|
98
|
+
dlg.setLabelText("Running simulation...\nPlease wait.")
|
|
99
|
+
dlg.setRange(0, 0) # busy indicator
|
|
100
|
+
dlg.setCancelButton(None)
|
|
101
|
+
dlg.setWindowModality(Qt.ApplicationModal)
|
|
102
|
+
dlg.setMinimumWidth(300)
|
|
103
|
+
dlg.setMinimumHeight(120)
|
|
104
|
+
dlg.show()
|
|
105
|
+
QApplication.processEvents()
|
|
106
|
+
|
|
107
|
+
self.set_running(True)
|
|
108
|
+
logs, flag, msg = self.runner.run(self.project_controller.project_state)
|
|
109
|
+
dlg.close()
|
|
110
|
+
self.set_running(False)
|
|
111
|
+
self.project_controller.project_state.logs = logs
|
|
112
|
+
|
|
113
|
+
if not flag:
|
|
114
|
+
QMessageBox.warning(
|
|
115
|
+
self,
|
|
116
|
+
"Simulation failed with error",
|
|
117
|
+
msg,
|
|
118
|
+
QMessageBox.Ok,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def on_plot_logs(self):
|
|
123
|
+
flag, msg = self.project_controller.project_state.can_plot()
|
|
124
|
+
if not flag:
|
|
125
|
+
QMessageBox.warning(
|
|
126
|
+
self,
|
|
127
|
+
"Plot Error",
|
|
128
|
+
msg,
|
|
129
|
+
QMessageBox.Ok,
|
|
130
|
+
)
|
|
131
|
+
return
|
|
132
|
+
self._plot_dialog = PlotDialog(self.project_controller.project_state, self.parent()) # keep ref because of python garbage collector
|
|
133
|
+
self._plot_dialog.show()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def set_running(self, running: bool):
|
|
137
|
+
for action in self.actions():
|
|
138
|
+
action.setEnabled(not running)
|
|
139
|
+
|
|
140
|
+
#####################################
|
|
141
|
+
# Adds on
|
|
142
|
+
def refresh_sofa_button(self):
|
|
143
|
+
if self.project_controller.has_sofa_block():
|
|
144
|
+
if self.sofa_action not in self.actions():
|
|
145
|
+
self.addAction(self.sofa_action)
|
|
146
|
+
else:
|
|
147
|
+
if self.sofa_action in self.actions():
|
|
148
|
+
self.removeAction(self.sofa_action)
|
|
149
|
+
|
|
150
|
+
def on_open_sofa_dialog(self):
|
|
151
|
+
ok, msg, details = self.sofa_service.can_use_sofa()
|
|
152
|
+
if not ok:
|
|
153
|
+
QMessageBox.warning(
|
|
154
|
+
self,
|
|
155
|
+
msg,
|
|
156
|
+
details,
|
|
157
|
+
QMessageBox.Ok
|
|
158
|
+
)
|
|
159
|
+
return
|
|
160
|
+
dialog = SofaDialog(self.sofa_service, self.parent())
|
|
161
|
+
dialog.exec()
|
|
@@ -0,0 +1,35 @@
|
|
|
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.project.generate_run_script import generate_run_script, generate_python_content
|
|
22
|
+
from pySimBlocks.project.load_project_config import load_project_config
|
|
23
|
+
from pySimBlocks.project.load_simulator import load_simulator_from_project
|
|
24
|
+
from pySimBlocks.project.load_simulation_config import load_simulation_config
|
|
25
|
+
from pySimBlocks.project.plot_from_config import plot_from_config
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"generate_run_script",
|
|
30
|
+
"generate_python_content",
|
|
31
|
+
"load_project_config",
|
|
32
|
+
"load_simulator_from_project",
|
|
33
|
+
"load_simulation_config",
|
|
34
|
+
"plot_from_config"
|
|
35
|
+
]
|
|
@@ -0,0 +1,92 @@
|
|
|
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 importlib
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Dict, Any
|
|
24
|
+
import yaml
|
|
25
|
+
|
|
26
|
+
from pySimBlocks.core.model import Model
|
|
27
|
+
|
|
28
|
+
# ============================================================
|
|
29
|
+
# Public API
|
|
30
|
+
# ============================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def build_model_from_dict(
|
|
34
|
+
model: Model,
|
|
35
|
+
model_data: Dict[str, Any],
|
|
36
|
+
params_dir: Path | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Build a Model instance from an already loaded model dictionary.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
# ------------------------------------------------------------
|
|
43
|
+
# Load block registry
|
|
44
|
+
# ------------------------------------------------------------
|
|
45
|
+
index_path = Path(__file__).parent / "pySimBlocks_blocks_index.yaml"
|
|
46
|
+
with index_path.open("r") as f:
|
|
47
|
+
blocks_index = yaml.safe_load(f) or {}
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------
|
|
50
|
+
# Instantiate blocks
|
|
51
|
+
# ------------------------------------------------------------
|
|
52
|
+
for desc in model_data.get("blocks", []):
|
|
53
|
+
name = desc["name"]
|
|
54
|
+
category = desc["category"]
|
|
55
|
+
block_type = desc["type"]
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
block_info = blocks_index[category][block_type]
|
|
59
|
+
except KeyError:
|
|
60
|
+
print(f"Available blocks in category '{category}':")
|
|
61
|
+
for bt in blocks_index.get(category, {}):
|
|
62
|
+
print(f" - {bt}")
|
|
63
|
+
print(desc)
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Unknown block '{block_type}' in category '{category}'."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# --------------------------------------------------------
|
|
69
|
+
# Load Python block class
|
|
70
|
+
# --------------------------------------------------------
|
|
71
|
+
module = importlib.import_module(block_info["module"])
|
|
72
|
+
BlockClass = getattr(module, block_info["class"])
|
|
73
|
+
|
|
74
|
+
# --------------------------------------------------------
|
|
75
|
+
# Load parameters
|
|
76
|
+
# --------------------------------------------------------
|
|
77
|
+
params = desc.get("parameters", {})
|
|
78
|
+
|
|
79
|
+
# --------------------------------------------------------
|
|
80
|
+
# Instantiate block
|
|
81
|
+
# --------------------------------------------------------
|
|
82
|
+
params = BlockClass.adapt_params(params, params_dir=params_dir)
|
|
83
|
+
block = BlockClass(name=name, **params)
|
|
84
|
+
model.add_block(block)
|
|
85
|
+
|
|
86
|
+
# ------------------------------------------------------------
|
|
87
|
+
# Connections
|
|
88
|
+
# ------------------------------------------------------------
|
|
89
|
+
for src, dst in model_data.get("connections", []):
|
|
90
|
+
src_block, src_port = src.split(".")
|
|
91
|
+
dst_block, dst_port = dst.split(".")
|
|
92
|
+
model.connect(src_block, src_port, dst_block, dst_port)
|