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,302 @@
|
|
|
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 copy
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
24
|
+
|
|
25
|
+
from PySide6.QtCore import QObject, Signal, QPointF
|
|
26
|
+
|
|
27
|
+
from pySimBlocks.gui.models import (
|
|
28
|
+
BlockInstance,
|
|
29
|
+
ConnectionInstance,
|
|
30
|
+
PortInstance,
|
|
31
|
+
ProjectState,
|
|
32
|
+
port_instance,
|
|
33
|
+
)
|
|
34
|
+
from pySimBlocks.gui.widgets.diagram_view import DiagramView
|
|
35
|
+
from pySimBlocks.gui.blocks.block_meta import BlockMeta
|
|
36
|
+
from pySimBlocks.gui.services.yaml_tools import cleanup_runtime_project_yaml
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from pySimBlocks.gui.services.project_loader import ProjectLoader
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ProjectController(QObject):
|
|
43
|
+
|
|
44
|
+
dirty_changed: Signal = Signal(bool)
|
|
45
|
+
|
|
46
|
+
def __init__(self,
|
|
47
|
+
project_state: ProjectState,
|
|
48
|
+
view: DiagramView,
|
|
49
|
+
resolve_block_meta: Callable[[str, str], BlockMeta]
|
|
50
|
+
):
|
|
51
|
+
super().__init__()
|
|
52
|
+
self.project_state = project_state
|
|
53
|
+
self.resolve_block_meta = resolve_block_meta
|
|
54
|
+
self.view = view
|
|
55
|
+
|
|
56
|
+
self.is_dirty: bool = False
|
|
57
|
+
|
|
58
|
+
# --------------------------------------------------------------------------
|
|
59
|
+
# Blocks methods
|
|
60
|
+
# --------------------------------------------------------------------------
|
|
61
|
+
def add_block(self, category: str,
|
|
62
|
+
block_type: str,
|
|
63
|
+
block_layout: dict | None = None) -> BlockInstance:
|
|
64
|
+
block_meta = self.resolve_block_meta(category, block_type)
|
|
65
|
+
block_instance = BlockInstance(block_meta)
|
|
66
|
+
return self._add_block(block_instance, block_layout)
|
|
67
|
+
|
|
68
|
+
# ------------------------------------------------------------------
|
|
69
|
+
def add_copy_block(self, block_instance: BlockInstance) -> BlockInstance:
|
|
70
|
+
copy = BlockInstance.copy(block_instance)
|
|
71
|
+
return self._add_block(copy)
|
|
72
|
+
|
|
73
|
+
# ------------------------------------------------------------------
|
|
74
|
+
def _add_block(self, block_instance: BlockInstance,
|
|
75
|
+
block_layout: dict | None = None) -> BlockInstance:
|
|
76
|
+
self.make_dirty()
|
|
77
|
+
block_instance.name = self.make_unique_name(block_instance.name)
|
|
78
|
+
block_instance.resolve_ports()
|
|
79
|
+
self.project_state.add_block(block_instance)
|
|
80
|
+
self.view.add_block(block_instance, block_layout)
|
|
81
|
+
|
|
82
|
+
return block_instance
|
|
83
|
+
|
|
84
|
+
# ------------------------------------------------------------------
|
|
85
|
+
def rename_block(self, block_instance: BlockInstance, new_name: str):
|
|
86
|
+
old_name = block_instance.name
|
|
87
|
+
|
|
88
|
+
if old_name == new_name:
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
self.make_dirty()
|
|
92
|
+
new_name = self.make_unique_name(new_name)
|
|
93
|
+
|
|
94
|
+
block_instance.name = new_name
|
|
95
|
+
prefix_old = f"{old_name}.outputs."
|
|
96
|
+
prefix_new = f"{new_name}.outputs."
|
|
97
|
+
|
|
98
|
+
self.project_state.logging = [
|
|
99
|
+
s.replace(prefix_old, prefix_new)
|
|
100
|
+
if s.startswith(prefix_old) else s
|
|
101
|
+
for s in self.project_state.logging
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
for plot in self.project_state.plots:
|
|
105
|
+
plot["signals"] = [
|
|
106
|
+
s.replace(prefix_old, prefix_new)
|
|
107
|
+
if s.startswith(prefix_old) else s
|
|
108
|
+
for s in plot["signals"]
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
def update_block_param(self, block_instance: BlockInstance, params: dict[str, Any]):
|
|
113
|
+
|
|
114
|
+
self.rename_block(block_instance, params.pop("name", block_instance.name))
|
|
115
|
+
|
|
116
|
+
if params == block_instance.parameters:
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
block_instance.update_params(params)
|
|
120
|
+
block_instance.resolve_ports()
|
|
121
|
+
self._remove_connection_if_port_disapear(block_instance)
|
|
122
|
+
self.view.refresh_block_port(block_instance)
|
|
123
|
+
self.make_dirty()
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------------
|
|
126
|
+
def remove_block(self, block_instance: BlockInstance):
|
|
127
|
+
self.make_dirty()
|
|
128
|
+
|
|
129
|
+
# remove connections
|
|
130
|
+
for connection in self.project_state.get_connections_of_block(block_instance):
|
|
131
|
+
self.remove_connection(connection)
|
|
132
|
+
|
|
133
|
+
# delete all outputs signal from logging
|
|
134
|
+
removed_signals = [
|
|
135
|
+
f"{block_instance.name}.outputs.{p.name}"
|
|
136
|
+
for p in block_instance.ports if p.direction == "output"
|
|
137
|
+
]
|
|
138
|
+
remaining_signals = [
|
|
139
|
+
s for s in self.project_state.logging
|
|
140
|
+
if s not in removed_signals
|
|
141
|
+
]
|
|
142
|
+
self.set_logged_signals(remaining_signals)
|
|
143
|
+
|
|
144
|
+
# remove signals from plots and delete empty plot
|
|
145
|
+
for i in reversed(range(len(self.project_state.plots))):
|
|
146
|
+
plot = self.project_state.plots[i]
|
|
147
|
+
# Keep only signals that are not removed
|
|
148
|
+
plot["signals"] = [s for s in plot["signals"] if s not in removed_signals]
|
|
149
|
+
# Delete the plot if it has no signals left
|
|
150
|
+
if not plot["signals"]:
|
|
151
|
+
self.delete_plot(i)
|
|
152
|
+
|
|
153
|
+
# remove block
|
|
154
|
+
self.project_state.remove_block(block_instance)
|
|
155
|
+
self.view.remove_block(block_instance)
|
|
156
|
+
|
|
157
|
+
# ------------------------------------------------------------------
|
|
158
|
+
def make_unique_name(self, base_name: str) -> str:
|
|
159
|
+
existing = {b.name for b in self.project_state.blocks}
|
|
160
|
+
|
|
161
|
+
if base_name not in existing:
|
|
162
|
+
return base_name
|
|
163
|
+
|
|
164
|
+
i = 1
|
|
165
|
+
while f"{base_name}_{i}" in existing:
|
|
166
|
+
i += 1
|
|
167
|
+
|
|
168
|
+
return f"{base_name}_{i}"
|
|
169
|
+
|
|
170
|
+
# ------------------------------------------------------------------
|
|
171
|
+
def is_name_available(self, name: str, current=None) -> bool:
|
|
172
|
+
for b in self.project_state.blocks:
|
|
173
|
+
if b is current:
|
|
174
|
+
continue
|
|
175
|
+
if b.name == name:
|
|
176
|
+
return False
|
|
177
|
+
return True
|
|
178
|
+
|
|
179
|
+
# --------------------------------------------------------------------------
|
|
180
|
+
# connection methods
|
|
181
|
+
# --------------------------------------------------------------------------
|
|
182
|
+
def add_connection(self,
|
|
183
|
+
port1: PortInstance,
|
|
184
|
+
port2: PortInstance,
|
|
185
|
+
points: list[QPointF] | None = None):
|
|
186
|
+
|
|
187
|
+
if not port1.is_compatible(port2):
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
src_port, dst_port = (
|
|
191
|
+
(port1, port2) if port1.direction == "output" else (port2, port1)
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
port_dst_connections = self.project_state.get_connections_of_port(dst_port)
|
|
195
|
+
|
|
196
|
+
if not dst_port.can_accept_connection(port_dst_connections):
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
connection_instance = ConnectionInstance(src_port, dst_port)
|
|
200
|
+
|
|
201
|
+
self.project_state.add_connection(connection_instance)
|
|
202
|
+
self.view.add_connection(connection_instance, points)
|
|
203
|
+
self.make_dirty()
|
|
204
|
+
|
|
205
|
+
# ------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
def _remove_connection_if_port_disapear(self, block_instance: BlockInstance) -> None:
|
|
208
|
+
|
|
209
|
+
for connection in self.project_state.get_connections_of_block(block_instance):
|
|
210
|
+
|
|
211
|
+
src_exists = connection.src_port in connection.src_block().ports
|
|
212
|
+
dst_exists = connection.dst_port in connection.dst_block().ports
|
|
213
|
+
if not (src_exists and dst_exists):
|
|
214
|
+
self.remove_connection(connection)
|
|
215
|
+
|
|
216
|
+
# ------------------------------------------------------------------
|
|
217
|
+
def remove_connection(self, connection: ConnectionInstance):
|
|
218
|
+
|
|
219
|
+
self.project_state.remove_connection(connection)
|
|
220
|
+
self.view.remove_connection(connection)
|
|
221
|
+
self.make_dirty()
|
|
222
|
+
|
|
223
|
+
# --------------------------------------------------------------------------
|
|
224
|
+
# Project methods
|
|
225
|
+
# --------------------------------------------------------------------------
|
|
226
|
+
def make_dirty(self):
|
|
227
|
+
if not self.is_dirty:
|
|
228
|
+
self.is_dirty = True
|
|
229
|
+
self.dirty_changed.emit(True)
|
|
230
|
+
|
|
231
|
+
# ------------------------------------------------------------------
|
|
232
|
+
def clear_dirty(self):
|
|
233
|
+
if self.is_dirty:
|
|
234
|
+
self.is_dirty = False
|
|
235
|
+
self.dirty_changed.emit(False)
|
|
236
|
+
|
|
237
|
+
# ------------------------------------------------------------------
|
|
238
|
+
def clear(self):
|
|
239
|
+
self.project_state.clear()
|
|
240
|
+
self.view.clear_scene()
|
|
241
|
+
|
|
242
|
+
# ------------------------------------------------------------------
|
|
243
|
+
def update_project_param(self, new_path: Path, ext: str):
|
|
244
|
+
cleanup_runtime_project_yaml(self.project_state.directory_path)
|
|
245
|
+
if new_path != self.project_state.directory_path:
|
|
246
|
+
self.make_dirty()
|
|
247
|
+
self.project_state.directory_path = new_path
|
|
248
|
+
|
|
249
|
+
if ext != self.project_state.external:
|
|
250
|
+
self.make_dirty()
|
|
251
|
+
self.project_state.external = None if ext == "" else ext
|
|
252
|
+
|
|
253
|
+
# ------------------------------------------------------------------
|
|
254
|
+
def load_project(self, loader: 'ProjectLoader'):
|
|
255
|
+
loader.load(self, self.project_state.directory_path)
|
|
256
|
+
|
|
257
|
+
# --------------------------------------------------------------------------
|
|
258
|
+
# Plot methods
|
|
259
|
+
# --------------------------------------------------------------------------
|
|
260
|
+
def create_plot(self, title: str, signals: list[str]) -> None:
|
|
261
|
+
self._ensure_logged(signals)
|
|
262
|
+
self.project_state.plots.append({
|
|
263
|
+
"title": title,
|
|
264
|
+
"signals": list(signals),
|
|
265
|
+
})
|
|
266
|
+
self.make_dirty()
|
|
267
|
+
|
|
268
|
+
# ------------------------------------------------------------------
|
|
269
|
+
def update_plot(self, index: int, title: str, signals: list[str]) -> None:
|
|
270
|
+
self._ensure_logged(signals)
|
|
271
|
+
plot = self.project_state.plots[index]
|
|
272
|
+
if plot["signals"] == signals and plot["title"] == title:
|
|
273
|
+
return
|
|
274
|
+
plot["title"] = title
|
|
275
|
+
plot["signals"] = list(signals)
|
|
276
|
+
self.make_dirty()
|
|
277
|
+
|
|
278
|
+
# ------------------------------------------------------------------
|
|
279
|
+
def delete_plot(self, index: int) -> None:
|
|
280
|
+
del self.project_state.plots[index]
|
|
281
|
+
self.make_dirty()
|
|
282
|
+
|
|
283
|
+
# ------------------------------------------------------------------
|
|
284
|
+
def _ensure_logged(self, signals: list[str]):
|
|
285
|
+
for sig in signals:
|
|
286
|
+
if sig not in self.project_state.logging:
|
|
287
|
+
self.project_state.logging.append(sig)
|
|
288
|
+
|
|
289
|
+
# ------------------------------------------------------------------
|
|
290
|
+
def update_simulation_params(self, params: dict[str, float | str]):
|
|
291
|
+
if self.project_state.simulation.__dict__ == params:
|
|
292
|
+
return
|
|
293
|
+
self.project_state.load_simulation(params)
|
|
294
|
+
self.make_dirty()
|
|
295
|
+
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
def set_logged_signals(self, signals: list[str]):
|
|
298
|
+
new_logging = list(dict.fromkeys(signals))
|
|
299
|
+
if set(self.project_state.logging) == set(new_logging):
|
|
300
|
+
return
|
|
301
|
+
self.project_state.logging = new_logging
|
|
302
|
+
self.make_dirty()
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
|