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,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)
|