Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ clangd/
# generated compose files
compose.yml
compose_pytest.yml
foxglove.json
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Large diffs are not rendered by default.

Binary file not shown.

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<contributor>
<authoring_tool>ctmconv</authoring_tool>
<comments>______________________________________________________________________ </comments>
</contributor>
<created>2026-02-24T13:31:11</created>
<modified>2026-02-24T13:31:11</modified>
<up_axis>Z_UP</up_axis>
</asset>
<library_geometries>
<geometry id="Mesh-1" name="Mesh-1">
<mesh>
<source id="Mesh-1-positions" name="position">
<float_array id="Mesh-1-positions-array" count="312">-0.0514649 0.000138856 0.015748 -0.0511064 -0.00606556 0.015748 -0.0510729 0.00634125 0.015748 -0.0507998 0.000137062 0.053848 -0.050446 -0.00598718 0.053848 -0.0504129 0.0062593 0.053848 -0.0500026 -0.0121815 0.015748 -0.0499361 0.0124512 0.015748 -0.0493565 -0.0120241 0.053848 -0.0492909 0.0122903 0.053848 -0.0481697 -0.0181199 0.015748 -0.0480712 0.0183795 0.015748 -0.0475473 -0.0178857 0.053848 -0.0474501 0.018142 0.053848 -0.0456344 -0.023794 0.015748 -0.0455053 0.0240399 0.015748 -0.0450447 -0.0234865 0.053848 -0.0449173 0.0237292 0.053848 -0.0424336 -0.0291211 0.015748 -0.0422759 0.0293496 0.015748 -0.0418853 -0.0287448 0.053848 -0.0417296 0.0289704 0.053848 -0.0386141 -0.0340236 0.015748 -0.0384299 0.0342314 0.015748 -0.0381151 -0.0335839 0.053848 -0.0379333 0.0337891 0.053848 -0.0342314 -0.0384299 0.015748 -0.0340236 0.0386141 0.015748 -0.0337891 -0.0379333 0.053848 -0.0335839 0.0381151 0.053848 -0.0293496 -0.0422759 0.015748 -0.0291211 0.0424336 0.015748 -0.0289704 -0.0417296 0.053848 -0.0287448 0.0418853 0.053848 -0.0240399 -0.0455053 0.015748 -0.023794 0.0456344 0.015748 -0.0237292 -0.0449173 0.053848 -0.0234865 0.0450447 0.053848 -0.0183795 -0.0480712 0.015748 -0.018142 -0.0474501 0.053848 -0.0181199 0.0481697 0.015748 -0.0178857 0.0475473 0.053848 -0.0124512 -0.0499361 0.015748 -0.0122903 -0.0492909 0.053848 -0.0121815 0.0500026 0.015748 -0.0120241 0.0493565 0.053848 -0.00634125 -0.0510729 0.015748 -0.0062593 -0.0504129 0.053848 -0.00606556 0.0511064 0.015748 -0.00598718 0.050446 0.053848 -0.000138856 -0.0514649 0.015748 -0.000137062 -0.0507998 0.053848 0.000137062 0.0507998 0.053848 0.000138856 0.0514649 0.015748 0.00598718 -0.050446 0.053848 0.00606556 -0.0511064 0.015748 0.0062593 0.0504129 0.053848 0.00634125 0.0510729 0.015748 0.0120241 -0.0493565 0.053848 0.0121815 -0.0500026 0.015748 0.0122903 0.0492909 0.053848 0.0124512 0.0499361 0.015748 0.0178857 -0.0475473 0.053848 0.0181199 -0.0481697 0.015748 0.018142 0.0474501 0.053848 0.0183795 0.0480712 0.015748 0.0234865 -0.0450447 0.053848 0.0237292 0.0449173 0.053848 0.023794 -0.0456344 0.015748 0.0240399 0.0455053 0.015748 0.0287448 -0.0418853 0.053848 0.0289704 0.0417296 0.053848 0.0291211 -0.0424336 0.015748 0.0293496 0.0422759 0.015748 0.0335839 -0.0381151 0.053848 0.0337891 0.0379333 0.053848 0.0340236 -0.0386141 0.015748 0.0342314 0.0384299 0.015748 0.0379333 -0.0337891 0.053848 0.0381151 0.0335839 0.053848 0.0384299 -0.0342314 0.015748 0.0386141 0.0340236 0.015748 0.0417296 -0.0289704 0.053848 0.0418853 0.0287448 0.053848 0.0422759 -0.0293496 0.015748 0.0424336 0.0291211 0.015748 0.0449173 -0.0237292 0.053848 0.0450447 0.0234865 0.053848 0.0455053 -0.0240399 0.015748 0.0456344 0.023794 0.015748 0.0474501 -0.018142 0.053848 0.0475473 0.0178857 0.053848 0.0480712 -0.0183795 0.015748 0.0481697 0.0181199 0.015748 0.0492909 -0.0122903 0.053848 0.0493565 0.0120241 0.053848 0.0499361 -0.0124512 0.015748 0.0500026 0.0121815 0.015748 0.0504129 -0.0062593 0.053848 0.050446 0.00598718 0.053848 0.0507998 -0.000137062 0.053848 0.0510729 -0.00634125 0.015748 0.0511064 0.00606556 0.015748 0.0514649 -0.000138856 0.015748 </float_array>
<technique_common>
<accessor count="104" offset="0" source="#Mesh-1-positions-array" stride="3">
<param name="X" type="float" />
<param name="Y" type="float" />
<param name="Z" type="float" />
</accessor>
</technique_common>
</source>
<vertices id="Mesh-1-vertices">
<input semantic="POSITION" source="#Mesh-1-positions" />
</vertices>
<triangles count="104">
<input offset="0" semantic="VERTEX" source="#Mesh-1-vertices" />
<p>100 103 102 100 102 99 99 102 97 99 97 95 95 97 93 95 93 91 91 93 89 91 89 87 87 89 85 87 85 83 83 85 81 83 81 79 79 81 77 79 77 75 75 77 73 75 73 71 71 73 69 71 69 67 67 69 65 67 65 64 64 65 61 64 61 60 60 61 57 60 57 56 56 57 53 56 53 52 52 53 48 52 48 49 49 48 44 49 44 45 45 44 40 45 40 41 41 40 35 41 35 37 37 35 31 37 31 33 33 31 27 33 27 29 29 27 23 29 23 25 25 23 19 25 19 21 21 19 15 21 15 17 17 15 11 17 11 13 13 11 7 13 7 9 9 7 2 9 2 5 5 2 0 5 0 3 3 0 1 3 1 4 4 1 6 4 6 8 8 6 10 8 10 12 12 10 14 12 14 16 16 14 18 16 18 20 20 18 22 20 22 24 24 22 26 24 26 28 28 26 30 28 30 32 32 30 34 32 34 36 36 34 38 36 38 39 39 38 42 39 42 43 43 42 46 43 46 47 47 46 50 47 50 51 51 50 55 51 55 54 54 55 59 54 59 58 58 59 63 58 63 62 62 63 68 62 68 66 66 68 72 66 72 70 70 72 76 70 76 74 74 76 80 74 80 78 78 80 84 78 84 82 82 84 88 82 88 86 86 88 92 86 92 90 90 92 96 90 96 94 94 96 101 94 101 98 98 101 103 98 103 100 </p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_visual_scenes>
<visual_scene id="Scene-1" name="Scene-1">
<node id="Object-1" name="Object-1">
<instance_geometry url="#Mesh-1" />
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#Scene-1" />
</scene>
</COLLADA>
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ Based on:
</inertial>
<visual>
<geometry>
<mesh filename="package://velodyne_description/meshes/VLP16_base_1.dae" />
<mesh filename="package://alliander_description/velodyne/meshes/VLP16_base_1.dae" />
</geometry>
</visual>
<visual>
<geometry>
<mesh filename="package://velodyne_description/meshes/VLP16_base_2.dae" />
<mesh filename="package://alliander_description/velodyne/meshes/VLP16_base_2.dae" />
</geometry>
</visual>
<collision>
Expand All @@ -69,7 +69,7 @@ Based on:
<visual>
<origin xyz="0 0 -0.0377" />
<geometry>
<mesh filename="package://velodyne_description/meshes/VLP16_scan.dae" />
<mesh filename="package://alliander_description/velodyne/meshes/VLP16_scan.dae" />
</geometry>
</visual>
</link>
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -358,10 +358,12 @@ class VisualizationConfig(Config):

