Skip to content

Commit 68461da

Browse files
authored
Merge pull request #116 from Tigul/pycram-integration
Pycram integration
2 parents c80ba40 + 00b72f3 commit 68461da

12 files changed

Lines changed: 106 additions & 70 deletions

File tree

.github/workflows/build_and_deploy_doc.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ jobs:
7878
rm -f "$ipynb"
7979
done
8080
81+
- name: Install Node.js
82+
uses: actions/setup-node@v4
83+
with:
84+
node-version: '18'
85+
86+
8187
# Build the book
8288
- name: Build the book
8389
run: |

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pathlib
2222
probabilistic_model
2323
python-fcl
2424
manifold3d
25-
krrood>=1.0.1
25+
krrood==1.1.1
2626
rustworkx_utils
2727
polytope
2828
ortools

scripts/generate_orm.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from dataclasses import is_dataclass
1212

1313
import krrood.entity_query_language.orm.model
14-
import krrood.entity_query_language.symbol_graph
1514
import trimesh
1615
from krrood.class_diagrams import ClassDiagram
1716
from krrood.entity_query_language.predicate import HasTypes, HasType, Symbol

src/semantic_digital_twin/datastructures/prefixed_name.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ def __str__(self):
1818
return f"{self.prefix}/{self.name}"
1919

2020
def __eq__(self, other):
21-
return str(self) == str(other)
21+
if not isinstance(other, type(self)):
22+
return False
23+
return self.prefix == other.prefix and self.name == other.name
2224

2325
def to_json(self) -> Dict[str, Any]:
2426
return {**super().to_json(), "name": self.name, "prefix": self.prefix}

src/semantic_digital_twin/pipeline/pipeline.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,7 @@ def _apply(self, world: World) -> World:
151151
if parent_connection is None:
152152
return factory.create()
153153

154-
for entity in world.compute_descendent_child_kinematic_structure_entities(
155-
body
156-
):
154+
for entity in world.get_kinematic_structure_entities_of_branch(body):
157155
world.remove_kinematic_structure_entity(entity)
158156

159157
world.remove_kinematic_structure_entity(body)

src/semantic_digital_twin/semantic_annotations/semantic_annotations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
from dataclasses import dataclass, field
44

55
import numpy as np
6+
67
from probabilistic_model.probabilistic_circuit.rx.helper import uniform_measure_of_event
78
from typing_extensions import List
89

910
from ..world_description.shape_collection import BoundingBoxCollection
10-
from ..datastructures.prefixed_name import PrefixedName
1111
from ..spatial_types import Point3
1212
from ..datastructures.variables import SpatialVariables
1313
from ..world_description.world_entity import SemanticAnnotation, Body, Region

