drone-models 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.
- docs/gen_ref_pages.py +103 -0
- drone_models/__init__.py +73 -0
- drone_models/_typing.py +8 -0
- drone_models/core.py +163 -0
- drone_models/data/assets/cf21B/cf21B_PropL.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_PropR.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_battery-holder.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_battery.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_connector-pins.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_connectors.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_full.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_header.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_motors.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_no-prop.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_pcb.stl +0 -0
- drone_models/data/assets/cf21B/cf21B_prop-guards.stl +0 -0
- drone_models/data/assets/cf21B/cf_led-diffusor.stl +0 -0
- drone_models/data/assets/cf2x/cf2xL_PropL.stl +0 -0
- drone_models/data/assets/cf2x/cf2xL_PropR.stl +0 -0
- drone_models/data/assets/cf2x/cf2xL_motors.stl +0 -0
- drone_models/data/assets/cf2x/cf2xP_PropL.stl +0 -0
- drone_models/data/assets/cf2x/cf2xP_PropR.stl +0 -0
- drone_models/data/assets/cf2x/cf2xP_motors.stl +0 -0
- drone_models/data/assets/cf2x/cf2xT_motors.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_battery-holder.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_battery.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_connector-pins.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_connectors.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_motor-holder.stl +0 -0
- drone_models/data/assets/cf2x/cf2x_pcb.stl +0 -0
- drone_models/data/assets/cf2x/cf_led-diffusor.stl +0 -0
- drone_models/data/cf21B_500.xml +101 -0
- drone_models/data/cf2x_L250.xml +101 -0
- drone_models/data/cf2x_P250.xml +101 -0
- drone_models/data/cf2x_T350.xml +102 -0
- drone_models/data/params.toml +152 -0
- drone_models/drones.py +16 -0
- drone_models/first_principles/__init__.py +88 -0
- drone_models/first_principles/model.py +287 -0
- drone_models/first_principles/params.toml +13 -0
- drone_models/so_rpy/__init__.py +33 -0
- drone_models/so_rpy/model.py +294 -0
- drone_models/so_rpy/params.toml +30 -0
- drone_models/so_rpy_rotor/__init__.py +32 -0
- drone_models/so_rpy_rotor/model.py +331 -0
- drone_models/so_rpy_rotor/params.toml +34 -0
- drone_models/so_rpy_rotor_drag/__init__.py +38 -0
- drone_models/so_rpy_rotor_drag/model.py +357 -0
- drone_models/so_rpy_rotor_drag/params.toml +54 -0
- drone_models/symbols.py +49 -0
- drone_models/transform.py +132 -0
- drone_models/utils/__init__.py +18 -0
- drone_models/utils/data_utils.py +227 -0
- drone_models/utils/identification.py +502 -0
- drone_models/utils/rotation.py +562 -0
- drone_models-0.1.0.dist-info/METADATA +145 -0
- drone_models-0.1.0.dist-info/RECORD +65 -0
- drone_models-0.1.0.dist-info/WHEEL +5 -0
- drone_models-0.1.0.dist-info/top_level.txt +3 -0
- tests/conftest.py +10 -0
- tests/integration/test_identification_pipeline.py +57 -0
- tests/unit/test_identification.py +60 -0
- tests/unit/test_models.py +442 -0
- tests/unit/test_parametrization.py +19 -0
- tests/unit/test_rot.py +223 -0
docs/gen_ref_pages.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Generate the code reference pages and navigation."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import mkdocs_gen_files
|
|
6
|
+
|
|
7
|
+
SKIP_PARTS = {"_typing", "__main__"}
|
|
8
|
+
|
|
9
|
+
# model.py in these packages is re-exported via __init__, so document only at index level
|
|
10
|
+
SKIP_MODULE_PAGES = {
|
|
11
|
+
("drone_models", "first_principles", "model"),
|
|
12
|
+
("drone_models", "so_rpy", "model"),
|
|
13
|
+
("drone_models", "so_rpy_rotor", "model"),
|
|
14
|
+
("drone_models", "so_rpy_rotor_drag", "model"),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
TOP_LEVEL_PAGE = """\
|
|
18
|
+
::: drone_models
|
|
19
|
+
options:
|
|
20
|
+
members:
|
|
21
|
+
- parametrize
|
|
22
|
+
- available_models
|
|
23
|
+
- model_features
|
|
24
|
+
show_submodules: false
|
|
25
|
+
|
|
26
|
+
## Submodules
|
|
27
|
+
|
|
28
|
+
### Models
|
|
29
|
+
|
|
30
|
+
| Module | Description |
|
|
31
|
+
|--------|-------------|
|
|
32
|
+
| [first_principles](first_principles/index.md) | Full physics model |
|
|
33
|
+
| [so_rpy_rotor_drag](so_rpy_rotor_drag/index.md) | Fitted model with rotor dynamics and drag |
|
|
34
|
+
| [so_rpy_rotor](so_rpy_rotor/index.md) | Fitted model with rotor dynamics |
|
|
35
|
+
| [so_rpy](so_rpy/index.md) | Simplest fitted model |
|
|
36
|
+
|
|
37
|
+
### Core
|
|
38
|
+
|
|
39
|
+
| Module | Description |
|
|
40
|
+
|--------|-------------|
|
|
41
|
+
| [core](../core.md) | `load_params`, `supports` decorator, internal `parametrize` |
|
|
42
|
+
| [drones](../drones.md) | `available_drones` tuple |
|
|
43
|
+
| [symbols](../symbols.md) | CasADi symbolic utilities |
|
|
44
|
+
| [transform](../transform.md) | Motor/PWM/rotor conversion utilities |
|
|
45
|
+
|
|
46
|
+
### Utilities
|
|
47
|
+
|
|
48
|
+
| Module | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| [utils.rotation](../utils/rotation.md) | Quaternion/Euler/angular velocity conversions |
|
|
51
|
+
| [utils.data_utils](../utils/data_utils.md) | `preprocessing`, `derivatives_svf` |
|
|
52
|
+
| [utils.identification](../utils/identification.md) | `sys_id_translation`, `sys_id_rotation` |
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
for path in sorted(Path("drone_models").rglob("*.py")):
|
|
56
|
+
module_path = path.relative_to(".").with_suffix("")
|
|
57
|
+
doc_path = path.relative_to(".").with_suffix(".md")
|
|
58
|
+
full_doc_path = Path("reference", doc_path)
|
|
59
|
+
|
|
60
|
+
parts = tuple(module_path.parts)
|
|
61
|
+
|
|
62
|
+
if any(part in SKIP_PARTS for part in parts):
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
if parts in SKIP_MODULE_PAGES:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if parts[-1] == "__init__":
|
|
69
|
+
parts = parts[:-1]
|
|
70
|
+
doc_path = doc_path.with_name("index.md")
|
|
71
|
+
full_doc_path = full_doc_path.with_name("index.md")
|
|
72
|
+
elif parts[-1] == "__main__":
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
|
|
76
|
+
if parts == ("drone_models",):
|
|
77
|
+
fd.write(TOP_LEVEL_PAGE)
|
|
78
|
+
else:
|
|
79
|
+
ident = ".".join(parts)
|
|
80
|
+
fd.write(f"::: {ident}\n")
|
|
81
|
+
|
|
82
|
+
mkdocs_gen_files.set_edit_path(full_doc_path, path)
|
|
83
|
+
|
|
84
|
+
summary = """\
|
|
85
|
+
* [drone_models](drone_models/index.md)
|
|
86
|
+
* [first_principles](drone_models/first_principles/index.md)
|
|
87
|
+
* [so_rpy_rotor_drag](drone_models/so_rpy_rotor_drag/index.md)
|
|
88
|
+
* [so_rpy_rotor](drone_models/so_rpy_rotor/index.md)
|
|
89
|
+
* [so_rpy](drone_models/so_rpy/index.md)
|
|
90
|
+
* Core
|
|
91
|
+
* [core](drone_models/core.md)
|
|
92
|
+
* [drones](drone_models/drones.md)
|
|
93
|
+
* [symbols](drone_models/symbols.md)
|
|
94
|
+
* [transform](drone_models/transform.md)
|
|
95
|
+
* Utilities
|
|
96
|
+
* [utils](drone_models/utils/index.md)
|
|
97
|
+
* [utils.rotation](drone_models/utils/rotation.md)
|
|
98
|
+
* [utils.data_utils](drone_models/utils/data_utils.md)
|
|
99
|
+
* [utils.identification](drone_models/utils/identification.md)
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file:
|
|
103
|
+
nav_file.write(summary)
|
drone_models/__init__.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""drone-models: quadrotor dynamics models for estimation, control, and simulation.
|
|
2
|
+
|
|
3
|
+
This package provides numeric and symbolic quadrotor dynamics models at multiple
|
|
4
|
+
fidelity levels. Models are pure functions compatible with any Array API backend
|
|
5
|
+
(NumPy, JAX, PyTorch, etc.) and with CasADi for symbolic computation.
|
|
6
|
+
|
|
7
|
+
Use [parametrize][drone_models.parametrize] to bind a dynamics function to a named drone configuration,
|
|
8
|
+
and [available_models][drone_models.available_models] to enumerate all registered models.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Callable
|
|
14
|
+
|
|
15
|
+
# SciPy array API check. We use the most recent array API features, which require the
|
|
16
|
+
# SCIPY_ARRAY_API environment variable to be set to "1". This flag MUST be set before importing
|
|
17
|
+
# scipy, because scipy's C extensions cannot be unloaded once they have been imported. Therefore, we
|
|
18
|
+
# have to error out if the flag is not set. Otherwise, we immediately import scipy to ensure that no
|
|
19
|
+
# other package sets the flag to a different value before importing scipy.
|
|
20
|
+
|
|
21
|
+
if "scipy" in sys.modules and os.environ.get("SCIPY_ARRAY_API") != "1":
|
|
22
|
+
msg = """scipy has already been imported and the 'SCIPY_ARRAY_API' environment variable has not
|
|
23
|
+
been set. Please restart your Python session and set SCIPY_ARRAY_API="1" before importing any
|
|
24
|
+
packages that depend on scipy, or import this package first to automatically set the flag."""
|
|
25
|
+
raise RuntimeError(msg)
|
|
26
|
+
|
|
27
|
+
os.environ["SCIPY_ARRAY_API"] = "1"
|
|
28
|
+
import scipy # noqa: F401, ensure scipy uses array API features
|
|
29
|
+
|
|
30
|
+
from drone_models.core import parametrize
|
|
31
|
+
from drone_models.first_principles import dynamics as _first_principles_dynamics
|
|
32
|
+
from drone_models.so_rpy import dynamics as _so_rpy_dynamics
|
|
33
|
+
from drone_models.so_rpy_rotor import dynamics as _so_rpy_rotor_dynamics
|
|
34
|
+
from drone_models.so_rpy_rotor_drag import dynamics as _so_rpy_rotor_drag_dynamics
|
|
35
|
+
|
|
36
|
+
__all__ = ["parametrize", "available_models", "model_features"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
available_models: dict[str, Callable] = {
|
|
40
|
+
"first_principles": _first_principles_dynamics,
|
|
41
|
+
"so_rpy": _so_rpy_dynamics,
|
|
42
|
+
"so_rpy_rotor": _so_rpy_rotor_dynamics,
|
|
43
|
+
"so_rpy_rotor_drag": _so_rpy_rotor_drag_dynamics,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def model_features(model: Callable) -> dict[str, bool]:
|
|
48
|
+
"""Return the feature flags declared by a dynamics function.
|
|
49
|
+
|
|
50
|
+
Feature flags are set by the [supports][drone_models.core.supports] decorator on each
|
|
51
|
+
dynamics function and describe which optional inputs the model accepts.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
model: A dynamics function, or a ``functools.partial`` wrapping one (as
|
|
55
|
+
returned by [parametrize][drone_models.parametrize]).
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A dict of feature names to booleans. Currently contains:
|
|
59
|
+
- ``"rotor_dynamics"``: ``True`` if the model accepts and integrates
|
|
60
|
+
``rotor_vel``, ``False`` if passing ``rotor_vel`` raises a
|
|
61
|
+
``ValueError``.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
```python
|
|
65
|
+
from drone_models import model_features
|
|
66
|
+
from drone_models.first_principles import dynamics
|
|
67
|
+
|
|
68
|
+
model_features(dynamics) # {'rotor_dynamics': True}
|
|
69
|
+
```
|
|
70
|
+
"""
|
|
71
|
+
if hasattr(model, "func"): # Is a partial function
|
|
72
|
+
return model_features(model.func)
|
|
73
|
+
return getattr(model, "__drone_model_features__")
|
drone_models/_typing.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""This file is to be remove later as soon as a proper typing is available by the official array-api."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeAlias
|
|
4
|
+
|
|
5
|
+
import numpy.typing as npt
|
|
6
|
+
|
|
7
|
+
Array: TypeAlias = Any # To be changed to array_api_typing later
|
|
8
|
+
ArrayLike: TypeAlias = Array | npt.ArrayLike
|
drone_models/core.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Core tools for registering and capability checking for the drone models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import tomllib
|
|
7
|
+
import warnings
|
|
8
|
+
from functools import partial, wraps
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, ParamSpec, TypeVar
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from types import ModuleType
|
|
16
|
+
|
|
17
|
+
from drone_models._typing import Array # To be changed to array_api_typing later
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
21
|
+
P = ParamSpec("P")
|
|
22
|
+
R = TypeVar("R")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def supports(rotor_dynamics: bool = True) -> Callable[[F], F]:
|
|
26
|
+
"""Decorator that declares which optional inputs a dynamics function supports.
|
|
27
|
+
|
|
28
|
+
Wraps the decorated function so that:
|
|
29
|
+
|
|
30
|
+
* If ``rotor_dynamics=False`` and the caller passes ``rotor_vel``, a
|
|
31
|
+
``ValueError`` is raised immediately.
|
|
32
|
+
* If ``rotor_dynamics=True`` and the caller omits ``rotor_vel``, a
|
|
33
|
+
``UserWarning`` is issued and the commanded value is used directly.
|
|
34
|
+
|
|
35
|
+
The decorator also attaches a ``__drone_model_features__`` attribute to the
|
|
36
|
+
wrapper, which [model_features][drone_models.model_features] reads.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
rotor_dynamics: Whether the decorated function models rotor velocity
|
|
40
|
+
dynamics. Set to ``False`` for models that do not accept or integrate
|
|
41
|
+
``rotor_vel`` (e.g. ``so_rpy``). Defaults to ``True``.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
A decorator that wraps the dynamics function with the capability checks
|
|
45
|
+
described above.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def decorator(fn: F) -> F:
|
|
49
|
+
@wraps(fn)
|
|
50
|
+
def wrapper(
|
|
51
|
+
pos: Array,
|
|
52
|
+
quat: Array,
|
|
53
|
+
vel: Array,
|
|
54
|
+
ang_vel: Array,
|
|
55
|
+
cmd: Array,
|
|
56
|
+
rotor_vel: Array | None = None,
|
|
57
|
+
*args: Any,
|
|
58
|
+
**kwargs: Any,
|
|
59
|
+
) -> tuple[Array, Array, Array, Array, Array | None]:
|
|
60
|
+
if not rotor_dynamics and rotor_vel is not None:
|
|
61
|
+
raise ValueError("Rotor dynamics not supported, but rotor_vel is provided.")
|
|
62
|
+
if rotor_dynamics and rotor_vel is None:
|
|
63
|
+
warnings.warn("Rotor velocity not provided, using commanded rotor velocity.")
|
|
64
|
+
return fn(pos, quat, vel, ang_vel, cmd, rotor_vel, *args, **kwargs)
|
|
65
|
+
|
|
66
|
+
wrapper.__drone_model_features__ = {"rotor_dynamics": rotor_dynamics}
|
|
67
|
+
|
|
68
|
+
return wrapper # type: ignore
|
|
69
|
+
|
|
70
|
+
return decorator
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parametrize(
|
|
74
|
+
fn: Callable[P, R], drone_model: str, xp: ModuleType | None = None, device: str | None = None
|
|
75
|
+
) -> Callable[P, R]:
|
|
76
|
+
"""Parametrize a dynamics function with the default dynamics parameters for a drone model.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
fn: The dynamics function to parametrize.
|
|
80
|
+
drone_model: The drone model to use.
|
|
81
|
+
xp: The array API module to use. If not provided, numpy is used.
|
|
82
|
+
device: The device to use. If none, the device is inferred from the xp module.
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
```{ .python notest }
|
|
86
|
+
from drone_models.core import parametrize
|
|
87
|
+
from drone_models.first_principles import dynamics
|
|
88
|
+
|
|
89
|
+
dynamics_fn = parametrize(dynamics, drone_model="cf2x_L250")
|
|
90
|
+
pos_dot, quat_dot, vel_dot, ang_vel_dot, rotor_vel_dot = dynamics_fn(
|
|
91
|
+
pos=pos, quat=quat, vel=vel, ang_vel=ang_vel, cmd=cmd, rotor_vel=rotor_vel
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The parametrized dynamics function with all keyword argument only parameters filled in.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
xp = np if xp is None else xp
|
|
100
|
+
physics = fn.__module__.split(".")[-2]
|
|
101
|
+
sig = inspect.signature(fn)
|
|
102
|
+
kwonly_params = [
|
|
103
|
+
name
|
|
104
|
+
for name, param in sig.parameters.items()
|
|
105
|
+
if param.kind == inspect.Parameter.KEYWORD_ONLY
|
|
106
|
+
]
|
|
107
|
+
params = load_params(physics, drone_model, xp=xp)
|
|
108
|
+
params = {k: xp.asarray(v, device=device) for k, v in params.items() if k in kwonly_params}
|
|
109
|
+
except KeyError as e:
|
|
110
|
+
raise KeyError(
|
|
111
|
+
f"Model `{physics}` does not exist in the parameter registry for drone `{drone_model}`"
|
|
112
|
+
) from e
|
|
113
|
+
except ValueError as e:
|
|
114
|
+
raise ValueError(f"Drone model `{drone_model}` not supported for `{physics}`") from e
|
|
115
|
+
return partial(fn, **params)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def load_params(physics: str, drone_model: str, xp: ModuleType | None = None) -> dict:
|
|
119
|
+
"""Load and merge physical and model-specific parameters for a drone configuration.
|
|
120
|
+
|
|
121
|
+
Reads parameters from two TOML files:
|
|
122
|
+
|
|
123
|
+
* ``drone_models/data/params.toml`` — physical parameters shared across all
|
|
124
|
+
models (mass, inertia, thrust curves, …).
|
|
125
|
+
* ``drone_models/<physics>/params.toml`` — model-specific coefficients
|
|
126
|
+
(e.g. fitted RPY coefficients for ``so_rpy``).
|
|
127
|
+
|
|
128
|
+
The two dicts are merged (model-specific values take precedence), and
|
|
129
|
+
``J_inv`` is computed from ``J`` and added to the result.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
physics: Name of the model sub-package, e.g. ``"first_principles"``,
|
|
133
|
+
``"so_rpy"``, ``"so_rpy_rotor"``, or ``"so_rpy_rotor_drag"``.
|
|
134
|
+
drone_model: Name of the drone configuration, e.g. ``"cf2x_L250"``.
|
|
135
|
+
Must exist as a section in both TOML files.
|
|
136
|
+
xp: Array API module used to convert parameter values. If ``None``,
|
|
137
|
+
NumPy is used.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
A flat dict mapping parameter names to arrays (or scalars) in the
|
|
141
|
+
requested array namespace. Always contains at least ``mass``, ``J``,
|
|
142
|
+
``J_inv``, ``gravity_vec``, and the model-specific coefficients for
|
|
143
|
+
``physics``.
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
KeyError: If ``drone_model`` is not found in either TOML file, or if
|
|
147
|
+
``physics`` does not correspond to a known sub-package.
|
|
148
|
+
"""
|
|
149
|
+
xp = np if xp is None else xp
|
|
150
|
+
with open(Path(__file__).parent / "data/params.toml", "rb") as f:
|
|
151
|
+
physical_params = tomllib.load(f)
|
|
152
|
+
if drone_model not in physical_params:
|
|
153
|
+
raise KeyError(f"Drone model `{drone_model}` not found in data/params.toml")
|
|
154
|
+
with open(Path(__file__).parent / f"{physics}/params.toml", "rb") as f:
|
|
155
|
+
model_params = tomllib.load(f)
|
|
156
|
+
if drone_model not in model_params:
|
|
157
|
+
raise KeyError(f"Drone model `{drone_model}` not found in model params.toml")
|
|
158
|
+
params = physical_params[drone_model] | model_params[drone_model]
|
|
159
|
+
# Make sure J_inv does not have a dtype fixed before conversion to xp arrays to avoid fixing it
|
|
160
|
+
# to np.float64 when other frameworks might prefer a different dtype.
|
|
161
|
+
params["J_inv"] = np.linalg.inv(params["J"]).tolist()
|
|
162
|
+
params = {k: xp.asarray(v) for k, v in params.items()} # if k in fields
|
|
163
|
+
return params
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<mujoco model="cf21B">
|
|
2
|
+
<compiler inertiafromgeom="false" meshdir="assets" autolimits="true" />
|
|
3
|
+
|
|
4
|
+
<asset>
|
|
5
|
+
<material name="black" rgba="0.102 0.102 0.102 1"/>
|
|
6
|
+
<material name="gray" rgba="0.3 0.3 0.3 1"/>
|
|
7
|
+
<material name="light-gray" rgba="0.7 0.7 0.7 1"/>
|
|
8
|
+
<material name="silver" rgba="0.898 0.898 0.898 1"/>
|
|
9
|
+
<material name="gold" rgba="0.969 0.878 0.6 1"/>
|
|
10
|
+
<material name="green" rgba="0.522 0.902 0.145 1"/>
|
|
11
|
+
<!-- The LED materials are initially transparent and can be changed dynamically -->
|
|
12
|
+
<material name="led_top" rgba="1.0 1.0 1.0 0.0"/>
|
|
13
|
+
<material name="led_bot" rgba="1.0 1.0 1.0 0.0"/>
|
|
14
|
+
|
|
15
|
+
<mesh name="pcb" file="cf21B/cf21B_pcb.stl" scale="1e-3 1e-3 1e-3"/>
|
|
16
|
+
<mesh name="motors" file="cf21B/cf21B_motors.stl" scale="1e-3 1e-3 1e-3"/>
|
|
17
|
+
<mesh name="propL" file="cf21B/cf21B_PropL.stl" scale="1e-3 1e-3 1e-3"/>
|
|
18
|
+
<mesh name="propR" file="cf21B/cf21B_PropR.stl" scale="1e-3 1e-3 1e-3"/>
|
|
19
|
+
<mesh name="prop-guards" file="cf21B/cf21B_prop-guards.stl" scale="1e-3 1e-3 1e-3"/>
|
|
20
|
+
<mesh name="connectors" file="cf21B/cf21B_connectors.stl" scale="1e-3 1e-3 1e-3"/>
|
|
21
|
+
<mesh name="connector-pins" file="cf21B/cf21B_connector-pins.stl" scale="1e-3 1e-3 1e-3"/>
|
|
22
|
+
<mesh name="battery" file="cf21B/cf21B_battery.stl" scale="1e-3 1e-3 1e-3"/>
|
|
23
|
+
<mesh name="battery-holder" file="cf21B/cf21B_battery-holder.stl" scale="1e-3 1e-3 1e-3"/>
|
|
24
|
+
<mesh name="led-diffusor" file="cf21B/cf_led-diffusor.stl" scale="1e-3 1e-3 1e-3"/>
|
|
25
|
+
</asset>
|
|
26
|
+
|
|
27
|
+
<default>
|
|
28
|
+
<default class="cf21B">
|
|
29
|
+
<default class="visual">
|
|
30
|
+
<geom group="2" type="mesh" contype="0" conaffinity="0" />
|
|
31
|
+
</default>
|
|
32
|
+
<default class="collision">
|
|
33
|
+
<geom group="3" condim="1" />
|
|
34
|
+
</default>
|
|
35
|
+
<site group="5" />
|
|
36
|
+
</default>
|
|
37
|
+
</default>
|
|
38
|
+
|
|
39
|
+
<worldbody>
|
|
40
|
+
<body name="drone" pos="0 0 0.05" childclass="cf21B">
|
|
41
|
+
<site name="CoM" />
|
|
42
|
+
<inertial pos="0.0 0.0 0.0" mass="0.04338" diaginertia="2.3951e-5 2.3951e-5 3.2347e-5" />
|
|
43
|
+
|
|
44
|
+
<!-- Meshes -->
|
|
45
|
+
<geom mesh="pcb" material="gray" class="visual" />
|
|
46
|
+
<geom mesh="motors" material="black" class="visual" />
|
|
47
|
+
<geom mesh="prop-guards" material="black" class="visual" />
|
|
48
|
+
<geom mesh="connectors" material="black" class="visual" />
|
|
49
|
+
<geom mesh="connector-pins" material="gold" class="visual" />
|
|
50
|
+
<geom mesh="battery" material="light-gray" class="visual" />
|
|
51
|
+
<geom mesh="battery-holder" material="black" class="visual" />
|
|
52
|
+
<geom name="led_top" mesh="led-diffusor" material="led_top" class="visual" pos="0 0 0.015" euler="180 0 0"/>
|
|
53
|
+
<geom name="led_bot" mesh="led-diffusor" material="led_bot" class="visual" pos="0 0 -0.002"/>
|
|
54
|
+
|
|
55
|
+
<!-- Collision geometry is estimated based on the visual mesh. We use a sphere for maximum
|
|
56
|
+
computational efficiency with larger swarms. Used by default-->
|
|
57
|
+
<geom name="col_sphere" type="sphere" size="0.086" pos="0 0 0.02" class="collision" contype="1" conaffinity="1" />
|
|
58
|
+
<!-- For higher accuracy with lower performance, you can use a box. By default off. -->
|
|
59
|
+
<geom name="col_box" type="box" size="0.07 0.07 0.02" class="collision" rgba="0.5 0.5 0.5 0" contype="0" conaffinity="0" />
|
|
60
|
+
|
|
61
|
+
<!-- Motors and Propellers -->
|
|
62
|
+
<site name="motor0" pos="0.03536 -0.03536 0.0" />
|
|
63
|
+
<site name="motor1" pos="-0.03536 -0.03536 0.0" />
|
|
64
|
+
<site name="motor2" pos="-0.03536 0.03536 0.0" />
|
|
65
|
+
<site name="motor3" pos="0.03536 0.03536 0.0" />
|
|
66
|
+
<body name="prop0" pos="0.03536 -0.03536 0.012" euler="0 0 45">
|
|
67
|
+
<geom mesh="propL" material="green" class="visual"/>
|
|
68
|
+
</body>
|
|
69
|
+
<body name="prop1" pos="-0.03536 -0.03536 0.012" euler="0 0 135">
|
|
70
|
+
<geom mesh="propR" material="green" class="visual"/>
|
|
71
|
+
</body>
|
|
72
|
+
<body name="prop2" pos="-0.03536 0.03536 0.012" euler="0 0 225">
|
|
73
|
+
<geom mesh="propL" material="green" class="visual"/>
|
|
74
|
+
</body>
|
|
75
|
+
<body name="prop3" pos="0.03536 0.03536 0.012" euler="0 0 315">
|
|
76
|
+
<geom mesh="propR" material="green" class="visual"/>
|
|
77
|
+
</body>
|
|
78
|
+
|
|
79
|
+
<!-- Cameras -->
|
|
80
|
+
<camera name="track_cam" pos="-1 0 .5" xyaxes="0 -1 0 1 0 2" mode="trackcom" />
|
|
81
|
+
<camera name="fpv_cam" pos="0.03 0 0" euler="0 -90 -90" fovy="70" resolution="640 480" />
|
|
82
|
+
</body>
|
|
83
|
+
</worldbody>
|
|
84
|
+
|
|
85
|
+
<!-- Actuators
|
|
86
|
+
We do not limit the actuation range of the motors here, because we would need to synchronize the
|
|
87
|
+
control limits from our constants module with the ones in the MuJoCo model. Instead, we rely on
|
|
88
|
+
the controller simulation for proper clipping.
|
|
89
|
+
-->
|
|
90
|
+
<actuator>
|
|
91
|
+
<motor gear="0 0 1 0 0 0" site="motor0" name="motor0_force" />
|
|
92
|
+
<motor gear="0 0 1 0 0 0" site="motor1" name="motor1_force" />
|
|
93
|
+
<motor gear="0 0 1 0 0 0" site="motor2" name="motor2_force" />
|
|
94
|
+
<motor gear="0 0 1 0 0 0" site="motor3" name="motor3_force" />
|
|
95
|
+
<motor gear="0 0 0 0 0 1" site="motor0" name="motor0_torque" />
|
|
96
|
+
<motor gear="0 0 0 0 0 1" site="motor1" name="motor1_torque" />
|
|
97
|
+
<motor gear="0 0 0 0 0 1" site="motor2" name="motor2_torque" />
|
|
98
|
+
<motor gear="0 0 0 0 0 1" site="motor3" name="motor3_torque" />
|
|
99
|
+
</actuator>
|
|
100
|
+
|
|
101
|
+
</mujoco>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<mujoco model="cf2x">
|
|
2
|
+
<compiler inertiafromgeom="false" meshdir="assets" autolimits="true" />
|
|
3
|
+
|
|
4
|
+
<asset>
|
|
5
|
+
<material name="black" rgba="0.102 0.102 0.102 1"/>
|
|
6
|
+
<material name="gray" rgba="0.3 0.3 0.3 1"/>
|
|
7
|
+
<material name="light-gray" rgba="0.7 0.7 0.7 1"/>
|
|
8
|
+
<material name="silver" rgba="0.898 0.898 0.898 1"/>
|
|
9
|
+
<material name="white" rgba="1 1 1 0.3"/>
|
|
10
|
+
<material name="gold" rgba="0.969 0.878 0.6 1"/>
|
|
11
|
+
<!-- The LED materials are initially transparent and can be changed dynamically -->
|
|
12
|
+
<material name="led_top" rgba="1.0 1.0 1.0 0.0"/>
|
|
13
|
+
<material name="led_bot" rgba="1.0 1.0 1.0 0.0"/>
|
|
14
|
+
|
|
15
|
+
<mesh name="pcb" file="cf2x/cf2x_pcb.stl" scale="0.001 0.001 0.001"/>
|
|
16
|
+
<mesh name="motors" file="cf2x/cf2xL_motors.stl" scale="0.001 0.001 0.001"/>
|
|
17
|
+
<mesh name="motor-holder" file="cf2x/cf2x_motor-holder.stl" scale="0.001 0.001 0.001"/>
|
|
18
|
+
<mesh name="propL" file="cf2x/cf2xL_PropL.stl" scale="0.001 0.001 0.001"/>
|
|
19
|
+
<mesh name="propR" file="cf2x/cf2xL_PropR.stl" scale="0.001 0.001 0.001"/>
|
|
20
|
+
<mesh name="connectors" file="cf2x/cf2x_connectors.stl" scale="0.001 0.001 0.001"/>
|
|
21
|
+
<mesh name="connector-pins" file="cf2x/cf2x_connector-pins.stl" scale="0.001 0.001 0.001"/>
|
|
22
|
+
<mesh name="battery" file="cf2x/cf2x_battery.stl" scale="0.001 0.001 0.001"/>
|
|
23
|
+
<mesh name="battery-holder" file="cf2x/cf2x_battery-holder.stl" scale="0.001 0.001 0.001"/>
|
|
24
|
+
<mesh name="led-diffusor" file="cf21B/cf_led-diffusor.stl" scale="1e-3 1e-3 1e-3"/>
|
|
25
|
+
</asset>
|
|
26
|
+
|
|
27
|
+
<default>
|
|
28
|
+
<default class="cf2x">
|
|
29
|
+
<default class="visual">
|
|
30
|
+
<geom group="2" type="mesh" contype="0" conaffinity="0" />
|
|
31
|
+
</default>
|
|
32
|
+
<default class="collision">
|
|
33
|
+
<geom group="3" condim="1" />
|
|
34
|
+
</default>
|
|
35
|
+
<site group="5" />
|
|
36
|
+
</default>
|
|
37
|
+
</default>
|
|
38
|
+
|
|
39
|
+
<worldbody>
|
|
40
|
+
<body name="drone" pos="0 0 0.05" childclass="cf2x">
|
|
41
|
+
<site name="CoM" />
|
|
42
|
+
<inertial pos="0.0 0.0 0.0" mass="0.027" diaginertia="2.3951e-5 2.3951e-5 3.2347e-5" />
|
|
43
|
+
|
|
44
|
+
<!-- Meshes -->
|
|
45
|
+
<geom mesh="pcb" material="gray" class="visual" />
|
|
46
|
+
<geom mesh="motors" material="silver" class="visual" />
|
|
47
|
+
<geom mesh="motor-holder" material="white" class="visual" />
|
|
48
|
+
<geom mesh="connectors" material="black" class="visual" />
|
|
49
|
+
<geom mesh="connector-pins" material="gold" class="visual" />
|
|
50
|
+
<geom mesh="battery" material="light-gray" class="visual" />
|
|
51
|
+
<geom mesh="battery-holder" material="black" class="visual" />
|
|
52
|
+
<geom name="led_top" mesh="led-diffusor" material="led_top" class="visual" pos="0 0 0.008" euler="180 0 0"/>
|
|
53
|
+
<geom name="led_bot" mesh="led-diffusor" material="led_bot" class="visual" pos="0 0 -0.003"/>
|
|
54
|
+
|
|
55
|
+
<!-- Collision geometry is estimated based on the visual mesh. We use a sphere for maximum
|
|
56
|
+
computational efficiency with larger swarms. Used by default-->
|
|
57
|
+
<geom name="col_sphere" type="sphere" size="0.07" pos="0 0 0.005" class="collision" contype="1" conaffinity="1" />
|
|
58
|
+
<!-- For higher accuracy with lower performance, you can use a box. By default off. -->
|
|
59
|
+
<geom name="col_box" type="box" size="0.055 0.055 0.02" class="collision" rgba="0.5 0.5 0.5 0" contype="0" conaffinity="0" />
|
|
60
|
+
|
|
61
|
+
<!-- Motors and Propellers -->
|
|
62
|
+
<site name="motor0" pos="0.0325 -0.0325 0.0" />
|
|
63
|
+
<site name="motor1" pos="-0.0325 -0.0325 0.0" />
|
|
64
|
+
<site name="motor2" pos="-0.0325 0.0325 0.0" />
|
|
65
|
+
<site name="motor3" pos="0.0325 0.0325 0.0" />
|
|
66
|
+
<body name="prop0" pos="0.0325 -0.0325 0.012" euler="0 0 45">
|
|
67
|
+
<geom mesh="propL" material="black" class="visual"/>
|
|
68
|
+
</body>
|
|
69
|
+
<body name="prop1" pos="-0.0325 -0.0325 0.012" euler="0 0 135">
|
|
70
|
+
<geom mesh="propR" material="black" class="visual"/>
|
|
71
|
+
</body>
|
|
72
|
+
<body name="prop2" pos="-0.0325 0.0325 0.012" euler="0 0 225">
|
|
73
|
+
<geom mesh="propL" material="black" class="visual"/>
|
|
74
|
+
</body>
|
|
75
|
+
<body name="prop3" pos="0.0325 0.0325 0.012" euler="0 0 315">
|
|
76
|
+
<geom mesh="propR" material="black" class="visual"/>
|
|
77
|
+
</body>
|
|
78
|
+
|
|
79
|
+
<!-- Cameras -->
|
|
80
|
+
<camera name="track_cam" pos="-1 0 .5" xyaxes="0 -1 0 1 0 2" mode="trackcom" />
|
|
81
|
+
<camera name="fpv_cam" pos="0.03 0 0" euler="0 -90 -90" fovy="70" resolution="640 480" />
|
|
82
|
+
</body>
|
|
83
|
+
</worldbody>
|
|
84
|
+
|
|
85
|
+
<!-- Actuators
|
|
86
|
+
We do not limit the actuation range of the motors here, because we would need to synchronize the
|
|
87
|
+
control limits from our constants module with the ones in the MuJoCo model. Instead, we rely on
|
|
88
|
+
the controller simulation for proper clipping.
|
|
89
|
+
-->
|
|
90
|
+
<actuator>
|
|
91
|
+
<motor gear="0 0 1 0 0 0" site="motor0" name="motor0_force" />
|
|
92
|
+
<motor gear="0 0 1 0 0 0" site="motor1" name="motor1_force" />
|
|
93
|
+
<motor gear="0 0 1 0 0 0" site="motor2" name="motor2_force" />
|
|
94
|
+
<motor gear="0 0 1 0 0 0" site="motor3" name="motor3_force" />
|
|
95
|
+
<motor gear="0 0 0 0 0 1" site="motor0" name="motor0_torque" />
|
|
96
|
+
<motor gear="0 0 0 0 0 1" site="motor1" name="motor1_torque" />
|
|
97
|
+
<motor gear="0 0 0 0 0 1" site="motor2" name="motor2_torque" />
|
|
98
|
+
<motor gear="0 0 0 0 0 1" site="motor3" name="motor3_torque" />
|
|
99
|
+
</actuator>
|
|
100
|
+
|
|
101
|
+
</mujoco>
|