Attributes:
rviz (bool): Whether to enable RViz visualization.
foxglove (bool): Whether to enable Foxglove visualization.
vizanti (bool): Whether to enable Vizanti visualization.
gui (bool): Whether to enable GUI.
"""

rviz: bool = True
rviz: bool = False
foxglove: bool = True
vizanti: bool = False
gui: bool = False
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ def state_publisher_node(
xacro_arguments = {}
xacro_path = get_file_path("alliander_description", [platform, "urdf"], xacro)
robot_description = get_robot_description(xacro_path, xacro_arguments)
change_meshes(robot_description)

return Node(
package="robot_state_publisher",
executable="robot_state_publisher",
Expand All @@ -40,6 +42,39 @@ def state_publisher_node(
)


def change_meshes(robot_description: dict) -> None:
"""Change .dae and .stl mesh files to .glb in the robot description.

Args:
robot_description (dict): The robot description dictionary.
"""
robot_description_str = str(robot_description["robot_description"])
robot_description_str = robot_description_str.replace(".dae", ".glb")
robot_description_str = robot_description_str.replace(".stl", ".glb")

for original, replacement in [
("husarion_ugv_description", "husarion"),
("franka_description", "franka"),
("realsense2_description", "realsense"),
("zed_msgs", "zed"),
("husarion_components_description", "nmea_gps"),
]:
robot_description_str = robot_description_str.replace(
rf'<mesh filename="package://{original}',
rf'<mesh filename="package://alliander_description/{replacement}',
)
robot_description_str = robot_description_str.replace(
rf'<mesh filename="file:///opt/ros/jazzy/share/{original}',
rf'<mesh filename="package://alliander_description/{replacement}',
)
robot_description_str = robot_description_str.replace(
rf'<mesh filename="file:///alliander/external/install/{original}/share/{original}',
rf'<mesh filename="package://alliander_description/{replacement}',
)

robot_description["robot_description"] = robot_description_str


def static_tf_node(
parent_frame: str,
child_frame: str,
Expand Down
1 change: 1 addition & 0 deletions alliander_visualization/alliander_visualization.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ RUN apt update && apt install -y --no-install-recommends \
ros-$ROS_DISTRO-rqt-tf-tree \
ros-$ROS_DISTRO-moveit-ros-visualization \
ros-$ROS_DISTRO-rviz-satellite \
ros-$ROS_DISTRO-foxglove-bridge \
&& rm -rf /var/lib/apt/lists/* \
&& apt autoremove -y \
&& apt clean
Expand Down
1 change: 1 addition & 0 deletions alliander_visualization/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ services:
volumes:
- "/tmp/.X11-unix:/tmp/.X11-unix"
- "/dev:/dev"
- "./foxglove.json:/foxglove.json"
command: ["/bin/bash", "-c", "ros2 launch alliander_visualization visualization.launch.py"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# SPDX-FileCopyrightText: Alliander N. V.
#
# SPDX-License-Identifier: Apache-2.0
import json

from alliander_utilities.ros_utils import get_file_path


class Foxglove:
"""A class to dynammically manage the Foxglove layout.

