Skip to content

Commit 2e5bf34

Browse files
authored
Merge pull request #46 from ichumuh/main
implemented _post_init_with_world and _post_init_without_world
2 parents ac12e8e + 51f2a02 commit 2e5bf34

3 files changed

Lines changed: 323 additions & 238 deletions

File tree

src/semantic_world/connections.py

Lines changed: 133 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from abc import ABC, abstractmethod
44
from dataclasses import dataclass, field
5-
from typing import List, Tuple
5+
from typing import List, Tuple, TYPE_CHECKING
66

77
import numpy as np
88

@@ -14,6 +14,8 @@
1414
from .types import NpMatrix4x4
1515
from .world_entity import Connection
1616

17+
if TYPE_CHECKING:
18+
from .world import World
1719

1820

1921
class Has1DOFState:
@@ -22,6 +24,7 @@ class Has1DOFState:
2224
"""
2325

2426
dof: DegreeOfFreedom
27+
_world: World
2528

2629
@property
2730
def position(self) -> float:
@@ -60,6 +63,24 @@ def jerk(self, value: float) -> None:
6063
self._world.notify_state_change()
6164

6265

66+
class HasUpdateState(ABC):
67+
"""
68+
Mixin class for connections that need state updated which are not trivial integrations.
69+
Typically needed for connections that use active and passive degrees of freedom.
70+
Look at OmniDrive for an example usage.
71+
"""
72+
73+
@abstractmethod
74+
def update_state(self, dt: float) -> None:
75+
"""
76+
Allows the connection to update the state of its dofs.
77+
An integration update for active dofs will have happened before this method is called.
78+
Write directly into self._world.state, but don't touch dofs that don't belong to this connection.
79+
:param dt: Time passed since last update.
80+
"""
81+
pass
82+
83+
6384
@dataclass
6485
class FixedConnection(Connection):
6586
"""
@@ -72,7 +93,10 @@ class ActiveConnection(Connection):
7293
"""
7394
Has one or more degrees of freedom that can be actively controlled, e.g., robot joints.
7495
"""
75-
active_dofs: List[DegreeOfFreedom] = field(default_factory=list, init=False)
96+
97+
@property
98+
def active_dofs(self) -> List[DegreeOfFreedom]:
99+
return []
76100

77101