src/semantic_digital_twin/spatial_computations/raytracer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def create_camera_rays(
205205
return self.scene.camera_rays()
206206

207207
def ray_test(
208-
self, origin_points: np.ndarray, target_points: np.ndarray
208+
self, origin_points: np.ndarray, target_points: np.ndarray, multiple_hits=False
209209
) -> Tuple[np.ndarray, np.ndarray, List[Body]]:
210210
"""
211211
Performs a ray test from the origin point to the target point in the ray tracer scene.
@@ -214,6 +214,8 @@ def ray_test(
214214
:param target_points: The end point of the ray.
215215
:return: A tuple containing the points where the ray intersects and the indices of rays that hit the scene as well as the bodies that were.
216216
"""
217+
origin_points = np.array(origin_points)
218+
target_points = np.array(target_points)
217219
self.update_scene()
218220
origin_points = origin_points.reshape((-1, 3))
219221
target_points = target_points.reshape((-1, 3))
@@ -222,7 +224,7 @@ def ray_test(
222224

223225
ray_directions = target_points - origin_points
224226
points, index_ray, index_tri = self.scene.to_mesh().ray.intersects_location(
225-
origin_points, ray_directions, multiple_hits=False
227+
origin_points, ray_directions, multiple_hits=multiple_hits
226228
)
227229
bodies = self.scene.triangles_node[index_tri]
228230

src/semantic_digital_twin/world.py

Lines changed: 14 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,17 @@ def reset_temporary_collision_config(self):
351351
body.reset_temporary_collision_config()
352352

353353
@property
354-
def root(self) -> Optional[KinematicStructureEntity]:
354+
def root(self):
355+
return self._root(self._model_version)
356+
357+
@lru_cache()
358+
def _root(self, model_version) -> Optional[KinematicStructureEntity]:
355359
"""
356360
The root of the world is the unique node with in-degree 0.
357361
362+
:param model_version: Version of the model used to manage the cache of return values.
358363
:return: The root of the world.
359364
"""
360-
361365
if not self.kinematic_structure_entities:
362366
return None
363367
possible_roots = [
@@ -490,7 +494,7 @@ def reset_state_context(self) -> ResetStateContextManager:
490494
def clear_all_lru_caches(self):
491495
for method_name in dir(self):
492496
try:
493-
method = getattr(self, method_name)
497+
method = getattr(type(self), method_name)
494498
if hasattr(method, "cache_clear") and callable(method.cache_clear):
495499
method.cache_clear()
496500
except AttributeError:
@@ -517,8 +521,8 @@ def _notify_model_change(self) -> None:
517521
for model changes.
518522
"""
519523
# if not self.world_is_being_modified:
520-
self.compile_forward_kinematics_expressions()
521524
self.clear_all_lru_caches()
525+
self.compile_forward_kinematics_expressions()
522526
self.notify_state_change()
523527
self._model_version += 1
524528

@@ -788,7 +792,7 @@ def tree_edge(self, edge: Tuple[int, int, Connection]):
788792

789793
return visitor.connections
790794

791-
def get_bodies_of_branch(
795+
def get_kinematic_structure_entities_of_branch(
792796
self, root: KinematicStructureEntity
793797
) -> List[KinematicStructureEntity]:
794798
"""
@@ -797,22 +801,10 @@ def get_bodies_of_branch(
797801
:param root: The root body of the branch
798802
:return: List of all bodies in the subtree rooted at the given body (including the root)
799803
"""
800-
801-
# Create a custom visitor to collect bodies
802-
class BodyCollector(rustworkx.visit.DFSVisitor):
803-
def __init__(self, world: World):
804-
self.world = world
805-
self.bodies = []
806-
807-
def discover_vertex(self, node_index: int, time: int) -> None:
808-
"""Called when a vertex is first discovered during DFS traversal"""
809-
body = self.world.kinematic_structure[node_index]
810-
self.bodies.append(body)
811-
812-
visitor = BodyCollector(self)
813-
rx.dfs_search(self.kinematic_structure, [root.index], visitor)
814-
815-
return visitor.bodies
804+
descendants_indices = rx.descendants(self.kinematic_structure, root.index)
805+
return [root] + [
806+
self.kinematic_structure[index] for index in descendants_indices
807+
]
816808

817809
def get_semantic_annotation_by_name(
818810
self, name: Union[str, PrefixedName]
@@ -1171,6 +1163,7 @@ def get_body_by_name(self, name: Union[str, PrefixedName]) -> Body:
11711163
return matches[0]
11721164
raise KeyError(f"Body with name {name} not found")
11731165

1166+
@lru_cache(maxsize=None)
11741167
def get_degree_of_freedom_by_name(
11751168
self, name: Union[str, PrefixedName]
11761169
) -> DegreeOfFreedom:
@@ -1246,24 +1239,6 @@ def compute_child_kinematic_structure_entities(
12461239
self.kinematic_structure.successors(kinematic_structure_entity.index)
12471240
)
12481241

1249-
@lru_cache(maxsize=None)
1250-
def compute_descendent_child_kinematic_structure_entities(
1251-
self, kinematic_structure_entity: KinematicStructureEntity
1252-
) -> List[KinematicStructureEntity]:
1253-
"""
1254-
Computes all child entities of a given KinematicStructureEntity in the world recursively.
1255-
:param kinematic_structure_entity: The KinematicStructureEntity for which to compute children.
1256-
:return: A list of all child KinematicStructureEntities.
1257-
"""
1258-
children = self.compute_child_kinematic_structure_entities(
1259-
kinematic_structure_entity
1260-
)
1261-
for child in children:
1262-
children.extend(
1263-
self.compute_descendent_child_kinematic_structure_entities(child)
1264-
)
1265-
return children
1266-
12671242
@lru_cache(maxsize=None)
12681243
def compute_parent_kinematic_structure_entity(
12691244
self, kinematic_structure_entity: KinematicStructureEntity

src/semantic_digital_twin/world_description/graph_of_convex_sets.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import time
1515
from functools import reduce
1616
from operator import or_
17-
from typing_extensions import List, Optional, Dict
17+
from typing_extensions import List, Optional, Dict, Sequence
1818

1919
# typing.Self is available starting with Python 3.11
2020
from typing_extensions import Self
@@ -80,6 +80,20 @@ def __init__(
8080
self.box_to_index_map = {}
8181
self.world = world
8282

83+
def create_subgraph(self, nodes: Sequence[int]) -> Self:
84+
"""
85+
Create a subgraph of the current graph containing only the given nodes.
86+
87+
:param nodes: The nodes to include in the subgraph.
88+
:return: The subgraph.
89+
"""
90+
subgraph = GraphOfConvexSets(self.world, self.search_space)
91+
subgraph.graph = self.graph.subgraph(nodes)
92+
subgraph.box_to_index_map = {
93+
box: index for box, index in self.box_to_index_map.items() if index in nodes
94+
}
95+
return subgraph
96+
8397
def add_node(self, box: BoundingBox):
8498
self.box_to_index_map[box] = self.graph.add_node(box)
8599

@@ -453,6 +467,31 @@ def free_space_from_world(
453467
bloat_obstacles=bloat_obstacles,
454468
)
455469

470+
@classmethod
471+
def obstacles_from_world(
472+
cls,
473+
world: World,
474+
search_space: BoundingBoxCollection,
475+
bloat_obstacles: float = 0.0,
476+
) -> Optional[Event]:
477+
"""
478+
Create an event representing the obstacles in the belief state of the robot.
479+
480+
:param world: The belief state.
481+
:param search_space: The search space for the connectivity graph.
482+
:param bloat_obstacles: The amount to bloat the obstacles.
483+
484+
:return: An event representing the obstacles in the search space.
485+
"""
486+
487+
view = SemanticEnvironmentAnnotation(root=world.root, _world=world)
488+
489+
return cls.obstacles_from_semantic_annotations(
490+
search_space=search_space,
491+
semantic_obstacle_annotation=view,
492+
bloat_obstacles=bloat_obstacles,
493+
)
494+
456495
@classmethod
457496
def navigation_map_from_semantic_annotation(
458497
cls,

src/semantic_digital_twin/world_description/shape_collection.py

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,9 @@ def _transform_to_own_frame(self, shape: Shape):
6767
# If we don’t have a world, fall back to the owning body/frame
6868
shape.origin.reference_frame = self.reference_frame
6969
elif (
70-
self.reference_frame is not None
71-
and shape.origin.reference_frame != self.reference_frame
72-
and self.reference_frame._world is not None
70+
self.reference_frame is not None
71+
and shape.origin.reference_frame != self.reference_frame
72+
and self.reference_frame._world is not None
7373
):
7474
logger.warning(
7575
f"Transformed shape {shape} to {self.reference_frame} since it was in a different "
@@ -114,7 +114,7 @@ def combined_mesh(self) -> Trimesh:
114114
return concatenate(transformed_meshes)
115115

116116
def as_bounding_box_collection_at_origin(
117-
self, origin: TransformationMatrix
117+
self, origin: TransformationMatrix
118118
) -> BoundingBoxCollection:
119119
"""
120120
Provides the bounding box collection for this entity given a transformation matrix as origin.
@@ -136,7 +136,7 @@ def as_bounding_box_collection_at_origin(
136136
)
137137

138138
def as_bounding_box_collection_in_frame(
139-
self, reference_frame: KinematicStructureEntity
139+
self, reference_frame: KinematicStructureEntity
140140
) -> BoundingBoxCollection:
141141
"""
142142
Provides the bounding box collection for this entity in the given reference frame.
@@ -182,9 +182,11 @@ class BoundingBoxCollection(ShapeCollection):
182182
shapes: List[BoundingBox]
183183

184184
def __post_init__(self):
185+
if not self.reference_frame:
186+
raise ValueError("BoundingBoxCollection must have a reference frame.")
185187
for box in self.bounding_boxes:
186188
assert (
187-
box.origin.reference_frame == self.reference_frame
189+
box.origin.reference_frame == self.reference_frame
188190
), "All bounding boxes must have the same reference frame."
189191

190192
def __iter__(self) -> Iterator[BoundingBox]:
@@ -209,15 +211,15 @@ def merge(self, other: BoundingBoxCollection) -> BoundingBoxCollection:
209211
:return: The merged bounding box collection.
210212
"""
211213
assert (
212-
self.reference_frame == other.reference_frame
214+
self.reference_frame == other.reference_frame
213215
), "The reference frames of the bounding box collections must be the same."
214216
return BoundingBoxCollection(
215217
reference_frame=self.reference_frame,
216218
shapes=self.bounding_boxes + other.bounding_boxes,
217219
)
218220

219221
def bloat(
220-
self, x_amount: float = 0.0, y_amount: float = 0, z_amount: float = 0
222+
self, x_amount: float = 0.0, y_amount: float = 0, z_amount: float = 0
221223
) -> BoundingBoxCollection:
222224
"""
223225
Enlarges all bounding boxes in the collection by a given amount in all dimensions.
@@ -235,10 +237,10 @@ def bloat(
235237

236238
@classmethod
237239
def from_simple_event(
238-
cls,
239-
reference_frame: KinematicStructureEntity,
240-
simple_event: SimpleEvent,
241-
keep_surface: bool = False,
240+
cls,
241+
reference_frame: KinematicStructureEntity,
242+
simple_event: SimpleEvent,
243+
keep_surface: bool = False,
242244
) -> BoundingBoxCollection:
243245
"""
244246
Create a list of bounding boxes from a simple random event.
@@ -250,9 +252,9 @@ def from_simple_event(
250252
"""
251253
result = []
252254
for x, y, z in itertools.product(
253-
simple_event[SpatialVariables.x.value].simple_sets,
254-
simple_event[SpatialVariables.y.value].simple_sets,
255-
simple_event[SpatialVariables.z.value].simple_sets,
255+
simple_event[SpatialVariables.x.value].simple_sets,
256+
simple_event[SpatialVariables.y.value].simple_sets,
257+
simple_event[SpatialVariables.z.value].simple_sets,
256258
):
257259

258260
bb = BoundingBox(
@@ -262,7 +264,9 @@ def from_simple_event(
262264
x.upper,
263265
y.upper,
264266
z.upper,
265-
TransformationMatrix(reference_frame=reference_frame),
267+
TransformationMatrix.from_xyz_quaternion(x.upper - (x.upper - x.lower) / 2, y.upper - (y.upper - y.lower) / 2,
268+
z.upper - (z.upper - z.lower) / 2, 0, 0, 0, 1,
269+
reference_frame=reference_frame)
266270
)
267271
if not keep_surface and (bb.depth == 0 or bb.height == 0 or bb.width == 0):
268272
continue
@@ -271,7 +275,7 @@ def from_simple_event(
271275

272276
@classmethod
273277
def from_event(
274-
cls, reference_frame: KinematicStructureEntity, event: Event
278+
cls, reference_frame: KinematicStructureEntity, event: Event
275279
) -> Self:
276280
"""
277281
Create a list of bounding boxes from a random event.
@@ -301,7 +305,7 @@ def from_shapes(cls, shapes: ShapeCollection) -> Self:
301305
return cls(shapes=[])
302306
for shape in shapes:
303307
assert (
304-
shape.origin.reference_frame == shapes[0].origin.reference_frame
308+
shape.origin.reference_frame == shapes[0].origin.reference_frame
305309
), "All shapes must have the same reference frame."
306310

307311
local_bbs = [shape.local_frame_bounding_box for shape in shapes]
@@ -339,5 +343,5 @@ def bounding_box(self) -> BoundingBox:
339343
max(all_x),
340344
max(all_y),
341345
max(all_z),
342-
self.reference_frame.global_pose,
346+
TransformationMatrix.from_xyz_quaternion(reference_frame=self.reference_frame),
343347
)

0 commit comments

Comments
 (0)