diff --git a/docs/robot/autonomy/1_sensors/gimbal.md b/docs/robot/autonomy/1_sensors/gimbal.md new file mode 100644 index 000000000..e009674b2 --- /dev/null +++ b/docs/robot/autonomy/1_sensors/gimbal.md @@ -0,0 +1,33 @@ + +# **Gimbal Extension** + +## **Overview** +The **Gimbal Extension** provides an easy way to integrate a controllable gimbal into an existing drone model within the scene. This extension is designed to facilitate the attachment and operation of a camera-equipped gimbal, allowing for real-time adjustments to pitch and yaw angles via ROS 2 messages. + + +## **Installation and Activation** +To enable the **Gimbal Extension**, follow these steps: + +1. Open the **Extensions** window by navigating to: + **Window** → **Extensions** +2. Under the **THIRD PARTIES** section, go to the **User** tab. +3. Locate the **Gimbal Extension** and turn it on. +4. Once enabled, a new **Gimbal Extension** window should appear. + +## **Adding a Gimbal to a Drone** +To attach a gimbal to an existing UAV model: + +1. Copy the **prim path** of the UAV to which you want to add the gimbal. +2. In the **Gimbal Extension** window, paste the copied path into the **Robot Prim Path** text box. +3. Set the **Robot Index** based on the `DOMAIN_ID` of the drone. + - The `DOMAIN_ID` should match the identifier used for the robot to ensure proper communication. + +For a step-by-step demonstration, refer to the video tutorial below: + + + +## **Gimbal Camera Image Topic** +Once the gimbal is successfully added, the camera image feed from the gimbal will be published on the following ROS 2 topic: `/robot_/gimbal/rgb`. + +## **Controlling the Gimbal** +The gimbal pitch and yaw angles can be controled by the ros2 messages `/robot_/gimbal/desired_gimbal_pitch` and `/robot_/gimbal/desired_gimbal_yaw` of type `std_msgs/msg/Float64`, respectively. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 8ba4fb5de..92af1aad0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - docs/robot/autonomy/0_interface/index.md - Sensors: - docs/robot/autonomy/1_sensors/index.md + - docs/robot/autonomy/1_sensors/gimbal.md - Perception: - docs/robot/autonomy/2_perception/index.md - docs/robot/autonomy/2_perception/state_estimation.md diff --git a/robot/ros_ws/src/autonomy/1_sensors/gimbal_stabilizer/gimbal_stabilizer/gimbal_stabilizer_node.py b/robot/ros_ws/src/autonomy/1_sensors/gimbal_stabilizer/gimbal_stabilizer/gimbal_stabilizer_node.py index 2bfe5e721..c923f44e6 100644 --- a/robot/ros_ws/src/autonomy/1_sensors/gimbal_stabilizer/gimbal_stabilizer/gimbal_stabilizer_node.py +++ b/robot/ros_ws/src/autonomy/1_sensors/gimbal_stabilizer/gimbal_stabilizer/gimbal_stabilizer_node.py @@ -4,25 +4,38 @@ from rclpy.node import Node from nav_msgs.msg import Odometry from sensor_msgs.msg import JointState -from transforms3d.euler import quat2euler +# from transforms3d.euler import quat2euler +from std_msgs.msg import Float64 # Assuming the desired yaw is published as a Float64 class GimbalStabilizerNode(Node): def __init__(self): super().__init__('gimbal_stabilizer') # Publisher to send joint commands - self.joint_pub = self.create_publisher(JointState, '/joint_command', 10) + self.joint_pub = self.create_publisher(JointState, 'gimbal/joint_command', 10) # Subscriber to receive drone odometry - self.create_subscription(Odometry, '/robot_1/odometry_conversion/odometry', self.odometry_callback, 10) - self.create_subscription(JointState, '/joint_states', self.joint_callback, 10) + self.create_subscription(Odometry, 'odometry_conversion/odometry', self.odometry_callback, 10) + self.create_subscription(JointState, 'gimbal/joint_states', self.joint_callback, 10) + self.create_subscription(Float64, 'gimbal/desired_gimbal_yaw', self.yaw_callback, 10) + self.create_subscription(Float64, 'gimbal/desired_gimbal_pitch', self.pitch_callback, 10) # Initialize joint state message self.joint_command = JointState() self.joint_command.name = ["yaw_joint","roll_joint", "pitch_joint"] self.joint_command.position = [0.0, 0.0, 0.0] + self.desired_yaw = 0.0 + self.desired_pitch = 0.0 + + def yaw_callback(self, msg): + self.desired_yaw = msg.data + # self.get_logger().info(f"Received desired yaw angle: {self.desired_yaw}") + + def pitch_callback(self, msg): + self.desired_pitch = msg.data def joint_callback(self, msg): + self.got_joint_states = True # Inverse the drone angles to stabilize the gimbal # self.joint_command.position[0] = -roll # roll joint # self.joint_command.position[1] = -pitch # pitch joint @@ -33,7 +46,7 @@ def joint_callback(self, msg): # self.joint_command.position[0] = -20.0/180*3.14 # yaw joint # self.joint_command.position[1] = 10.0/180*3.14 # roll joint # self.joint_command.position[2] = 20.0/180*3.14 # pitch joint - self.joint_command.velocity = [float('nan'), float('nan'), float('nan')] + # self.joint_command.velocity = [float('nan'), float('nan'), float('nan')] # self.joint_command.velocity = [-1.0, -1.0, -1.0] # Publish the joint command @@ -50,18 +63,18 @@ def odometry_callback(self, msg): ] # Convert quaternion to Euler angles (roll, pitch, yaw) - roll, pitch, yaw = quat2euler(quaternion, axes='sxyz') + # roll, pitch, yaw = quat2euler(quaternion, axes='sxyz') # Inverse the drone angles to stabilize the gimbal # self.joint_command.position[0] = -roll # roll joint # self.joint_command.position[1] = -pitch # pitch joint # self.joint_command.position[2] = -yaw # yaw joint - self.joint_command.position[0] = -0.0/180*3.14 # yaw joint - self.joint_command.position[1] = -roll # roll joint - self.joint_command.position[2] = 0.0/180*3.14 # pitch joint + self.joint_command.position[0] = -self.desired_yaw/180*3.14 # yaw joint + self.joint_command.position[1] = -0.0/180*3.14 # roll joint + self.joint_command.position[2] = self.desired_pitch/180*3.14 # pitch joint self.joint_command.velocity = [float('nan'), float('nan'), float('nan')] - self.joint_command.velocity = [-1.0, -1.0, -1.0] + # self.joint_command.velocity = [-1.0, -1.0, -1.0] # Publish the joint command self.joint_pub.publish(self.joint_command) @@ -78,4 +91,4 @@ def main(): rclpy.shutdown() if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/robot/ros_ws/src/autonomy/1_sensors/sensors_bringup/launch/sensors.launch.xml b/robot/ros_ws/src/autonomy/1_sensors/sensors_bringup/launch/sensors.launch.xml index d59762074..ff55ac15a 100644 --- a/robot/ros_ws/src/autonomy/1_sensors/sensors_bringup/launch/sensors.launch.xml +++ b/robot/ros_ws/src/autonomy/1_sensors/sensors_bringup/launch/sensors.launch.xml @@ -4,4 +4,7 @@ + + \ No newline at end of file diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/__init__.py b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/__init__.py new file mode 100644 index 000000000..a54368b2a --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/__init__.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +from .extension import * diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/extension.py b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/extension.py new file mode 100644 index 000000000..b58d5e9d6 --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/extension.py @@ -0,0 +1,219 @@ +import omni.ext +import subprocess + +import omni.ui as ui +import omni.kit.window.filepicker as filepicker +from omni.isaac.core import World +from omni.isaac.core.utils.stage import add_reference_to_stage +from pxr import Usd +from omni.isaac.core import World +# from omni.ext import get_extension_path +import omni.usd +from pxr import UsdGeom, Gf, UsdPhysics +import omni.graph.core as og +import time + +class MyExtension(omni.ext.IExt): + def on_startup(self): + """Called when the extension is loaded.""" + print("[GimbalExtension] Startup called") # Debug log + + # self.world = World.get_instance() + self.ui = GimbalUI() + print("[GimbalExtension] UI initialized") # Debug log + + def on_shutdown(self): + """Called when the extension is unloaded.""" + # self.world.cleanup() + self.ui.window.destroy() + +class GimbalUI: + def __init__(self): + self.window = ui.Window("Gimbal Extension UI", width=400, height=300) + self.usd_file_path = "" + self.robot_prim_path = "" + self.robot_name = 1 + self.default_usd_path = "omniverse://airlab-storage.andrew.cmu.edu:8443/Library/Assets/Gimbal/gimbal_test.usd" + self.default_robot_path = "/World/TEMPLATE_spirit_uav_robot/map_FLU/spirit_uav" + + self.build_ui() + + def build_ui(self): + with self.window.frame: + with ui.VStack(spacing=10): + # USD File Input + ui.Label("USD File Path", height=20) + with ui.HStack(height=30): + self.usd_input_field = ui.StringField( + model=ui.SimpleStringModel(self.default_usd_path), height=20 + ) + ui.Button("Browse", clicked_fn=self.open_file_picker) + + # Robot Prim Path Input + ui.Label("Robot Prim Path (copy from Stage)", height=20) + self.robot_prim_input_field = ui.StringField( + model=ui.SimpleStringModel(self.default_robot_path), height=20 + ) + + # Robot Name Input + ui.Label("Robot Index", height=20) + self.robot_name_input_field = ui.StringField( + model=ui.SimpleStringModel("2"), height=20 + ) + # Apply Button + ui.Button("Apply and Add Gimbal", height=40, clicked_fn=self.on_apply) + + def open_file_picker(self): + """Opens a file picker to select the USD file.""" + file_picker = filepicker.FilePickerDialog( + title="Select Gimbal USD File", + apply_button_label="Select", + file_extension_filter="*.usd", + apply_clicked_fn=self.on_file_selected + ) + file_picker.show() + + def on_file_selected(self, file_path): + """Callback for file selection.""" + self.usd_input_field.model.set_value(file_path) + + def on_apply(self): + """Apply button callback to add the gimbal and configure the ActionGraph.""" + self.usd_file_path = self.usd_input_field.model.get_value_as_string() + self.robot_prim_path = self.robot_prim_input_field.model.get_value_as_string() + self.robot_name = self.robot_name_input_field.model.get_value_as_string() + + # Validate input + if not self.usd_file_path or not self.robot_prim_path or not self.robot_name: + print("Please fill in all fields.") + return + + # Add the gimbal to the stage + self.add_gimbal_to_stage() + + def find_existing_op(self, xform_ops, op_type): + for op in xform_ops: + if op.GetOpName() == op_type: + return op + return None + + def add_gimbal_to_stage(self): + """Adds the gimbal to the robot and configures the OmniGraph.""" + stage = omni.usd.get_context().get_stage() + + # Add the USD file reference + gimbal_prim_path = f"{self.robot_prim_path}/gimbal" + add_reference_to_stage(self.usd_file_path, gimbal_prim_path) + + + # Apply transformations (scale and translation) + gimbal_prim = stage.GetPrimAtPath(gimbal_prim_path) + if gimbal_prim.IsValid(): + gimbal_xform = UsdGeom.Xformable(gimbal_prim) + xform_ops = gimbal_xform.GetOrderedXformOps() + + # Check if a scale operation already exists + scale_op = self.find_existing_op(xform_ops, "xformOp:scale") + if not scale_op: + scale_op = gimbal_xform.AddScaleOp() + scale_value = Gf.Vec3d(0.01, 0.01, 0.01) + scale_op.Set(scale_value) + + # Check if a translate operation already exists + translate_op = self.find_existing_op(xform_ops, "xformOp:translate") + if not translate_op: + translate_op = gimbal_xform.AddTranslateOp() + translation_value = Gf.Vec3d(0.02, 0.015, 0.1) + translate_op.Set(translation_value) + + print(f"Gimbal added at {gimbal_prim_path} with scale {scale_value} and translation {translation_value}.") + + # Add a fixed joint between the gimbal and the robot + self.add_fixed_joint(stage, self.robot_prim_path, gimbal_prim_path) + + # """Enables the ActionGraph within the gimbal and sets inputs.""" + action_graph_path = f"{gimbal_prim_path}/ActionGraph" + action_graph_prim = stage.GetPrimAtPath(action_graph_path) + + if action_graph_prim.IsValid(): + # Access the graph + graph = og.Controller.graph(action_graph_path) + graph_handle = og.get_graph_by_path(action_graph_path) + + # Set the input value + node_path = "/World/ActionGraph/MyNode" # Replace with the path to your node + input_name = "myInput" # Replace with the name of the input + value = 10 # Replace with the value you want to set + + # Define the path to ros2_context node + ros2_context_path = action_graph_path+"/ros2_context" + self.list_node_attributes(ros2_context_path) + + self.set_or_create_node_attribute(ros2_context_path, "inputs:domain_id", int(self.robot_name)) + self.set_or_create_node_attribute(action_graph_path+"/ros2_subscriber", "inputs:topicName", "robot_"+self.robot_name+"/gimbal/not_used") + self.set_or_create_node_attribute(action_graph_path+"/ros2_subscribe_joint_state", "inputs:topicName", "robot_"+self.robot_name+"/gimbal/joint_command") + self.set_or_create_node_attribute(action_graph_path+"/ros2_publish_joint_state", "inputs:topicName", "robot_"+self.robot_name+"/gimbal/joint_states") + self.set_or_create_node_attribute(action_graph_path+"/ros2_camera_helper", "inputs:topicName", "robot_"+self.robot_name+"/gimbal/rgb") + + # og.Controller.attribute(action_graph_path+"/ros2_context.inputs:domain_id").set(int(self.robot_name)) + # og.Controller.attribute(action_graph_path+"/ros2_subscriber.inputs:topicName").set("robot_"+self.robot_name+"/gimbal_yaw_control") + # og.Controller.attribute(action_graph_path+"/ros2_subscribe_joint_state.inputs:topicName").set("robot_"+self.robot_name+"/joint_command") + # og.Controller.attribute(action_graph_path+"/ros2_publish_joint_state.inputs:topicName").set("robot_"+self.robot_name+"/joint_states") + + + def add_fixed_joint(self, stage, robot_prim_path, gimbal_prim_path): + """Adds a fixed joint between the robot and the gimbal.""" + joint_path = f"{gimbal_prim_path}/FixedJoint" + joint_prim = stage.DefinePrim(joint_path, "PhysicsFixedJoint") + + # Define the fixed joint's relationship to the robot and gimbal components + joint = UsdPhysics.FixedJoint(joint_prim) + if not joint: + print(f"Failed to create fixed joint at {joint_path}") + return + + # Set joint relationships + joint.CreateBody0Rel().SetTargets([f"{robot_prim_path}/base_link/meshes/Cone"]) + joint.CreateBody1Rel().SetTargets([f"{gimbal_prim_path}/yaw"]) + + print(f"Fixed joint created between {robot_prim_path} and {gimbal_prim_path}.") + + def list_node_attributes(self, node_path): + """Lists all attributes of a given node in OmniGraph.""" + stage = omni.usd.get_context().get_stage() + + if not stage: + print("Error: USD stage not found.") + return + + node_prim = stage.GetPrimAtPath(node_path) + + if not node_prim.IsValid(): + print(f"Error: Node not found at {node_path}") + return + + print(f"Attributes in node '{node_path}':") + + for attr in node_prim.GetAttributes(): + print(f"- {attr.GetName()}") + + def set_or_create_node_attribute(self, node_path, attribute_name, value): + """Sets an attribute value for a given node in OmniGraph, creating it if necessary.""" + stage = omni.usd.get_context().get_stage() + node_prim = stage.GetPrimAtPath(node_path) + + if not node_prim.IsValid(): + print(f"Error: Node not found at {node_path}") + return + + attr = node_prim.GetAttribute(attribute_name) + + if not attr: + print(f"Attribute {attribute_name} not found, creating it...") + attr = node_prim.CreateAttribute(attribute_name, Sdf.ValueTypeNames.Int) + + if attr: + attr.Set(value) + print(f"Set {attribute_name} to {value} on node {node_path}.") + else: + print(f"Failed to create or set attribute {attribute_name}.") diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/__init__.py b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/__init__.py new file mode 100644 index 000000000..880c75da1 --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/__init__.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +from .test_hello_world import * diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/test_hello_world.py b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/test_hello_world.py new file mode 100644 index 000000000..77e854a40 --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/airlab/gimbal/tests/test_hello_world.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +# NOTE: +# omni.kit.test - std python's unittest module with additional wrapping to add suport for async/await tests +# For most things refer to unittest docs: https://docs.python.org/3/library/unittest.html +import omni.kit.test + +# Extension for writing UI tests (to simulate UI interaction) +import omni.kit.ui_test as ui_test + +# Import extension python module we are testing with absolute import path, as if we are external user (other extension) +import airlab.tmux_manager + + +# Having a test class dervived from omni.kit.test.AsyncTestCase declared on the root of module will make it auto-discoverable by omni.kit.test +class Test(omni.kit.test.AsyncTestCase): + # Before running each test + async def setUp(self): + pass + + # After running each test + async def tearDown(self): + pass + + # Actual test, notice it is an "async" function, so "await" can be used if needed + async def test_hello_public_function(self): + result = airlab.tmux_manager.some_public_function(4) + self.assertEqual(result, 256) + + async def test_window_button(self): + + # Find a label in our window + label = ui_test.find("TMUX Manager//Frame/**/Label[*]") + + # Find buttons in our window + add_button = ui_test.find("TMUX Manager//Frame/**/Button[*].text=='Add'") + reset_button = ui_test.find("TMUX Manager//Frame/**/Button[*].text=='Reset'") + + # Click reset button + await reset_button.click() + self.assertEqual(label.widget.text, "empty") + + await add_button.click() + self.assertEqual(label.widget.text, "count: 1") + + await add_button.click() + self.assertEqual(label.widget.text, "count: 2") diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/config/extension.toml b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/config/extension.toml new file mode 100644 index 000000000..6f891211d --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/config/extension.toml @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-NvidiaProprietary +# +# NVIDIA CORPORATION, its affiliates and licensors retain all intellectual +# property and proprietary rights in and to this material, related +# documentation and any modifications thereto. Any use, reproduction, +# disclosure or distribution of this material and related documentation +# without an express license agreement from NVIDIA CORPORATION or +# its affiliates is strictly prohibited. + +[package] +# Semantic Versionning is used: https://semver.org/ +version = "0.1.0" + +# Lists people or organizations that are considered the "authors" of the package. +authors = [ + "Author Name ", +] + +# The title and description fields are primarily for displaying extension info in the UI +title = 'Gimbal extension' +description = "The simplest python UI extension example. Use it as a starting point for your extensions." + +# Path (relative to the root) or content of readme markdown file for UI. +readme = "docs/README.md" + +# Path (relative to the root) of changelog +# More info on writing changelog: https://keepachangelog.com/en/1.0.0/ +changelog = "docs/CHANGELOG.md" + +# URL of the extension source repository. +# repository = "https://github.com/example/repository_name" + +# One of categories for the UI. +category = "Example" + +# Keywords for the extension +keywords = ["kit", "example"] + +# Watch the .ogn files for hot reloading (only works for Python files) +[fswatcher.patterns] +include = ["*.ogn", "*.py"] +exclude = ["Ogn*Database.py"] + +# Preview image and icon. Folder named "data" automatically goes in git lfs (see .gitattributes file). +# Preview image is shown in "Overview" of Extensions window. Screenshot of an extension might be a good preview image. +preview_image = "data/preview.png" + +# Icon is shown in Extension manager. It is recommended to be square, of size 256x256. +icon = "data/icon.png" + +# Use omni.ui to build simple UI +[dependencies] +"omni.kit.uiapp" = {} +"omni.kit.test" = {} +"omni.graph" = {} + +# Main python module this extension provides, it will be publicly available as "import airlab.gimbal". +[[python.module]] +name = "airlab.gimbal" + +[[test]] +# Extra dependencies only to be used during test run +dependencies = [ + "omni.kit.test", + "omni.kit.ui_test" # UI testing extension +] + +[documentation] +pages = [ + "docs/Overview.md", + "docs/CHANGELOG.md", +] \ No newline at end of file diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/icon.png b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/icon.png new file mode 100644 index 000000000..70e17a5c9 Binary files /dev/null and b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/icon.png differ diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/preview.png b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/preview.png new file mode 100644 index 000000000..5c3fc1967 Binary files /dev/null and b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/data/preview.png differ diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/CHANGELOG.md b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/CHANGELOG.md new file mode 100644 index 000000000..3c031de5e --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + + +## [0.1.0] - 2024-07-17 +- Initial version of extension UI template with a window diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/Overview.md b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/Overview.md new file mode 100644 index 000000000..4bba659eb --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/Overview.md @@ -0,0 +1 @@ +# Overview \ No newline at end of file diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/README.md b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/README.md new file mode 100644 index 000000000..1c0c5033e --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/docs/README.md @@ -0,0 +1,3 @@ +# TMUX Manager [airlab.tmux_manager] + +A simple python UI extension example. Use it as a starting point for your extensions. diff --git a/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/premake5.lua b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/premake5.lua new file mode 100644 index 000000000..e2926d0eb --- /dev/null +++ b/simulation/isaac-sim/sitl_integration/kit-app-template/source/extensions/airlab.gimbal/premake5.lua @@ -0,0 +1,11 @@ +-- Use folder name to build extension name and tag. Version is specified explicitly. +local ext = get_current_extension_info() + +project_ext (ext) + +-- Link only those files and folders into the extension target directory +repo_build.prebuild_link { + { "data", ext.target_dir.."/data" }, + { "docs", ext.target_dir.."/docs" }, + { "airlab", ext.target_dir.."/airlab" }, +}