78102
@dataclass
@@ -81,7 +105,10 @@ class PassiveConnection(Connection):
81105
Has one or more degrees of freedom that cannot be actively controlled.
82106
Useful if a transformation is only tracked, e.g., the robot's localization.
83107
"""
84-
passive_dofs: List[DegreeOfFreedom] = field(default_factory=list, init=False)
108+
109+
@property
110+
def passive_dofs(self) -> List[DegreeOfFreedom]:
111+
return []
85112

86113

87114
@dataclass
@@ -170,8 +197,7 @@ def __post_init__(self):
170197
else:
171198
self.offset = self.offset
172199
self.axis = self.axis
173-
self.dof = self.dof or self._world.create_degree_of_freedom(name=PrefixedName(str(self.name)))
174-
self.active_dofs = [self.dof]
200+
self._post_init_world_part()
175201

176202
motor_expression = self.dof.symbols.position * self.multiplier + self.offset
177203
translation_axis = cas.Vector3(self.axis) * motor_expression
@@ -182,6 +208,18 @@ def __post_init__(self):
182208
self.origin_expression.reference_frame = self.parent
183209
self.origin_expression.child_frame = self.child
184210

211+
def _post_init_with_world(self):
212+
self.dof = self.dof or self._world.create_degree_of_freedom(name=PrefixedName(str(self.name)))
213+
214+
def _post_init_without_world(self):
215+
if self.dof is None:
216+
raise ValueError("PrismaticConnection cannot be created without a world "
217+
"if the dof is not provided.")
218+
219+
@property
220+
def active_dofs(self) -> List[DegreeOfFreedom]:
221+
return [self.dof]
222+
185223
def __hash__(self):
186224
return hash((self.parent, self.child))
187225

@@ -224,8 +262,7 @@ def __post_init__(self):
224262
else:
225263
self.offset = self.offset
226264
self.axis = self.axis
227-
self.dof = self.dof or self._world.create_degree_of_freedom(name=PrefixedName(str(self.name)))
228-
self.active_dofs = [self.dof]
265+
self._post_init_world_part()
229266

230267
motor_expression = self.dof.symbols.position * self.multiplier + self.offset
231268
rotation_axis = cas.Vector3(self.axis)
@@ -234,6 +271,18 @@ def __post_init__(self):
234271
self.origin_expression.reference_frame = self.parent
235272
self.origin_expression.child_frame = self.child
236273

274+
def _post_init_with_world(self):
275+
self.dof = self.dof or self._world.create_degree_of_freedom(name=PrefixedName(str(self.name)))
276+
277+
def _post_init_without_world(self):
278+
if self.dof is None:
279+
raise ValueError("RevoluteConnection cannot be created without a world "
280+
"if the dof is not provided.")
281+
282+
@property
283+
def active_dofs(self) -> List[DegreeOfFreedom]:
284+
return [self.dof]
285+
237286
def __hash__(self):
238287
return hash((self.parent, self.child))
239288

@@ -268,16 +317,7 @@ class Connection6DoF(PassiveConnection):
268317

269318
def __post_init__(self):
270319
super().__post_init__()
271-
self.x = self.x or self._world.create_degree_of_freedom(name=PrefixedName('x', str(self.name)))
272-
self.y = self.y or self._world.create_degree_of_freedom(name=PrefixedName('y', str(self.name)))
273-
self.z = self.z or self._world.create_degree_of_freedom(name=PrefixedName('z', str(self.name)))
274-
self.qx = self.qx or self._world.create_degree_of_freedom(name=PrefixedName('qx', str(self.name)))
275-
self.qy = self.qy or self._world.create_degree_of_freedom(name=PrefixedName('qy', str(self.name)))
276-
self.qz = self.qz or self._world.create_degree_of_freedom(name=PrefixedName('qz', str(self.name)))
277-
self.qw = self.qw or self._world.create_degree_of_freedom(name=PrefixedName('qw', str(self.name)))
278-
self.passive_dofs = [self.x, self.y, self.z, self.qx, self.qy, self.qz, self.qw]
279-
280-
self._world.state[self.qw.name].position = 1.
320+
self._post_init_world_part()
281321
parent_P_child = cas.Point3((self.x.symbols.position,
282322
self.y.symbols.position,
283323
self.z.symbols.position))
@@ -290,6 +330,29 @@ def __post_init__(self):
290330
reference_frame=self.parent,
291331
child_frame=self.child)
292332

333+
def _post_init_with_world(self):
334+
if all(dof is None for dof in self.passive_dofs):
335+
self.x = self._world.create_degree_of_freedom(name=PrefixedName('x', str(self.name)))
336+
self.y = self._world.create_degree_of_freedom(name=PrefixedName('y', str(self.name)))
337+
self.z = self._world.create_degree_of_freedom(name=PrefixedName('z', str(self.name)))
338+
self.qx = self._world.create_degree_of_freedom(name=PrefixedName('qx', str(self.name)))
339+
self.qy = self._world.create_degree_of_freedom(name=PrefixedName('qy', str(self.name)))
340+
self.qz = self._world.create_degree_of_freedom(name=PrefixedName('qz', str(self.name)))
341+
self.qw = self._world.create_degree_of_freedom(name=PrefixedName('qw', str(self.name)))
342+
self._world.state[self.qw.name].position = 1.
343+
elif any(dof is None for dof in self.passive_dofs):
344+
raise ValueError("Connection6DoF can only be created "
345+
"if you provide all or none of the passive degrees of freedom")
346+
347+
def _post_init_without_world(self):
348+
if any(dof is None for dof in self.passive_dofs):
349+
raise ValueError("Connection6DoF cannot be created without a world "
350+
"if some passive degrees of freedom are not provided.")
351+
352+
@property
353+
def passive_dofs(self) -> List[DegreeOfFreedom]:
354+
return [self.x, self.y, self.z, self.qx, self.qy, self.qz, self.qw]
355+
293356
@property
294357
def origin(self) -> NpMatrix4x4:
295358
return super().origin
@@ -307,24 +370,6 @@ def origin(self, transformation: NpMatrix4x4) -> None:
307370
self._world.notify_state_change()
308371

309372

310-
class HasUpdateState(ABC):
311-
"""
312-
Mixin class for connections that need state updated which are not trivial integrations.
313-
Typically needed for connections that use active and passive degrees of freedom.
314-
Look at OmniDrive for an example usage.
315-
"""
316-
317-
@abstractmethod
318-
def update_state(self, dt: float) -> None:
319-
"""
320-
Allows the connection to update the state of its dofs.
321-
An integration update for active dofs will have happened before this method is called.
322-
Write directly into self._world.state, but don't touch dofs that don't belong to this connection.
323-
:param dt: Time passed since last update.
324-
"""
325-
pass
326-
327-
328373
@dataclass
329374
class OmniDrive(ActiveConnection, PassiveConnection, HasUpdateState):
330375
x: DegreeOfFreedom = field(default=None)
@@ -341,38 +386,7 @@ class OmniDrive(ActiveConnection, PassiveConnection, HasUpdateState):
341386

342387
def __post_init__(self):
343388
super().__post_init__()
344-
stringified_name = str(self.name)
345-
lower_translation_limits = DerivativeMap()
346-
lower_translation_limits.velocity = -self.translation_velocity_limits
347-
upper_translation_limits = DerivativeMap()
348-
upper_translation_limits.velocity = self.translation_velocity_limits
349-
lower_rotation_limits = DerivativeMap()
350-
lower_rotation_limits.velocity = -self.rotation_velocity_limits
351-
upper_rotation_limits = DerivativeMap()
352-
upper_rotation_limits.velocity = self.rotation_velocity_limits
353-
354-
self.x = self.x or self._world.create_degree_of_freedom(name=PrefixedName('x', stringified_name))
355-
self.y = self.y or self._world.create_degree_of_freedom(name=PrefixedName('y', stringified_name))
356-
self.z = self.z or self._world.create_degree_of_freedom(name=PrefixedName('z', stringified_name))
357-
358-
self.roll = self.roll or self._world.create_degree_of_freedom(name=PrefixedName('roll', stringified_name))
359-
self.pitch = self.pitch or self._world.create_degree_of_freedom(name=PrefixedName('pitch', stringified_name))
360-
self.yaw = self.yaw or self._world.create_degree_of_freedom(
361-
name=PrefixedName('yaw', stringified_name),
362-
lower_limits=lower_rotation_limits,
363-
upper_limits=upper_rotation_limits)
364-
365-
self.x_vel = self.x_vel or self._world.create_degree_of_freedom(
366-
name=PrefixedName('x_vel', stringified_name),
367-
lower_limits=lower_translation_limits,
368-
upper_limits=upper_translation_limits)
369-
self.y_vel = self.y_vel or self._world.create_degree_of_freedom(
370-
name=PrefixedName('y_vel', stringified_name),
371-
lower_limits=lower_translation_limits,
372-
upper_limits=upper_translation_limits)
373-
self.active_dofs = [self.x_vel, self.y_vel, self.yaw]
374-
self.passive_dofs = [self.x, self.y, self.z, self.roll, self.pitch]
375-
389+
self._post_init_world_part()
376390
odom_T_bf = cas.TransformationMatrix.from_xyz_rpy(x=self.x.symbols.position,
377391
y=self.y.symbols.position,
378392
yaw=self.yaw.symbols.position)
@@ -388,6 +402,58 @@ def __post_init__(self):
388402
self.origin_expression.reference_frame = self.parent
389403
self.origin_expression.child_frame = self.child
390404

405+
def _post_init_with_world(self):
406+
if all(dof is None for dof in self.dofs):
407+
stringified_name = str(self.name)
408+
lower_translation_limits = DerivativeMap()
409+
lower_translation_limits.velocity = -self.translation_velocity_limits
410+
upper_translation_limits = DerivativeMap()
411+
upper_translation_limits.velocity = self.translation_velocity_limits
412+
lower_rotation_limits = DerivativeMap()
413+
lower_rotation_limits.velocity = -self.rotation_velocity_limits
414+
upper_rotation_limits = DerivativeMap()
415+
upper_rotation_limits.velocity = self.rotation_velocity_limits
416+
417+
self.x = self._world.create_degree_of_freedom(name=PrefixedName('x', stringified_name))
418+
self.y = self._world.create_degree_of_freedom(name=PrefixedName('y', stringified_name))
419+
self.z = self._world.create_degree_of_freedom(name=PrefixedName('z', stringified_name))
420+
421+
self.roll = self._world.create_degree_of_freedom(name=PrefixedName('roll', stringified_name))
422+
self.pitch = self._world.create_degree_of_freedom(name=PrefixedName('pitch', stringified_name))
423+
self.yaw = self._world.create_degree_of_freedom(
424+
name=PrefixedName('yaw', stringified_name),
425+
lower_limits=lower_rotation_limits,
426+
upper_limits=upper_rotation_limits)
427+
428+
self.x_vel = self._world.create_degree_of_freedom(
429+
name=PrefixedName('x_vel', stringified_name),
430+
lower_limits=lower_translation_limits,
431+
upper_limits=upper_translation_limits)
432+
self.y_vel = self._world.create_degree_of_freedom(
433+
name=PrefixedName('y_vel', stringified_name),
434+
lower_limits=lower_translation_limits,
435+
upper_limits=upper_translation_limits)
436+
elif any(dof is None for dof in self.passive_dofs):
437+
raise ValueError("OmniDrive can only be created "
438+
"if you provide all or none of the passive degrees of freedom")
439+
440+
def _post_init_without_world(self):
441+
if any(dof is None for dof in self.dofs):
442+
raise ValueError("OmniDrive cannot be created without a world "
443+
"if some passive degrees of freedom are not provided.")
444+
445+
@property
446+
def active_dofs(self) -> List[DegreeOfFreedom]:
447+
return [self.x_vel, self.y_vel, self.yaw]
448+
449+
@property
450+
def passive_dofs(self) -> List[DegreeOfFreedom]:
451+
return [self.x, self.y, self.z, self.roll, self.pitch]
452+
453+
@property
454+
def dofs(self) -> List[DegreeOfFreedom]:
455+
return self.active_dofs + self.passive_dofs
456+
391457
def update_state(self, dt: float) -> None:
392458
state = self._world.state
393459
state[self.x_vel.name].position = 0

0 commit comments

Comments
 (0)