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.
Files changed (262) hide show
  1. examples/advanced/hardware/block_diagram.py +162 -0
  2. examples/advanced/hardware/camera.py +93 -0
  3. examples/advanced/hardware/gui_emio.py +231 -0
  4. examples/advanced/hardware/lmi.py +69 -0
  5. examples/advanced/hardware/main.py +57 -0
  6. examples/advanced/hardware/parameters.py +54 -0
  7. examples/advanced/qp_problem/params.py +10 -0
  8. examples/advanced/qp_problem/run.py +14 -0
  9. examples/advanced/sofa/gui/finger/Finger.py +108 -0
  10. examples/advanced/sofa/gui/finger/FingerController.py +53 -0
  11. examples/advanced/sofa/gui/finger/parameters_auto.py +22 -0
  12. examples/advanced/sofa/gui/sofa_plant/run.py +14 -0
  13. examples/advanced/sofa/python/Finger.py +107 -0
  14. examples/advanced/sofa/python/FingerController.py +48 -0
  15. examples/advanced/sofa/python/closedloop.py +64 -0
  16. examples/advanced/sofa/python/openloop.py +58 -0
  17. examples/basics/algebraic_function/g.py +7 -0
  18. examples/basics/algebraic_function/run.py +14 -0
  19. examples/basics/dc_motor/gui/run.py +14 -0
  20. examples/basics/dc_motor/multiple_order.py +91 -0
  21. examples/basics/dc_motor/openloop.py +62 -0
  22. examples/basics/dc_motor/pi.py +99 -0
  23. examples/basics/dc_motor/pid.py +118 -0
  24. examples/basics/dc_motor/pid_block.py +82 -0
  25. examples/basics/dc_motor/pid_block_vs_manual.py +143 -0
  26. examples/basics/external/parameters.py +4 -0
  27. examples/basics/external/run.py +14 -0
  28. examples/basics/file_sources/temp.py +16 -0
  29. examples/basics/observer.py +76 -0
  30. examples/basics/state_feedback/gui/parameters.py +9 -0
  31. examples/basics/state_feedback/gui/run.py +14 -0
  32. examples/basics/state_feedback/state_feedback.py +102 -0
  33. examples/basics/state_space/linear/gui/run.py +14 -0
  34. examples/basics/state_space/linear/ss_block.py +89 -0
  35. examples/basics/state_space/linear/ss_manual.py +105 -0
  36. examples/basics/state_space/non_linear/nlss.py +47 -0
  37. examples/basics/state_space/non_linear/run.py +14 -0
  38. examples/basics/state_space/polytope/params.py +39 -0
  39. examples/basics/state_space/polytope/weights.py +12 -0
  40. examples/comparison/simulink/convert.py +29 -0
  41. examples/comparison/simulink/openloop.py +127 -0
  42. examples/comparison/simulink/pi.py +148 -0
  43. examples/comparison/simulink/pid.py +154 -0
  44. examples/comparison/simulink/pid_backward.py +155 -0
  45. examples/quick_start/filter.py +35 -0
  46. examples/quick_start/gui/run.py +14 -0
  47. examples/tutorials/tutorial_1_python/main.py +99 -0
  48. examples/tutorials/tutorial_3_sofa/finger/Finger.py +120 -0
  49. examples/tutorials/tutorial_3_sofa/finger/FingerController.py +37 -0
  50. pySimBlocks/__init__.py +35 -0
  51. pySimBlocks/blocks/__init__.py +68 -0
  52. pySimBlocks/blocks/controllers/__init__.py +27 -0
  53. pySimBlocks/blocks/controllers/pid.py +250 -0
  54. pySimBlocks/blocks/controllers/state_feedback.py +141 -0
  55. pySimBlocks/blocks/interfaces/__init__.py +27 -0
  56. pySimBlocks/blocks/interfaces/external_input.py +107 -0
  57. pySimBlocks/blocks/interfaces/external_output.py +113 -0
  58. pySimBlocks/blocks/observers/__init__.py +23 -0
  59. pySimBlocks/blocks/observers/luenberger.py +175 -0
  60. pySimBlocks/blocks/operators/__init__.py +47 -0
  61. pySimBlocks/blocks/operators/algebraic_function.py +258 -0
  62. pySimBlocks/blocks/operators/dead_zone.py +186 -0
  63. pySimBlocks/blocks/operators/delay.py +254 -0
  64. pySimBlocks/blocks/operators/demux.py +123 -0
  65. pySimBlocks/blocks/operators/discrete_derivator.py +237 -0
  66. pySimBlocks/blocks/operators/discrete_integrator.py +265 -0
  67. pySimBlocks/blocks/operators/gain.py +294 -0
  68. pySimBlocks/blocks/operators/mux.py +131 -0
  69. pySimBlocks/blocks/operators/product.py +194 -0
  70. pySimBlocks/blocks/operators/rate_limiter.py +214 -0
  71. pySimBlocks/blocks/operators/saturation.py +178 -0
  72. pySimBlocks/blocks/operators/sum.py +188 -0
  73. pySimBlocks/blocks/operators/zero_order_hold.py +141 -0
  74. pySimBlocks/blocks/optimizers/__init__.py +23 -0
  75. pySimBlocks/blocks/optimizers/quadratic_program.py +326 -0
  76. pySimBlocks/blocks/sources/__init__.py +37 -0
  77. pySimBlocks/blocks/sources/chirp.py +168 -0
  78. pySimBlocks/blocks/sources/constant.py +82 -0
  79. pySimBlocks/blocks/sources/file_source.py +294 -0
  80. pySimBlocks/blocks/sources/function_source.py +197 -0
  81. pySimBlocks/blocks/sources/ramp.py +99 -0
  82. pySimBlocks/blocks/sources/sinusoidal.py +113 -0
  83. pySimBlocks/blocks/sources/step.py +138 -0
  84. pySimBlocks/blocks/sources/white_noise.py +107 -0
  85. pySimBlocks/blocks/systems/__init__.py +27 -0
  86. pySimBlocks/blocks/systems/linear_state_space.py +180 -0
  87. pySimBlocks/blocks/systems/non_linear_state_space.py +309 -0
  88. pySimBlocks/blocks/systems/polytopic_state_space.py +193 -0
  89. pySimBlocks/blocks/systems/sofa/__init__.py +30 -0
  90. pySimBlocks/blocks/systems/sofa/sofa_controller.py +472 -0
  91. pySimBlocks/blocks/systems/sofa/sofa_exchange_i_o.py +120 -0
  92. pySimBlocks/blocks/systems/sofa/sofa_plant.py +306 -0
  93. pySimBlocks/cli.py +127 -0
  94. pySimBlocks/core/__init__.py +32 -0
  95. pySimBlocks/core/block.py +167 -0
  96. pySimBlocks/core/block_source.py +94 -0
  97. pySimBlocks/core/config.py +90 -0
  98. pySimBlocks/core/fixed_time_manager.py +46 -0
  99. pySimBlocks/core/model.py +250 -0
  100. pySimBlocks/core/scheduler.py +30 -0
  101. pySimBlocks/core/simulator.py +329 -0
  102. pySimBlocks/core/task.py +73 -0
  103. pySimBlocks/docs/blocks/controllers/pid.md +78 -0
  104. pySimBlocks/docs/blocks/controllers/state_feedback.md +60 -0
  105. pySimBlocks/docs/blocks/interfaces/external_input.md +39 -0
  106. pySimBlocks/docs/blocks/interfaces/external_output.md +39 -0
  107. pySimBlocks/docs/blocks/observers/luenberger.md +70 -0
  108. pySimBlocks/docs/blocks/operators/algebraic_function.md +76 -0
  109. pySimBlocks/docs/blocks/operators/dead_zone.md +67 -0
  110. pySimBlocks/docs/blocks/operators/delay.md +76 -0
  111. pySimBlocks/docs/blocks/operators/demux.md +64 -0
  112. pySimBlocks/docs/blocks/operators/discrete_derivator.md +61 -0
  113. pySimBlocks/docs/blocks/operators/discrete_integrator.md +87 -0
  114. pySimBlocks/docs/blocks/operators/gain.md +97 -0
  115. pySimBlocks/docs/blocks/operators/mux.md +64 -0
  116. pySimBlocks/docs/blocks/operators/product.md +51 -0
  117. pySimBlocks/docs/blocks/operators/rate_limiter.md +70 -0
  118. pySimBlocks/docs/blocks/operators/saturation.md +62 -0
  119. pySimBlocks/docs/blocks/operators/sum.md +49 -0
  120. pySimBlocks/docs/blocks/operators/zero_order_hold.md +61 -0
  121. pySimBlocks/docs/blocks/optimizers/quadratic_program.md +98 -0
  122. pySimBlocks/docs/blocks/sources/chirp.md +102 -0
  123. pySimBlocks/docs/blocks/sources/constant.md +53 -0
  124. pySimBlocks/docs/blocks/sources/file_source.md +51 -0
  125. pySimBlocks/docs/blocks/sources/function_source.md +52 -0
  126. pySimBlocks/docs/blocks/sources/ramp.md +58 -0
  127. pySimBlocks/docs/blocks/sources/sinusoidal.md +60 -0
  128. pySimBlocks/docs/blocks/sources/step.md +63 -0
  129. pySimBlocks/docs/blocks/sources/white_noise.md +58 -0
  130. pySimBlocks/docs/blocks/systems/linear_state_space.md +66 -0
  131. pySimBlocks/docs/blocks/systems/non_linear_state_space.md +90 -0
  132. pySimBlocks/docs/blocks/systems/polytopic_state_space.md +72 -0
  133. pySimBlocks/docs/blocks/systems/sofa/sofa_exchange_i_o.md +63 -0
  134. pySimBlocks/docs/blocks/systems/sofa/sofa_plant.md +62 -0
  135. pySimBlocks/gui/__init__.py +20 -0
  136. pySimBlocks/gui/addons/__init__.py +19 -0
  137. pySimBlocks/gui/addons/sofa/__init__.py +19 -0
  138. pySimBlocks/gui/addons/sofa/sofa_dialog.py +190 -0
  139. pySimBlocks/gui/addons/sofa/sofa_service.py +209 -0
  140. pySimBlocks/gui/blocks/block_dialog_session.py +47 -0
  141. pySimBlocks/gui/blocks/block_meta.py +408 -0
  142. pySimBlocks/gui/blocks/controllers/pid.py +116 -0
  143. pySimBlocks/gui/blocks/controllers/state_feedback.py +80 -0
  144. pySimBlocks/gui/blocks/interfaces/external_input.py +64 -0
  145. pySimBlocks/gui/blocks/interfaces/external_output.py +63 -0
  146. pySimBlocks/gui/blocks/observers/luenberger.py +108 -0
  147. pySimBlocks/gui/blocks/operators/algebraic_function.py +232 -0
  148. pySimBlocks/gui/blocks/operators/dead_zone.py +79 -0
  149. pySimBlocks/gui/blocks/operators/delay.py +79 -0
  150. pySimBlocks/gui/blocks/operators/demux.py +98 -0
  151. pySimBlocks/gui/blocks/operators/discrete_derivator.py +67 -0
  152. pySimBlocks/gui/blocks/operators/discrete_integrator.py +80 -0
  153. pySimBlocks/gui/blocks/operators/gain.py +79 -0
  154. pySimBlocks/gui/blocks/operators/mux.py +100 -0
  155. pySimBlocks/gui/blocks/operators/product.py +109 -0
  156. pySimBlocks/gui/blocks/operators/rate_limiter.py +79 -0
  157. pySimBlocks/gui/blocks/operators/saturation.py +72 -0
  158. pySimBlocks/gui/blocks/operators/sum.py +91 -0
  159. pySimBlocks/gui/blocks/operators/zero_order_hold.py +64 -0
  160. pySimBlocks/gui/blocks/optimizers/quadratic_program.py +128 -0
  161. pySimBlocks/gui/blocks/parameter_meta.py +34 -0
  162. pySimBlocks/gui/blocks/port_meta.py +31 -0
  163. pySimBlocks/gui/blocks/sources/chirp.py +146 -0
  164. pySimBlocks/gui/blocks/sources/constant.py +63 -0
  165. pySimBlocks/gui/blocks/sources/file_source.py +196 -0
  166. pySimBlocks/gui/blocks/sources/function_source.py +202 -0
  167. pySimBlocks/gui/blocks/sources/ramp.py +73 -0
  168. pySimBlocks/gui/blocks/sources/sinusoidal.py +81 -0
  169. pySimBlocks/gui/blocks/sources/step.py +83 -0
  170. pySimBlocks/gui/blocks/sources/white_noise.py +70 -0
  171. pySimBlocks/gui/blocks/systems/linear_state_space.py +102 -0
  172. pySimBlocks/gui/blocks/systems/non_linear_state_space.py +233 -0
  173. pySimBlocks/gui/blocks/systems/polytopic_state_space.py +109 -0
  174. pySimBlocks/gui/blocks/systems/sofa/sofa_exchange_i_o.py +221 -0
  175. pySimBlocks/gui/blocks/systems/sofa/sofa_plant.py +220 -0
  176. pySimBlocks/gui/dialogs/__init__.py +19 -0
  177. pySimBlocks/gui/dialogs/block_dialog.py +125 -0
  178. pySimBlocks/gui/dialogs/display_yaml_dialog.py +89 -0
  179. pySimBlocks/gui/dialogs/help_dialog.py +39 -0
  180. pySimBlocks/gui/dialogs/plot_dialog.py +230 -0
  181. pySimBlocks/gui/dialogs/settings/plots.py +187 -0
  182. pySimBlocks/gui/dialogs/settings/project.py +146 -0
  183. pySimBlocks/gui/dialogs/settings/simulation.py +137 -0
  184. pySimBlocks/gui/dialogs/settings_dialog.py +88 -0
  185. pySimBlocks/gui/dialogs/unsaved_dialog.py +92 -0
  186. pySimBlocks/gui/editor.py +49 -0
  187. pySimBlocks/gui/graphics/__init__.py +29 -0
  188. pySimBlocks/gui/graphics/block_item.py +289 -0
  189. pySimBlocks/gui/graphics/connection_item.py +266 -0
  190. pySimBlocks/gui/graphics/port_item.py +153 -0
  191. pySimBlocks/gui/graphics/theme.py +119 -0
  192. pySimBlocks/gui/main_window.py +162 -0
  193. pySimBlocks/gui/models/__init__.py +31 -0
  194. pySimBlocks/gui/models/block_instance.py +114 -0
  195. pySimBlocks/gui/models/connection_instance.py +43 -0
  196. pySimBlocks/gui/models/port_instance.py +52 -0
  197. pySimBlocks/gui/models/project_simulation_params.py +50 -0
  198. pySimBlocks/gui/models/project_state.py +114 -0
  199. pySimBlocks/gui/project_controller.py +302 -0
  200. pySimBlocks/gui/services/__init__.py +20 -0
  201. pySimBlocks/gui/services/project_loader.py +381 -0
  202. pySimBlocks/gui/services/project_saver.py +68 -0
  203. pySimBlocks/gui/services/simulation_runner.py +66 -0
  204. pySimBlocks/gui/services/yaml_tools.py +261 -0
  205. pySimBlocks/gui/widgets/__init__.py +19 -0
  206. pySimBlocks/gui/widgets/block_list.py +131 -0
  207. pySimBlocks/gui/widgets/diagram_view.py +312 -0
  208. pySimBlocks/gui/widgets/toolbar_view.py +161 -0
  209. pySimBlocks/project/__init__.py +35 -0
  210. pySimBlocks/project/build_model.py +92 -0
  211. pySimBlocks/project/generate_run_script.py +109 -0
  212. pySimBlocks/project/generate_sofa_controller.py +215 -0
  213. pySimBlocks/project/load_project_config.py +197 -0
  214. pySimBlocks/project/load_simulation_config.py +166 -0
  215. pySimBlocks/project/load_simulator.py +45 -0
  216. pySimBlocks/project/plot_from_config.py +174 -0
  217. pySimBlocks/project/pySimBlocks_blocks_index.yaml +106 -0
  218. pySimBlocks/real_time/__init__.py +23 -0
  219. pySimBlocks/real_time/real_time_runner.py +148 -0
  220. pySimBlocks/tools/__init__.py +19 -0
  221. pySimBlocks/tools/blocks_registry.py +141 -0
  222. pySimBlocks/tools/generate_blocks_index.py +124 -0
  223. pysimblocks-0.1.0.dist-info/METADATA +180 -0
  224. pysimblocks-0.1.0.dist-info/RECORD +262 -0
  225. pysimblocks-0.1.0.dist-info/WHEEL +5 -0
  226. pysimblocks-0.1.0.dist-info/entry_points.txt +2 -0
  227. pysimblocks-0.1.0.dist-info/licenses/LICENSE.md +165 -0
  228. pysimblocks-0.1.0.dist-info/top_level.txt +3 -0
  229. tests/blocks/controllers/test_pid.py +143 -0
  230. tests/blocks/controllers/test_state_feedback.py +193 -0
  231. tests/blocks/observers/test_luenberger.py +225 -0
  232. tests/blocks/operators/test_algebraic_function.py +230 -0
  233. tests/blocks/operators/test_dead_zone.py +106 -0
  234. tests/blocks/operators/test_delay.py +167 -0
  235. tests/blocks/operators/test_demux.py +111 -0
  236. tests/blocks/operators/test_discrete_derivator.py +160 -0
  237. tests/blocks/operators/test_discrete_integrator.py +149 -0
  238. tests/blocks/operators/test_gain.py +182 -0
  239. tests/blocks/operators/test_mux.py +101 -0
  240. tests/blocks/operators/test_product.py +171 -0
  241. tests/blocks/operators/test_rate_limiter.py +166 -0
  242. tests/blocks/operators/test_saturation.py +157 -0
  243. tests/blocks/operators/test_sum.py +183 -0
  244. tests/blocks/operators/test_zero_order_hold.py +106 -0
  245. tests/blocks/sources/test_chirp.py +157 -0
  246. tests/blocks/sources/test_constant.py +55 -0
  247. tests/blocks/sources/test_file_source.py +185 -0
  248. tests/blocks/sources/test_function_source.py +135 -0
  249. tests/blocks/sources/test_ramp.py +110 -0
  250. tests/blocks/sources/test_sinusoidal.py +113 -0
  251. tests/blocks/sources/test_step.py +76 -0
  252. tests/blocks/sources/test_white_noise.py +108 -0
  253. tests/blocks/systems/test_linear_state_space.py +135 -0
  254. tests/blocks/systems/test_polytopic_state_space.py +133 -0
  255. tests/core/test_core_errors_and_determinism.py +82 -0
  256. tests/core/test_core_multirate_tasks.py +88 -0
  257. tests/core/test_core_two_phase_and_propagation.py +132 -0
  258. tests/gui/main_window_open_test.py +162 -0
  259. tests/gui/test_block_meta_param_cache.py +16 -0
  260. tests/gui/test_file_source_meta.py +17 -0
  261. tests/gui/test_function_source_meta.py +28 -0
  262. tests/regression/test_simulink_dc_motor.py +217 -0
