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.
Files changed (65) hide show
  1. docs/gen_ref_pages.py +103 -0
  2. drone_models/__init__.py +73 -0
  3. drone_models/_typing.py +8 -0
  4. drone_models/core.py +163 -0
  5. drone_models/data/assets/cf21B/cf21B_PropL.stl +0 -0
  6. drone_models/data/assets/cf21B/cf21B_PropR.stl +0 -0
  7. drone_models/data/assets/cf21B/cf21B_battery-holder.stl +0 -0
  8. drone_models/data/assets/cf21B/cf21B_battery.stl +0 -0
  9. drone_models/data/assets/cf21B/cf21B_connector-pins.stl +0 -0
  10. drone_models/data/assets/cf21B/cf21B_connectors.stl +0 -0
  11. drone_models/data/assets/cf21B/cf21B_full.stl +0 -0
  12. drone_models/data/assets/cf21B/cf21B_header.stl +0 -0
  13. drone_models/data/assets/cf21B/cf21B_motors.stl +0 -0
  14. drone_models/data/assets/cf21B/cf21B_no-prop.stl +0 -0
  15. drone_models/data/assets/cf21B/cf21B_pcb.stl +0 -0
  16. drone_models/data/assets/cf21B/cf21B_prop-guards.stl +0 -0
  17. drone_models/data/assets/cf21B/cf_led-diffusor.stl +0 -0
  18. drone_models/data/assets/cf2x/cf2xL_PropL.stl +0 -0
  19. drone_models/data/assets/cf2x/cf2xL_PropR.stl +0 -0
  20. drone_models/data/assets/cf2x/cf2xL_motors.stl +0 -0
  21. drone_models/data/assets/cf2x/cf2xP_PropL.stl +0 -0
  22. drone_models/data/assets/cf2x/cf2xP_PropR.stl +0 -0
  23. drone_models/data/assets/cf2x/cf2xP_motors.stl +0 -0
  24. drone_models/data/assets/cf2x/cf2xT_motors.stl +0 -0
  25. drone_models/data/assets/cf2x/cf2x_battery-holder.stl +0 -0
  26. drone_models/data/assets/cf2x/cf2x_battery.stl +0 -0
  27. drone_models/data/assets/cf2x/cf2x_connector-pins.stl +0 -0
  28. drone_models/data/assets/cf2x/cf2x_connectors.stl +0 -0
  29. drone_models/data/assets/cf2x/cf2x_motor-holder.stl +0 -0
  30. drone_models/data/assets/cf2x/cf2x_pcb.stl +0 -0
  31. drone_models/data/assets/cf2x/cf_led-diffusor.stl +0 -0
  32. drone_models/data/cf21B_500.xml +101 -0
  33. drone_models/data/cf2x_L250.xml +101 -0
  34. drone_models/data/cf2x_P250.xml +101 -0
  35. drone_models/data/cf2x_T350.xml +102 -0
  36. drone_models/data/params.toml +152 -0
  37. drone_models/drones.py +16 -0
  38. drone_models/first_principles/__init__.py +88 -0
  39. drone_models/first_principles/model.py +287 -0
  40. drone_models/first_principles/params.toml +13 -0
  41. drone_models/so_rpy/__init__.py +33 -0
  42. drone_models/so_rpy/model.py +294 -0
  43. drone_models/so_rpy/params.toml +30 -0
  44. drone_models/so_rpy_rotor/__init__.py +32 -0
  45. drone_models/so_rpy_rotor/model.py +331 -0
  46. drone_models/so_rpy_rotor/params.toml +34 -0
  47. drone_models/so_rpy_rotor_drag/__init__.py +38 -0
  48. drone_models/so_rpy_rotor_drag/model.py +357 -0
  49. drone_models/so_rpy_rotor_drag/params.toml +54 -0
  50. drone_models/symbols.py +49 -0
  51. drone_models/transform.py +132 -0
  52. drone_models/utils/__init__.py +18 -0
  53. drone_models/utils/data_utils.py +227 -0
  54. drone_models/utils/identification.py +502 -0
  55. drone_models/utils/rotation.py +562 -0
  56. drone_models-0.1.0.dist-info/METADATA +145 -0
  57. drone_models-0.1.0.dist-info/RECORD +65 -0
  58. drone_models-0.1.0.dist-info/WHEEL +5 -0
  59. drone_models-0.1.0.dist-info/top_level.txt +3 -0
  60. tests/conftest.py +10 -0
  61. tests/integration/test_identification_pipeline.py +57 -0
  62. tests/unit/test_identification.py +60 -0
  63. tests/unit/test_models.py +442 -0
  64. tests/unit/test_parametrization.py +19 -0
  65. 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)
@@ -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__")
@@ -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
@@ -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>