virtualmodelcontrol 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.
- virtualmodelcontrol/__init__.py +75 -0
- virtualmodelcontrol/_version.py +24 -0
- virtualmodelcontrol/compiler.py +158 -0
- virtualmodelcontrol/control/__init__.py +5 -0
- virtualmodelcontrol/control/controller.py +76 -0
- virtualmodelcontrol/core/__init__.py +24 -0
- virtualmodelcontrol/core/params.py +244 -0
- virtualmodelcontrol/core/registry.py +54 -0
- virtualmodelcontrol/core/signals.py +46 -0
- virtualmodelcontrol/core/space.py +162 -0
- virtualmodelcontrol/core/symbolic.py +67 -0
- virtualmodelcontrol/core/units.py +34 -0
- virtualmodelcontrol/dynamics.py +149 -0
- virtualmodelcontrol/mechanisms/__init__.py +67 -0
- virtualmodelcontrol/mechanisms/components/__init__.py +33 -0
- virtualmodelcontrol/mechanisms/components/base.py +71 -0
- virtualmodelcontrol/mechanisms/components/dissipation.py +50 -0
- virtualmodelcontrol/mechanisms/components/inertance.py +56 -0
- virtualmodelcontrol/mechanisms/components/sources.py +63 -0
- virtualmodelcontrol/mechanisms/components/storage.py +272 -0
- virtualmodelcontrol/mechanisms/coordinates/__init__.py +24 -0
- virtualmodelcontrol/mechanisms/coordinates/base.py +117 -0
- virtualmodelcontrol/mechanisms/coordinates/frames.py +62 -0
- virtualmodelcontrol/mechanisms/coordinates/joints.py +51 -0
- virtualmodelcontrol/mechanisms/coordinates/ops.py +146 -0
- virtualmodelcontrol/mechanisms/coordinates/references.py +43 -0
- virtualmodelcontrol/mechanisms/mechanism.py +88 -0
- virtualmodelcontrol/models/__init__.py +22 -0
- virtualmodelcontrol/models/actuation.py +196 -0
- virtualmodelcontrol/models/assembly.py +154 -0
- virtualmodelcontrol/models/continuum/__init__.py +5 -0
- virtualmodelcontrol/models/continuum/pcc.py +120 -0
- virtualmodelcontrol/models/kinematic.py +41 -0
- virtualmodelcontrol/models/rigid/__init__.py +6 -0
- virtualmodelcontrol/models/rigid/couplings.py +64 -0
- virtualmodelcontrol/models/rigid/poe.py +95 -0
- virtualmodelcontrol/py.typed +0 -0
- virtualmodelcontrol/robots/__init__.py +5 -0
- virtualmodelcontrol/robots/adapt.py +98 -0
- virtualmodelcontrol/robots/helyx.py +79 -0
- virtualmodelcontrol/sim/__init__.py +7 -0
- virtualmodelcontrol/sim/model_plant.py +79 -0
- virtualmodelcontrol/sim/plant.py +40 -0
- virtualmodelcontrol/sim/run.py +73 -0
- virtualmodelcontrol/system.py +55 -0
- virtualmodelcontrol-0.1.0.dist-info/METADATA +82 -0
- virtualmodelcontrol-0.1.0.dist-info/RECORD +49 -0
- virtualmodelcontrol-0.1.0.dist-info/WHEEL +4 -0
- virtualmodelcontrol-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""The run loop: one loop for simulated and real plants, with a guard and a recorder."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from ..core.signals import Signals
|
|
11
|
+
|
|
12
|
+
MEASUREMENTS = ("motor_position", "motor_velocity")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SimClock:
|
|
17
|
+
"""Simulated time: each step advances the plant by ``dt`` [s]."""
|
|
18
|
+
|
|
19
|
+
dt: float
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Guard:
|
|
24
|
+
"""Sends zero torque when a measurement is missing, flagged invalid, NaN or Inf."""
|
|
25
|
+
|
|
26
|
+
required: tuple[str, ...] = MEASUREMENTS
|
|
27
|
+
trips: int = 0
|
|
28
|
+
|
|
29
|
+
def ok(self, meas: Signals) -> bool:
|
|
30
|
+
"""True if every required measurement is valid."""
|
|
31
|
+
good = all(meas.is_valid(name) for name in self.required)
|
|
32
|
+
self.trips += not good
|
|
33
|
+
return good
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class RunLog:
|
|
38
|
+
"""Recorded signals of a run, one row per step; ``arrays`` stacks them."""
|
|
39
|
+
|
|
40
|
+
rows: dict[str, list[np.ndarray]] = field(default_factory=dict)
|
|
41
|
+
|
|
42
|
+
def append(self, **values: Any) -> None:
|
|
43
|
+
"""Add one row of named values."""
|
|
44
|
+
for name, value in values.items():
|
|
45
|
+
self.rows.setdefault(name, []).append(np.atleast_1d(np.asarray(value, dtype=float)))
|
|
46
|
+
|
|
47
|
+
def arrays(self) -> dict[str, np.ndarray]:
|
|
48
|
+
"""Each signal as an (n_steps, ...) array."""
|
|
49
|
+
return {name: np.array(rows) for name, rows in self.rows.items()}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def run(
|
|
53
|
+
plant: Any, controller: Any, clock: SimClock, T: float, guard: Guard | None = None
|
|
54
|
+
) -> RunLog:
|
|
55
|
+
"""Run ``controller`` on a simulated ``plant`` for ``T`` [s]; returns the recorded run.
|
|
56
|
+
|
|
57
|
+
Each step reads the plant, asks the controller for a command (zero torque if the guard
|
|
58
|
+
trips), writes it and advances the plant by ``clock.dt``.
|
|
59
|
+
"""
|
|
60
|
+
guard = Guard() if guard is None else guard
|
|
61
|
+
log = RunLog()
|
|
62
|
+
meas = plant.read()
|
|
63
|
+
controller.reset(plant.t, meas)
|
|
64
|
+
for _ in range(round(T / clock.dt)):
|
|
65
|
+
meas = plant.read()
|
|
66
|
+
if guard.ok(meas):
|
|
67
|
+
cmd = controller.step(plant.t, meas)
|
|
68
|
+
else:
|
|
69
|
+
cmd = Signals(plant.t, motor_torque=np.zeros_like(plant.u))
|
|
70
|
+
plant.write(cmd)
|
|
71
|
+
log.append(t=plant.t, motor_torque=cmd["motor_torque"], **{n: meas[n] for n in meas.names})
|
|
72
|
+
plant.advance(clock.dt)
|
|
73
|
+
return log
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""A robot and the virtual mechanism that controls it, as one system."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .core.params import ParamSet
|
|
8
|
+
from .mechanisms.components.base import Component
|
|
9
|
+
from .mechanisms.coordinates.joints import State
|
|
10
|
+
from .mechanisms.mechanism import Mechanism
|
|
11
|
+
from .models.actuation import Direct
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VirtualMechanismSystem:
|
|
15
|
+
"""A robot mechanism (with a kinematic model) and the virtual mechanism attached to it.
|
|
16
|
+
|
|
17
|
+
Params are named ``<mechanism>.<name>``; a Param shared by the robot and the controller keeps
|
|
18
|
+
the robot's name. ``couplings`` is an optional third mechanism, treated like the controller.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self, robot: Mechanism, virtual: Mechanism, couplings: Mechanism | None = None
|
|
23
|
+
) -> None:
|
|
24
|
+
if robot.model is None:
|
|
25
|
+
raise ValueError(f"robot {robot.name!r} needs a kinematic model")
|
|
26
|
+
self.robot = robot
|
|
27
|
+
self.virtual = virtual
|
|
28
|
+
self.couplings = couplings
|
|
29
|
+
self.actuation: Any = robot.actuation if robot.actuation is not None else Direct()
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def controllers(self) -> list[Mechanism]:
|
|
33
|
+
"""The virtual mechanism and the couplings, if any."""
|
|
34
|
+
return [m for m in (self.virtual, self.couplings) if m is not None]
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def params(self) -> ParamSet:
|
|
38
|
+
"""Every Param of the system, robot first."""
|
|
39
|
+
ps = ParamSet()
|
|
40
|
+
for mechanism in (self.robot, *self.controllers):
|
|
41
|
+
ps.merge(mechanism.params, mechanism.name)
|
|
42
|
+
return ps
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def states(self) -> list[State]:
|
|
46
|
+
"""Virtual degrees of freedom, in order."""
|
|
47
|
+
return [s for m in self.controllers for s in m.states.values()]
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def components(self) -> list[tuple[str, Component]]:
|
|
51
|
+
"""Controller components as (``mechanism.name``, component)."""
|
|
52
|
+
return [(f"{m.name}.{n}", c) for m in self.controllers for n, c in m.components.items()]
|
|
53
|
+
|
|
54
|
+
def __repr__(self) -> str:
|
|
55
|
+
return f"VirtualMechanismSystem({self.robot.name!r}, {self.virtual.name!r})"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: virtualmodelcontrol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Virtual Model Control: robots and controllers as mechanisms of springs, dampers and inertances, compiled with CasADi.
|
|
5
|
+
Project-URL: Repository, https://github.com/vigno0405/VirtualModelControl
|
|
6
|
+
Project-URL: Documentation, https://vigno0405.github.io/VirtualModelControl/
|
|
7
|
+
Author: Lorenzo Vignoli
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: casadi,control,passivity,robotics,soft robots,virtual model control
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: casadi>=3.6
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Requires-Dist: scipy>=1.11
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: import-linter>=2.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
23
|
+
Requires-Dist: pre-commit>=3.5; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
26
|
+
Provides-Extra: docs
|
|
27
|
+
Requires-Dist: myst-nb>=1.1; extra == 'docs'
|
|
28
|
+
Requires-Dist: pydata-sphinx-theme>=0.15; extra == 'docs'
|
|
29
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == 'docs'
|
|
30
|
+
Requires-Dist: sphinx-design>=0.5; extra == 'docs'
|
|
31
|
+
Requires-Dist: sphinx>=7.2; extra == 'docs'
|
|
32
|
+
Provides-Extra: viz
|
|
33
|
+
Requires-Dist: matplotlib>=3.8; extra == 'viz'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# VirtualModelControl
|
|
37
|
+
|
|
38
|
+
`virtualmodelcontrol` is a Python library for Virtual Model Control (VMC). Robots and
|
|
39
|
+
controllers are described as mechanisms: coordinates plus springs, dampers, inertances and
|
|
40
|
+
sources. One CasADi model serves the real-time controller, simulation and optimization.
|
|
41
|
+
|
|
42
|
+
A collaboration between EPFL (Prof. Josie Hughes) and the University of Cambridge
|
|
43
|
+
(Prof. Fulvio Forni).
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
Python ≥ 3.10 on Linux, macOS or Windows:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install virtualmodelcontrol
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then `import virtualmodelcontrol as vmc`. The latest development version installs with
|
|
54
|
+
`pip install "git+https://github.com/vigno0405/VirtualModelControl.git"`.
|
|
55
|
+
|
|
56
|
+
## Develop
|
|
57
|
+
|
|
58
|
+
On Linux or macOS (Windows: see the install page in the docs):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/vigno0405/VirtualModelControl.git
|
|
62
|
+
cd VirtualModelControl
|
|
63
|
+
python3 -m venv .venv
|
|
64
|
+
.venv/bin/pip install -e ".[dev,docs]"
|
|
65
|
+
.venv/bin/python -m pytest
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
On Debian/Ubuntu, `python3 -m venv` needs the `python3-venv` package
|
|
69
|
+
(`sudo apt install python3-venv`); a conda environment works too. If ROS 2 is sourced in your
|
|
70
|
+
shell, prefix these commands with `env -u PYTHONPATH` so ROS's Python packages stay out of the
|
|
71
|
+
environment.
|
|
72
|
+
|
|
73
|
+
## Documentation
|
|
74
|
+
|
|
75
|
+
https://vigno0405.github.io/VirtualModelControl/
|
|
76
|
+
|
|
77
|
+
To build it locally: `sphinx-build docs docs/_build/html`, then open
|
|
78
|
+
`docs/_build/html/index.html`.
|
|
79
|
+
|
|
80
|
+
## Citing
|
|
81
|
+
|
|
82
|
+
See `CITATION.cff`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
virtualmodelcontrol/__init__.py,sha256=E0QJMB_Ku0DLj06SSBGgnXUImzGIstTgBXe3e6LHoGw,1505
|
|
2
|
+
virtualmodelcontrol/_version.py,sha256=n_5vdJsPNu7wZ57LGuRL585uvll-hiuvZUBWzdG0RQU,520
|
|
3
|
+
virtualmodelcontrol/compiler.py,sha256=97qhVBamIvYBDAf2HxP9WR3GeGXsVkQFWGdsvExYNzo,5876
|
|
4
|
+
virtualmodelcontrol/dynamics.py,sha256=YWplGRVqtyH4xKWNms9DcmHzhOs9_Ec4zwkmJ-fLDNM,5416
|
|
5
|
+
virtualmodelcontrol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
virtualmodelcontrol/system.py,sha256=gfcDmffB7xpUJphtw4Wr31M7kdjI-ruYIStRzPM8GRo,2083
|
|
7
|
+
virtualmodelcontrol/control/__init__.py,sha256=NmW5vQN8UcJlhDW3V4fc37LZYSnJ6XyL7E82fUSEhls,135
|
|
8
|
+
virtualmodelcontrol/control/controller.py,sha256=ZLh1xxjgeGQRctDcYNsG1Ku5YateKK_iolNsqC1wsfc,3128
|
|
9
|
+
virtualmodelcontrol/core/__init__.py,sha256=qxsDqaGE_6I2De9AGzWxPCMuT2KzGuShVfizdu1HkGY,527
|
|
10
|
+
virtualmodelcontrol/core/params.py,sha256=C5BqagsGLay-aSOEQqCGiuMvFVF0X0pCFu0k-YOgzWI,9108
|
|
11
|
+
virtualmodelcontrol/core/registry.py,sha256=8IWfZJB1cGyygET-qqJsi_nH_BdiSiLtWqu9DeYlO68,1651
|
|
12
|
+
virtualmodelcontrol/core/signals.py,sha256=5fT6iiOk4UBV2ZUoD1gGJMrCG8rTz6yMzc-RwOPTSd0,1617
|
|
13
|
+
virtualmodelcontrol/core/space.py,sha256=xG59fXBoLkA1FHGCyvrPgWty98yCxhtLFOyj4Cvy0vk,5182
|
|
14
|
+
virtualmodelcontrol/core/symbolic.py,sha256=FxWm6hGFNjD6X6rQSjUyI3dJaDMN2z-Bi6SXud-OgAE,2151
|
|
15
|
+
virtualmodelcontrol/core/units.py,sha256=XJSY1_2toVh_rSPVcH2vUfPlEsf6LGTB-fpB-hd8xnc,1006
|
|
16
|
+
virtualmodelcontrol/mechanisms/__init__.py,sha256=ooJV38z81RHdstT6zIbtT4kFl_VeNbIEwp_qZs0cRUU,1084
|
|
17
|
+
virtualmodelcontrol/mechanisms/mechanism.py,sha256=KQ8WQWkN3UE-GVwPGeDTpL4s_CgEslAv_6uzKGxYrKM,3615
|
|
18
|
+
virtualmodelcontrol/mechanisms/components/__init__.py,sha256=a5Fm56UFgVuhRrQgRLdE_X2KeBYNeAg8BFz_AxX_iHM,728
|
|
19
|
+
virtualmodelcontrol/mechanisms/components/base.py,sha256=vq3irvmneUZf8uq_gFuI1uyDWhdayZlj-3JvLNY78J0,2590
|
|
20
|
+
virtualmodelcontrol/mechanisms/components/dissipation.py,sha256=E0AH4T47wh6B0dUGfPS5mpDN_7LWI4nE6oL5Ouc9i44,1685
|
|
21
|
+
virtualmodelcontrol/mechanisms/components/inertance.py,sha256=uTO8d25WTiclbIhynvyrqjQSJOt9f5TMxKe0uUenonc,1744
|
|
22
|
+
virtualmodelcontrol/mechanisms/components/sources.py,sha256=-tU3o_pIG6BtH2nZyjAgwXkBwDLEfTrMQut8dRE5UjM,2309
|
|
23
|
+
virtualmodelcontrol/mechanisms/components/storage.py,sha256=nAe76S8L_aZtDuB_pm2BATYo2o03Tusdu2epsl-n9SU,10300
|
|
24
|
+
virtualmodelcontrol/mechanisms/coordinates/__init__.py,sha256=bTY7pyy9MRrol5A3EKJqh9Ra0xx532HzBCE4S7tBx4U,540
|
|
25
|
+
virtualmodelcontrol/mechanisms/coordinates/base.py,sha256=81WIiLpE017YX5FeiC-pHnHUdKR5kU2oE4IN9BylMDY,3661
|
|
26
|
+
virtualmodelcontrol/mechanisms/coordinates/frames.py,sha256=nGuLSAQowICJ_-uNx8H9krK3GXxYMKOHDRaPLir3NlU,2156
|
|
27
|
+
virtualmodelcontrol/mechanisms/coordinates/joints.py,sha256=aCj7nXBppYNpPWLY6O4cXTVXOUskm7iPkR36m04bd_Q,1715
|
|
28
|
+
virtualmodelcontrol/mechanisms/coordinates/ops.py,sha256=DpvbLbWAmddhKZpcio2gtO3cJcgtl2XIxkMZL7sudU4,4972
|
|
29
|
+
virtualmodelcontrol/mechanisms/coordinates/references.py,sha256=KXiT3UVtznfqgHDfM8a0AnipTw_fg7nVVlodVM8eBw0,1305
|
|
30
|
+
virtualmodelcontrol/models/__init__.py,sha256=A3_HwU2p5S5XuvwSaY--PRp6EJBeNmjV0tf70n1Gl7g,561
|
|
31
|
+
virtualmodelcontrol/models/actuation.py,sha256=PDKRUmPZloD8mq2gDZzKg-lp7kPVvhPh4i4CNmadKEc,7171
|
|
32
|
+
virtualmodelcontrol/models/assembly.py,sha256=QGmdDWcvUlpfC-uKaM-SCS-aal8GD-MhUNZRRK9Lb8A,6884
|
|
33
|
+
virtualmodelcontrol/models/kinematic.py,sha256=oKgEpk9PAqYkyoCMdkJEVhptMqUVUa1-k2MohdS4jtk,1422
|
|
34
|
+
virtualmodelcontrol/models/continuum/__init__.py,sha256=pkrNcvrQ93Ly86iS5lgNGN0r1c__N57aX0Uw9nDHKyM,97
|
|
35
|
+
virtualmodelcontrol/models/continuum/pcc.py,sha256=V0HJJWz73p1BnSXQVCbczOAEdu1fyOmMfKyhdsr9wUE,4977
|
|
36
|
+
virtualmodelcontrol/models/rigid/__init__.py,sha256=-THPuFfle_2vjeZe-nu2lG0xi31pztvBS2jQTkI6HEQ,133
|
|
37
|
+
virtualmodelcontrol/models/rigid/couplings.py,sha256=E7BO8v5kLg2mC2QxekrLpklXakzJsNRbrYatMpRUO9E,2363
|
|
38
|
+
virtualmodelcontrol/models/rigid/poe.py,sha256=VVWzcAcZF6fojeoNSk1SErGM8mTmUK_VL4EIvbhaL8o,3927
|
|
39
|
+
virtualmodelcontrol/robots/__init__.py,sha256=tdOyxcvni3revezROiWDV8eqX9X2GR5gPd_a2Olkrco,136
|
|
40
|
+
virtualmodelcontrol/robots/adapt.py,sha256=U0a11gV37xbW2Krn3XquAZ4fqUO_HaDO_S2by5nRpUY,3546
|
|
41
|
+
virtualmodelcontrol/robots/helyx.py,sha256=fagNg8iEU3evxa9AiOjLDZFkvya3gE8eKx6HXO7eh8Q,3604
|
|
42
|
+
virtualmodelcontrol/sim/__init__.py,sha256=khTS2enjoYrbXRfgrFTLLLVcgEwvqY9wO_ZvfWLIViI,291
|
|
43
|
+
virtualmodelcontrol/sim/model_plant.py,sha256=y5P7XUjk5_mlUC5DA1gXf6DOMFYkAqM6ALIVls8CL00,2805
|
|
44
|
+
virtualmodelcontrol/sim/plant.py,sha256=pG_e1Qn9ozAysSecMSHG9HgezRU1dyPXL49lM4kpnL4,1122
|
|
45
|
+
virtualmodelcontrol/sim/run.py,sha256=t4L0_KCsNO931GZAKVp2H9Q9u4wtl6K2X-sC6xmWxpg,2285
|
|
46
|
+
virtualmodelcontrol-0.1.0.dist-info/METADATA,sha256=fXt-VpyvUyPMxt4ruSrG2vM25Fqz5-bTZw7PRghpKws,2843
|
|
47
|
+
virtualmodelcontrol-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
48
|
+
virtualmodelcontrol-0.1.0.dist-info/licenses/LICENSE,sha256=0stuzGeBCLbI3KFHplc0nCKgO67svcYW-vIMw3I3Isw,1072
|
|
49
|
+
virtualmodelcontrol-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lorenzo Vignoli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|