Attributes:
topics (list): The topics to bridge.
services (list): The services to bridge.
layout (dict): The Foxglove layout.
"""

topics: list = ["/clock", "/tf", "/tf_static"]
services: list = [""]
layout: dict

with open(
get_file_path("alliander_visualization", ["config"], "foxglove_layout.json"),
encoding="utf-8",
) as json_file:
layout = json.load(json_file)

@staticmethod
def create_layout_file() -> None:
"""Create the Foxglove layout file."""
panels = list(Foxglove.layout["configById"].keys())

layout = {"first": {}, "second": {}, "direction": "row"}
references = [layout]

if len(panels) == 1:
layout = panels.pop()

places = 2
while len(panels) > 0:
reference = references[0]
direction = "row" if reference["direction"] == "column" else "column"
if places < len(panels):
if reference["second"] == {}:
reference["second"] = {
"first": {},
"second": {},
"direction": direction,
}
references.append(reference["second"])
else:
reference["first"] = {
"first": {},
"second": {},
"direction": direction,
}
references.append(reference["first"])
references.pop(0)
places += 1
elif reference["second"] == {}:
reference["second"] = panels.pop()
else:
reference["first"] = panels.pop()
references.pop(0)

Foxglove.layout["layout"] = layout
with open("/foxglove.json", "w", encoding="utf-8") as outfile:
json.dump(Foxglove.layout, outfile)

@staticmethod
def add_platform_model(namespace: str) -> None:
"""Add a robot model to the Foxglove layout.

