Skip to content

Commit

Permalink
Morphological measures (#182)
Browse files Browse the repository at this point in the history
* Converting Body to grid format.
* Up mypy to 0.982
  • Loading branch information
surgura committed Oct 19, 2022
1 parent 86cd70d commit 4a27c1d
Show file tree
Hide file tree
Showing 4 changed files with 746 additions and 2 deletions.
2 changes: 1 addition & 1 deletion codetools/mypy/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
mypy==0.961
mypy==0.982
2 changes: 2 additions & 0 deletions core/revolve2/core/modular_robot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from ._core import Core
from ._modular_robot import ModularRobot
from ._module import Module
from ._morphological_measures import MorphologicalMeasures
from ._not_finalized_error import NotFinalizedError

__all__ = [
Expand All @@ -17,5 +18,6 @@
"Core",
"ModularRobot",
"Module",
"MorphologicalMeasures",
"NotFinalizedError",
]
155 changes: 154 additions & 1 deletion core/revolve2/core/modular_robot/_body.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import math
from typing import List, Tuple
from dataclasses import dataclass
from typing import List, Optional, Tuple

import numpy as np
from pyrr import Quaternion, Vector3
Expand Down Expand Up @@ -66,6 +67,17 @@ def find_active_hinges(self) -> List[ActiveHinge]:
raise NotFinalizedError()
return _ActiveHingeFinder().find(self)

def find_bricks(self) -> List[Brick]:
"""
Find all bricks in the body.
:returns: A list of all bricks in the body
:raises NotFinalizedError: In case this body has not yet been finalized.
"""
if not self.is_finalized:
raise NotFinalizedError()
return _BrickFinder().find(self)

def grid_position(self, module: Module) -> Vector3:
"""
Calculate the position of this module in a 3d grid with the core as center.
Expand Down Expand Up @@ -126,6 +138,24 @@ def grid_position(self, module: Module) -> Vector3:

return position

def to_grid(
self,
) -> Tuple[List[List[List[Optional[Module]]]], Tuple[int, int, int]]:
"""
Convert the tree structure to a grid.
The distance between all modules is assumed to be one grid cell.
All module angles must be multiples of 90 degrees.
The grid is indexed depth, width, height, or x, y, z, from the perspective of the core.
:returns: The created grid with cells set to either a Module or None and a tuple representing the position of the core.
:raises NotImplementedError: In case a module is encountered that is not supported.
# noqa: DAR402 NotImplementedError
"""
return _GridMaker().make_grid(self)


class _Finalizer:
_body: Body
Expand All @@ -148,6 +178,111 @@ def _finalize_recur(self, module: Module) -> None:
self._finalize_recur(child)


class _GridMaker:
@dataclass
class _Cell:
x: int
y: int
z: int
module: Module

_core_pos: Tuple[int, int, int]
_cells: List[_Cell]

def __init__(self) -> None:
self._cells = []

def make_grid(
self, body: Body
) -> Tuple[List[List[List[Optional[Module]]]], Tuple[int, int, int]]:
self._make_grid_recur(body.core, Vector3(), Quaternion())

minx = min([cell.x for cell in self._cells])
maxx = max([cell.x for cell in self._cells])
miny = min([cell.y for cell in self._cells])
maxy = max([cell.y for cell in self._cells])
minz = min([cell.z for cell in self._cells])
maxz = max([cell.z for cell in self._cells])

depth = maxx - minx + 1
width = maxy - miny + 1
height = maxz - minz + 1

grid: List[List[List[Optional[Module]]]] = []
for _ in range(depth):
y: List[List[Optional[Module]]] = []
for _ in range(width):
y.append([None] * (height))
grid.append(y)

for cell in self._cells:
grid[cell.x - minx][cell.y - miny][cell.z - minz] = cell.module

return grid, (-minx, -miny, -minz)

def _make_grid_recur(
self, module: Module, position: Vector3, orientation: Quaternion
) -> None:
self._cells.append(
self._Cell(round(position.x), round(position.y), round(position.z), module)
)

if isinstance(module, Core):
for (child_index, angle) in [
(Core.FRONT, 0.0),
(Core.BACK, math.pi),
(Core.LEFT, math.pi / 2.0),
(Core.RIGHT, math.pi / 2.0 * 3),
]:
child = module.children[child_index]

if child is not None:
assert np.isclose(child.rotation % (math.pi / 2.0), 0.0)

rotation = (
orientation
* Quaternion.from_eulers([0.0, 0.0, angle])
* Quaternion.from_eulers([child.rotation, 0, 0])
)

self._make_grid_recur(
child, position + rotation * Vector3([1.0, 0.0, 0.0]), rotation
)
elif isinstance(module, Brick):
for (child_index, angle) in [
(Brick.FRONT, 0.0),
(Brick.LEFT, math.pi / 2.0),
(Brick.RIGHT, math.pi / 2.0 * 3),
]:
child = module.children[child_index]

if child is not None:
assert np.isclose(child.rotation % (math.pi / 2.0), 0.0)

rotation = (
orientation
* Quaternion.from_eulers([0.0, 0.0, angle])
* Quaternion.from_eulers([child.rotation, 0, 0])
)

self._make_grid_recur(
child, position + rotation * Vector3([1.0, 0.0, 0.0]), rotation
)
elif isinstance(module, ActiveHinge):
child = module.children[ActiveHinge.ATTACHMENT]

if child is not None:
assert np.isclose(child.rotation % (math.pi / 2.0), 0.0)

rotation = Quaternion.from_eulers([child.rotation, 0.0, 0.0])

self._make_grid_recur(
child, position + rotation * Vector3([1.0, 0.0, 0.0]), rotation
)
else:
raise NotImplementedError()


class _ActorBuilder:
_STATIC_FRICTION = 1.0
_DYNAMIC_FRICTION = 1.0
Expand Down Expand Up @@ -462,3 +597,21 @@ def _find_recur(self, module: Module) -> None:
for child in module.children:
if child is not None:
self._find_recur(child)


class _BrickFinder:
_bricks: List[Brick]

def __init__(self) -> None:
self._bricks = []

def find(self, body: Body) -> List[Brick]:
self._find_recur(body.core)
return self._bricks

def _find_recur(self, module: Module) -> None:
if isinstance(module, Brick):
self._bricks.append(module)
for child in module.children:
if child is not None:
self._find_recur(child)
Loading

0 comments on commit 4a27c1d

Please sign in to comment.