@@ -0,0 +1,220 @@
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
30
+ from pySimBlocks.gui.blocks.parameter_meta import ParameterMeta
31
+ from pySimBlocks.gui.blocks.port_meta import PortMeta
32
+ from pySimBlocks.gui.models import BlockInstance, PortInstance
33
+
34
+
35
+ class SofaPlantMeta(BlockMeta):
36
+
37
+ def __init__(self):
38
+ self.name = "SofaPlant"
39
+ self.category = "systems"
40
+ self.type = "sofa_plant"
41
+ self.summary = "SOFA-based dynamic plant."
42
+ self.description = (
43
+ "Executes a SOFA simulation in a worker process."
44
+ )
45
+
46
+ self.parameters = [
47
+ ParameterMeta(
48
+ name="scene_file",
49
+ type="string",
50
+ required=True,
51
+ description="Path to the SOFA scene file, relative to the project.yaml file."
52
+ ),
53
+ ParameterMeta(
54
+ name="input_keys",
55
+ type="list[string]",
56
+ required=True,
57
+ description="List of input keys corresponding to SOFA input ports."
58
+ ),
59
+ ParameterMeta(
60
+ name="output_keys",
61
+ type="list[string]",
62
+ required=True,
63
+ description="List of output keys corresponding to SOFA output ports."
64
+ ),
65
+ ParameterMeta(
66
+ name="slider_params",
67
+ type="dict",
68
+ description="Dictionary of slider parameters to be modified in the SOFA scene at runtime."
69
+ ),
70
+ ParameterMeta(
71
+ name="sample_time",
72
+ type="float"
73
+ )
74
+ ]
75
+
76
+ self.inputs = [
77
+ PortMeta(
78
+ name="sofa_inputs",
79
+ display_as="",
80
+ shape=[]
81
+ )
82
+ ]
83
+
84
+ self.outputs = [
85
+ PortMeta(
86
+ name="sofa_outputs",
87
+ display_as="",
88
+ shape=[]
89
+ )
90
+ ]
91
+
92
+ # --------------------------------------------------------------------------
93
+ # Port Resolution
94
+ # --------------------------------------------------------------------------
95
+ def resolve_port_group(
96
+ self,
97
+ port_meta: PortMeta,
98
+ direction: Literal["input", "output"],
99
+ instance: "BlockInstance"
100
+ ) -> list["PortInstance"]:
101
+
102
+ if direction == "input" and port_meta.name == "sofa_inputs":
103
+ keys = instance.parameters.get("input_keys", [])
104
+ if keys is None:
105
+ return []
106
+ return [
107
+ PortInstance(
108
+ name=key,
109
+ display_as=key,
110
+ direction="input",
111
+ block=instance
112
+ )
113
+ for key in keys
114
+ ]
115
+
116
+ if direction == "output" and port_meta.name == "sofa_outputs":
117
+ keys = instance.parameters.get("output_keys", [])
118
+ if keys is None:
119
+ return []
120
+ return [
121
+ PortInstance(
122
+ name=key,
123
+ display_as=key,
124
+ direction="output",
125
+ block=instance
126
+ )
127
+ for key in keys
128
+ ]
129
+
130
+ return super().resolve_port_group(port_meta, direction, instance)
131
+
132
+ # --------------------------------------------------------------------------
133
+ # Dialog Methods
134
+ # --------------------------------------------------------------------------
135
+ def build_param(
136
+ self,
137
+ session,
138
+ form: QFormLayout,
139
+ readonly: bool = False,
140
+ ):
141
+ name_edit = QLineEdit(session.instance.name)
142
+ name_edit.textChanged.connect(
143
+ lambda val: self._on_param_changed(val, "name", session, readonly)
144
+ )
145
+ form.addRow(QLabel("Block name:"), name_edit)
146
+ if readonly:
147
+ name_edit.setReadOnly(True)
148
+ session.name_edit = name_edit
149
+
150
+ for pmeta in self.parameters:
151
+ if pmeta.name == "scene_file":
152
+ self.build_file_param_row(
153
+ session,
154
+ form,
155
+ pmeta,
156
+ readonly=readonly,
157
+ file_filter="SOFA scene files (*.py);;All files (*)",
158
+ )
159
+ continue
160
+
161
+ label, widget = self._create_param_row(session, pmeta, readonly)
162
+ if widget is None:
163
+ continue
164
+ if readonly:
165
+ self._set_readonly_style(widget)
166
+
167
+ form.addRow(label, widget)
168
+ session.param_widgets[pmeta.name] = widget
169
+ session.param_labels[pmeta.name] = label
170
+
171
+ # ------------------------------------------------------
172
+ def build_post_param(self, session, form: QFormLayout, readonly: bool = False):
173
+ open_btn = QPushButton("Open file")
174
+ open_btn.clicked.connect(lambda: self._open_file_from_session(session))
175
+ form.addRow(QLabel(""), open_btn)
176
+ session.open_file_btn = open_btn
177
+ self._refresh_open_button_state(session)
178
+
179
+ # ------------------------------------------------------
180
+ def refresh_form(self, session):
181
+ super().refresh_form(session)
182
+ self._refresh_open_button_state(session)
183
+
184
+ # ------------------------------------------------------
185
+ def _resolve_file_path(self, session) -> Path | None:
186
+ raw = session.local_params.get("scene_file")
187
+ if not raw:
188
+ return None
189
+
190
+ path = Path(str(raw)).expanduser()
191
+ if not path.is_absolute() and session.project_dir is not None:
192
+ path = (session.project_dir / path).resolve()
193
+ return path
194
+
195
+ # ------------------------------------------------------
196
+ def _refresh_open_button_state(self, session) -> None:
197
+ btn = getattr(session, "open_file_btn", None)
198
+ if btn is None:
199
+ return
200
+
201
+ target = self._resolve_file_path(session)
202
+ exists = target is not None and target.is_file()
203
+ btn.setEnabled(exists)
204
+ if exists:
205
+ btn.setToolTip(str(target))
206
+ else:
207
+ btn.setToolTip("Set a valid existing scene_file to open the file.")
208
+
209
+ # ------------------------------------------------------
210
+ def _open_file_from_session(self, session) -> None:
211
+ target = self._resolve_file_path(session)
212
+ if target is None or not target.is_file():
213
+ return
214
+
215
+ if sys.platform.startswith("darwin"):
216
+ subprocess.Popen(["open", str(target)])
217
+ elif os.name == "nt":
218
+ os.startfile(str(target))
219
+ else:
220
+ subprocess.Popen(["xdg-open", str(target)])
@@ -0,0 +1,19 @@
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
+ # ******************************************************************************
@@ -0,0 +1,125 @@
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
22
+
23
+ from PySide6.QtWidgets import (
24
+ QDialog,
25
+ QFormLayout,
26
+ QHBoxLayout,
27
+ QMessageBox,
28
+ QPushButton,
29
+ QVBoxLayout,
30
+ )
31
+
32
+ from pySimBlocks.gui.dialogs.help_dialog import HelpDialog
33
+
34
+ if TYPE_CHECKING:
35
+ from pySimBlocks.gui.graphics.block_item import BlockItem
36
+
37
+
38
+ class BlockDialog(QDialog):
39
+ def __init__(self,
40
+ block: 'BlockItem',
41
+ readonly: bool = False
42
+ ):
43
+ super().__init__()
44
+ self.block = block
45
+ self.meta = block.instance.meta
46
+ self.instance = block.instance
47
+
48
+ self.readonly = readonly
49
+ if self.readonly:
50
+ self.setWindowTitle(f"[{self.block.instance.name}] Information")
51
+ else:
52
+ self.setWindowTitle(f"Edit [{self.block.instance.name}] Parameters")
53
+ self.setMinimumWidth(300)
54
+
55
+ main_layout = QVBoxLayout(self)
56
+ project_dir = None
57
+ if hasattr(self.block, "view") and self.block.view is not None:
58
+ controller = getattr(self.block.view, "project_controller", None)
59
+ if controller is not None and controller.project_state is not None:
60
+ project_dir = controller.project_state.directory_path
61
+
62
+ self.session = self.meta.create_dialog_session(self.instance, project_dir)
63
+ self.build_meta_layout(main_layout)
64
+ self.build_buttons_layout(main_layout)
65
+
66
+ # --------------------------------------------------------------------------
67
+ # Dialog Layout Methods
68
+ # --------------------------------------------------------------------------
69
+ def build_meta_layout(self, layout: QVBoxLayout):
70
+ form = QFormLayout()
71
+ self.meta.build_description(form)
72
+ self.meta.build_pre_param(self.session, form, self.readonly)
73
+ self.meta.build_param(self.session, form, self.readonly)
74
+ self.meta.build_post_param(self.session, form, self.readonly)
75
+ layout.addLayout(form)
76
+ self.meta.refresh_form(self.session)
77
+
78
+ def build_buttons_layout(self, layout: QVBoxLayout):
79
+ buttons_layout = QHBoxLayout()
80
+ buttons_layout.addStretch()
81
+
82
+ ok_btn = QPushButton("Ok")
83
+ ok_btn.setDefault(True)
84
+ ok_btn.setAutoDefault(True)
85
+ ok_btn.clicked.connect(self.ok)
86
+ buttons_layout.addWidget(ok_btn)
87
+
88
+ help_btn = QPushButton("Help")
89
+ help_btn.clicked.connect(self.open_help)
90
+ buttons_layout.addWidget(help_btn)
91
+
92
+ apply_btn = QPushButton("Apply")
93
+ apply_btn.clicked.connect(self.apply)
94
+ buttons_layout.addWidget(apply_btn)
95
+
96
+ if self.readonly:
97
+ ok_btn.setEnabled(False)
98
+ apply_btn.setEnabled(False)
99
+
100
+ layout.addLayout(buttons_layout)
101
+
102
+
103
+ # --------------------------------------------------------------------------
104
+ # Button Methods
105
+ # --------------------------------------------------------------------------
106
+ def apply(self):
107
+ if self.readonly:
108
+ return
109
+
110
+ params = self.meta.gather_params(self.session)
111
+ self.block.view.update_block_param_event(self.block.instance, params)
112
+
113
+ # ------------------------------------------------------------
114
+ def ok(self):
115
+ self.apply()
116
+ self.accept()
117
+
118
+ # ------------------------------------------------------------
119
+ def open_help(self):
120
+ help_path = self.block.instance.meta.doc_path
121
+
122
+ if help_path and help_path.exists():
123
+ HelpDialog(help_path, self).exec()
124
+ else:
125
+ QMessageBox.information(self, "Help", "No documentation available.")
@@ -0,0 +1,89 @@
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 (
22
+ QDialog,
23
+ QVBoxLayout,
24
+ QTabWidget,
25
+ QTextEdit,
26
+ QPushButton,
27
+ QHBoxLayout,
28
+ )
29
+ from PySide6.QtGui import QFont
30
+
31
+ from pySimBlocks.gui.models.project_state import ProjectState
32
+ from pySimBlocks.gui.services.yaml_tools import dump_project_yaml
33
+ from pySimBlocks.gui.widgets.diagram_view import DiagramView
34
+
35
+
36
+ class DisplayYamlDialog(QDialog):
37
+ def __init__(self,
38
+ project: ProjectState,
39
+ view: DiagramView,
40
+ parent=None):
41
+ super().__init__(parent)
42
+
43
+ self.setWindowTitle("project.yaml Preview")
44
+ self.resize(900, 600)
45
+
46
+ self.project_state = project
47
+ self.view = view
48
+
49
+ main_layout = QVBoxLayout(self)
50
+
51
+ # -------------------------------------------------
52
+ # Tabs
53
+ # -------------------------------------------------
54
+ tabs = QTabWidget()
55
+
56
+ if self.view.block_items:
57
+ blocks_items = self.view.block_items
58
+ else:
59
+ blocks_items = {}
60
+ project_text = dump_project_yaml(self.project_state, blocks_items)
61
+ tabs.addTab(
62
+ self._make_code_view(project_text),
63
+ "project.yaml"
64
+ )
65
+
66
+ main_layout.addWidget(tabs)
67
+
68
+ # -------------------------------------------------
69
+ # Buttons
70
+ # -------------------------------------------------
71
+ buttons = QHBoxLayout()
72
+ buttons.addStretch()
73
+
74
+ close_btn = QPushButton("Close")
75
+ close_btn.clicked.connect(self.accept)
76
+ buttons.addWidget(close_btn)
77
+
78
+ main_layout.addLayout(buttons)
79
+
80
+ # -------------------------------------------------
81
+ # Helpers
82
+ # -------------------------------------------------
83
+ def _make_code_view(self, text:str) -> QTextEdit:
84
+ edit = QTextEdit()
85
+ edit.setReadOnly(True)
86
+ edit.setFont(QFont("Courier New", 10))
87
+ edit.setPlainText(text)
88
+ edit.setLineWrapMode(QTextEdit.NoWrap)
89
+ return edit
@@ -0,0 +1,39 @@
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 pathlib import Path
22
+ from PySide6.QtWidgets import QTextBrowser, QDialog, QVBoxLayout
23
+
24
+
25
+ class HelpDialog(QDialog):
26
+ def __init__(self, md_path: Path, parent=None):
27
+ super().__init__(parent)
28
+ self.setWindowTitle(md_path.name)
29
+ self.resize(600, 500)
30
+
31
+ layout = QVBoxLayout(self)
32
+
33
+ browser = QTextBrowser()
34
+ browser.setOpenExternalLinks(True)
35
+
36
+ with md_path.open("r") as f:
37
+ browser.setMarkdown(f.read())
38
+
39
+ layout.addWidget(browser)