Args:
namespace (str): The namespace of the robot.
"""
Foxglove.topics.append([f"/{namespace}/robot_description"])
Foxglove.layout["configById"]["3D"]["layers"][f"urdf-{namespace}"] = {
"layerId": "foxglove.Urdf",
"sourceType": "topic",
"topic": f"/{namespace}/robot_description",
"framePrefix": f"{namespace}/",
}

@staticmethod
def add_street_map(namespace: str) -> None:
"""Add a street map to the Foxglove 3D panel.

Args:
namespace (str): The namespace of the platform.
"""
Foxglove.topics.append(f"/{namespace}/gps/fix")
Foxglove.topics.append(f"/{namespace}/gps/filtered")
Foxglove.layout["configById"]["3D"]["layers"]["map"] = {
"layerId": "foxglove.TiledMap",
"visible": True,
"serverConfig": "map",
"label": "Map",
}

@staticmethod
def add_image(namespace: str) -> None:
"""Add a camera feed to the Foxglove layout.

Args:
namespace (str): The namespace of the platform.
"""
Foxglove.topics.append(f"/{namespace}/color/image_raw")
Foxglove.topics.append(f"/{namespace}/color/camera_info")
Foxglove.layout["configById"][f"Image!{namespace}"] = {
"imageMode": {
"imageTopic": f"/{namespace}/color/image_raw",
"calibrationTopic": f"/{namespace}/color/camera_info",
}
}

@staticmethod
def add_pointcloud(namespace: str) -> None:
"""Add a pointcloud to the Foxglove 3D panel.

Args:
namespace (str): The namespace of the platform.
"""
Foxglove.topics.append(f"/{namespace}/scan/points")
Foxglove.layout["configById"]["3D"]["topics"][f"/{namespace}/scan/points"] = {
"visible": True,
"colorMode": "colormap",
"colorMap": "rainbow",
"colorField": "intensity",
}

@staticmethod
def add_map(topic: str) -> None:
"""Add a map to the Foxglove 3D panel.

Args:
topic (str): The topic of the costmap.
"""
Foxglove.topics.append(topic)
Foxglove.layout["configById"]["3D"]["topics"][topic] = {
"visible": True,
"colorMode": "costmap",
}

@staticmethod
def add_path(topic: str) -> None:
"""Add a path to the Foxglove 3D panel.

Args:
topic (str): The topic of the path.
"""
Foxglove.topics.append(topic)
Foxglove.layout["configById"]["3D"]["topics"][topic] = {
"visible": True,
"lineWidth": 0.03,
"gradient": ["#00ff00ff", "#00ff00ff"],
}

@staticmethod
def add_polygon(topic: str) -> None:
"""Add a polygon to the Foxglove 3D panel.

Args:
topic (str): The topic of the polygon.
"""
Foxglove.topics.append(topic)

@staticmethod
def add_trigger_service(name: str, service: str) -> None:
"""Add a service call panel to the Foxglove layout.

Args:
name (str): The name of the button.
service (str): The service to call when the button is pressed.
"""
Foxglove.services.append(service)
Foxglove.layout["configById"][f"CallService!{name}"] = {
"serviceName": service,
"foxglovePanelTitle": service,
"editingMode": False,
"buttonText": name,
}

@staticmethod
def add_joystick(namespace: str) -> None:
"""Add a virtual joystick to the Foxglove layout.

Args:
namespace (str): The namespace of the robot.
"""
Foxglove.layout["configById"]["virtual-joystick.Virtual Joystick"]["topic"] = (
f"/{namespace}/cmd_vel"
)
Loading
Loading