From 63384df110961883efd1e989c2f2050b53063990 Mon Sep 17 00:00:00 2001 From: franklinselva Date: Sun, 8 Sep 2024 14:21:26 +0200 Subject: [PATCH] Add extensions for IssacSim targetting Space ROS Related to #18 - Add new extension - Rover Simple Controller - Add example omnigraph extension - Negate Number --- .../omni.new.extension/config/extension.toml | 36 ++++++++ .../omni.new.extension/docs/CHANGELOG.md | 4 + extensions/omni.new.extension/docs/README.md | 3 + .../omni/new/extension/__init__.py | 14 +++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 852 bytes .../omni/new/extension/ogn/__init__.py | 0 .../ogn/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 211 bytes .../new/extension/ogn/nodes/NegateNumber.ogn | 29 +++++++ .../new/extension/ogn/nodes/NegateNumber.py | 18 ++++ .../__pycache__/NegateNumber.cpython-310.pyc | Bin 0 -> 765 bytes .../config/extension.toml | 38 ++++++++ .../data/node-preview.png | Bin 0 -> 36323 bytes .../docs/README.md | 3 + .../roversimplecontroller/__init__.py | 14 +++ .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1005 bytes .../roversimplecontroller/ogn/__init__.py | 0 .../ogn/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 245 bytes .../ogn/nodes/RoverSimpleController.ogn | 82 ++++++++++++++++++ .../ogn/nodes/RoverSimpleController.py | 78 +++++++++++++++++ .../RoverSimpleController.cpython-310.pyc | Bin 0 -> 1845 bytes 20 files changed, 319 insertions(+) create mode 100644 extensions/omni.new.extension/config/extension.toml create mode 100644 extensions/omni.new.extension/docs/CHANGELOG.md create mode 100644 extensions/omni.new.extension/docs/README.md create mode 100644 extensions/omni.new.extension/omni/new/extension/__init__.py create mode 100644 extensions/omni.new.extension/omni/new/extension/__pycache__/__init__.cpython-310.pyc create mode 100644 extensions/omni.new.extension/omni/new/extension/ogn/__init__.py create mode 100644 extensions/omni.new.extension/omni/new/extension/ogn/__pycache__/__init__.cpython-310.pyc create mode 100644 extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.ogn create mode 100644 extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.py create mode 100644 extensions/omni.new.extension/omni/new/extension/ogn/nodes/__pycache__/NegateNumber.cpython-310.pyc create mode 100644 extensions/omni.spaceros.roversimplecontroller/config/extension.toml create mode 100644 extensions/omni.spaceros.roversimplecontroller/data/node-preview.png create mode 100644 extensions/omni.spaceros.roversimplecontroller/docs/README.md create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/__init__.py create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/__pycache__/__init__.cpython-310.pyc create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__init__.py create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__pycache__/__init__.cpython-310.pyc create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.ogn create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.py create mode 100644 extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/__pycache__/RoverSimpleController.cpython-310.pyc diff --git a/extensions/omni.new.extension/config/extension.toml b/extensions/omni.new.extension/config/extension.toml new file mode 100644 index 0000000..a7aad1c --- /dev/null +++ b/extensions/omni.new.extension/config/extension.toml @@ -0,0 +1,36 @@ + +[package] +# Semantic Versioning is used: https://semver.org/ +version = "0.1.0" + +# Lists people or organizations that are considered the "authors" of the package. +authors = [] + +# The title and description fields are primarly for displaying extension info in UI +title = "Omniverse Graph Extension Example" +description="Example extension for OmniGraph nodes." + +# Path (relative to the root) or content of readme markdown file for UI. +readme = "docs/README.md" + +# URL of the extension source repository. +repository="https://gitlab-master.nvidia.com/omniverse/kit-extensions/example" + +# Categories for UI. +category = "Example" + +# Keywords for the extension +keywords = ["kit", "omnigraph"] + +# Watch the .ogn files for hot reloading (only works for Python files) +[fswatcher.patterns] +include = ["*.ogn", "*.py"] +exclude = ["Ogn*Database.py"] + +[dependencies] +"omni.kit.test" = {} +"omni.graph" = {} + +# Main python module this extension provides, it will be publicly available as "import omni.new.extension". +[[python.module]] +name = "omni.new.extension" diff --git a/extensions/omni.new.extension/docs/CHANGELOG.md b/extensions/omni.new.extension/docs/CHANGELOG.md new file mode 100644 index 0000000..80e2d56 --- /dev/null +++ b/extensions/omni.new.extension/docs/CHANGELOG.md @@ -0,0 +1,4 @@ +### 0.1.0 + + - Initial release of Omni New Extension + - Added simple steer and move based controller \ No newline at end of file diff --git a/extensions/omni.new.extension/docs/README.md b/extensions/omni.new.extension/docs/README.md new file mode 100644 index 0000000..72439a2 --- /dev/null +++ b/extensions/omni.new.extension/docs/README.md @@ -0,0 +1,3 @@ + +# OmniGraph Extension [omni.new.extension] +Extension with implementation of some OmniGraph nodes diff --git a/extensions/omni.new.extension/omni/new/extension/__init__.py b/extensions/omni.new.extension/omni/new/extension/__init__.py new file mode 100644 index 0000000..fd11976 --- /dev/null +++ b/extensions/omni.new.extension/omni/new/extension/__init__.py @@ -0,0 +1,14 @@ + +import omni.ext + +# Any class derived from `omni.ext.IExt` in a top level module (defined in `python.modules` of `extension.toml`) will be +# instantiated when the extension is enabled and `on_startup(ext_id)` will be called. Later when extension gets disabled +# on_shutdown() will be called. +class OmniNewExtensionExtension(omni.ext.IExt): + # ext_id is the current extension id. It can be used with the extension manager to query additional information, + # such as where this extension is located in the filesystem. + def on_startup(self, ext_id): + print("[omni.new.extension] OmniNewExtensionExtension startup", flush=True) + + def on_shutdown(self): + print("[omni.new.extension] OmniNewExtensionExtension shutdown", flush=True) diff --git a/extensions/omni.new.extension/omni/new/extension/__pycache__/__init__.cpython-310.pyc b/extensions/omni.new.extension/omni/new/extension/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a2aadcd793f69ce26ca307a9cdac630dbd8e201d GIT binary patch literal 852 zcmb7>Jx{|h5QgnIEm3JvN01mgz|etag9RZZgb+&^U@AgZRJ&>*af%Fq zWnx5ZOxz_cMdCx^Bwt?Jm)DQaS))-S5Y^_>;MF7K87E)Ehsiz+(}E<3pb6>GBO*M( z?ulTMUV4}8j`e&&Pf6QQ2~dlTRTZr z+?C_QiIvL4nfercw3!ELggb!o)`VmM4zV6Q3ZMwnoo=6JVAfG`+>vv;i`K6Pw2Y0k z%|~aC)KvyaZiXmEI##yrIp4@+;HqH6WAR8dHtt=A!z`8IKu78-i4~OGM4^y3p&3Pe zxur8xDiB*HPV*$PaHa5*#)RJqi=_~t7iA&mv5GC{ozbnUWs1*8k>L$n(WKLjU+>jf z{LrURJP*|K9%=>??AgwLdp69i$j0hSqm{0r4b=f^q$|@hD7TClv+4$JVVZ9KjXkUH zwks~+Tt%tm+|@WwGm$5#>zrTbQBv;cI{L0)S{z;|B+%HYB^U-N0}`-0E7s7wcn5eF e=c970kjIA;TU7jH=YrjmVh@cIE=t$YB5I^$FT literal 0 HcmV?d00001 diff --git a/extensions/omni.new.extension/omni/new/extension/ogn/__init__.py b/extensions/omni.new.extension/omni/new/extension/ogn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/omni.new.extension/omni/new/extension/ogn/__pycache__/__init__.cpython-310.pyc b/extensions/omni.new.extension/omni/new/extension/ogn/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f83a5656ae58c5465d33a62d0c891180aa7c55cc GIT binary patch literal 211 zcmYk0Q3}E^42C-yBErCH_^{;yBA&n(4^WEBwAiMj*`^-J8;P&p!Y5M&XCPnxeB>da zDCSCPy0|xKJ`?^clVu~#xu8}%wam6R6_XzE8sHc_LK6+0oFzs#7^raoZp|Lc82Fnc zlx%(POlJcDUOFU}#1K5$itt?h+(&_^@eIO&Kx<33)4FP}kJ-wHx_7uv|L{bRt2Y*U BI#vJx literal 0 HcmV?d00001 diff --git a/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.ogn b/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.ogn new file mode 100644 index 0000000..71a3458 --- /dev/null +++ b/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.ogn @@ -0,0 +1,29 @@ +{ + "NegateNumber": { + "version": 1, + "description": "Negates a number", + "language": "Python", + "metadata": { + "uiName": "Negate Number" + }, + "inputs": { + "a": { + "type": "int", + "description": "Input", + "default": 0, + "metadata": { + "uiName": "A" + } + } + }, + "outputs": { + "b": { + "type": "int", + "description": "Ouput", + "metadata": { + "uiName": "B" + } + } + } + } +} \ No newline at end of file diff --git a/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.py b/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.py new file mode 100644 index 0000000..b820161 --- /dev/null +++ b/extensions/omni.new.extension/omni/new/extension/ogn/nodes/NegateNumber.py @@ -0,0 +1,18 @@ +""" +This is the implementation of the OGN node defined in NegateNumber.ogn +""" + +# Array or tuple values are accessed as numpy arrays so you probably need this import +import numpy + + +class NegateNumber: + """ + Negates a number + """ + + @staticmethod + def compute(db) -> bool: + """Compute the outputs from the current input""" + db.outputs.b = -db.inputs.a + return True diff --git a/extensions/omni.new.extension/omni/new/extension/ogn/nodes/__pycache__/NegateNumber.cpython-310.pyc b/extensions/omni.new.extension/omni/new/extension/ogn/nodes/__pycache__/NegateNumber.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9829dea03a5dd13088ab3401f953ad69846c479 GIT binary patch literal 765 zcmY*X&2H2%5Vjrf58EG5)e{F~aa*KrUI3v=J(SaOpr>4{Y&_|v#P(t*rCo7ADtDfP zXW&JUubg-VPK>>)5=NOX<1gdP%xBx9qZvW_@$6^)jS})JE{2xj;vC(-Kv1M0iYnI9 zf?g7pDgKox{=f>R*n2V`ZQstW){PhVbZZce-n5{>bY<6ABW#`6cb7|HtOB8+ZVV{V z7_o%wvV&!>SKt=*+RT1qHL|QC4vpjD9NoV}08&uwh$&iRz!ig0%~DVNffi#m!j8t_ zaQN%(^O-=~QC^h7q>70QV+URDOob{uT8tU`1-d^$_)Kod4Lyi&CNo8`2HU>6uzJ&X zkjArphp`v6vpNw~?;L7?V8A7~jS`|Lnha~haheJAb-mihA~Ut0}%?Mm~Z zZH$NZw#*gW=6+LFaOSMvMHrmpUKY z&xblKHuqst?S?vzqetD3Gfuas|E29ymh(xNN@+?BQihq7+N!>d@u8F-`?B4CnMkQ@ z6=@%@M5UoyTNU`q+SVOli8T8F;?;Cf|EP(dB1{RVjGxlP&NKHExiG?G+}tN>usQB= W@eg78de_jKc$|KWz{zxUlKlmOPRuj_ literal 0 HcmV?d00001 diff --git a/extensions/omni.spaceros.roversimplecontroller/config/extension.toml b/extensions/omni.spaceros.roversimplecontroller/config/extension.toml new file mode 100644 index 0000000..e6b32c2 --- /dev/null +++ b/extensions/omni.spaceros.roversimplecontroller/config/extension.toml @@ -0,0 +1,38 @@ + +[package] +# Semantic Versioning is used: https://semver.org/ +version = "0.1.0" + +# Lists people or organizations that are considered the "authors" of the package. +authors = ["https://github.com/franklinselva"] + +# The title and description fields are primarly for displaying extension info in UI +description = "Rover Simple Controller for Space ROS" +title = "Rover Simple Controller" + +# Path (relative to the root) or content of readme markdown file for UI. +changelog = "docs/CHANGELOG.md" +preview_image = "data/node-preview.png" +readme = "docs/README.md" + +# URL of the extension source repository. +repository = "https://github.com/space-ros/simulation" + +# Categories for UI. +category = "Space ROS" + +# Keywords for the extension +keywords = ["ros2", "omnigraph", "space-ros"] + +# Watch the .ogn files for hot reloading (only works for Python files) +[fswatcher.patterns] +exclude = ["Ogn*Database.py"] +include = ["*.ogn", "*.py"] + +[dependencies] +"omni.graph" = {} +"omni.kit.test" = {} + +# Main python module this extension provides, it will be publicly available as "import omni.spaceros.roversimplecontroller". +[[python.module]] +name = "omni.spaceros.roversimplecontroller" diff --git a/extensions/omni.spaceros.roversimplecontroller/data/node-preview.png b/extensions/omni.spaceros.roversimplecontroller/data/node-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..ffa2ad680ebfe502201946b077e25fc19351d34a GIT binary patch literal 36323 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 H(1Xq&Mv^4V literal 0 HcmV?d00001 diff --git a/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__init__.py b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__pycache__/__init__.cpython-310.pyc b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65f291bd65f6badc65c380d07261bd431692af06 GIT binary patch literal 245 zcmZ{eF%H5o3`JAa0U>qc5)5rFfDk8O;Q&$8x)71rRgzY46E$k`gP6^m?h6+W%M_B_iyIgqW_c(4Wo*g?w%1{peC zL+L5J6NfSw*IOM8p@d@u44i@_ho%~7W-3nucm_KQHDUB*E%=f7+>w8n9ZIDPny6IO So$k}6qo%j8N*{*_TP|KdWl4Vk literal 0 HcmV?d00001 diff --git a/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.ogn b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.ogn new file mode 100644 index 0000000..2656932 --- /dev/null +++ b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.ogn @@ -0,0 +1,82 @@ +{ + "RoverSimpleController": { + "version": 1, + "description": "Receives Linear and Angular Velocity for rover and translates to wheel and steering outputs", + "language": "Python", + "metadata": { + "uiName": "Rover Simple Controller" + }, + "inputs": { + "chassis_length": { + "type": "float", + "description": "Chassis Length in meters (L)", + "default": 0, + "metadata": { + "uiName": "Chassis Length" + } + }, + "chassis_width": { + "type": "float", + "description": "Chassis Width in meters (T)", + "default": 0, + "metadata": { + "uiName": "Chassis Width" + } + }, + "linear_velocity": { + "type": "float[3]", + "description": "Linear Velocity Vector in m/s (x, y, z)", + "default": [ + 0.0, + 0.0, + 0.0 + ], + "metadata": { + "uiName": "Linear Velocity Vector" + } + }, + "angular_velocity": { + "type": "float[3]", + "description": "Angular Velocity Vector in rad/s (x, y, z)", + "default": [ + 0.0, + 0.0, + 0.0 + ], + "metadata": { + "uiName": "Angular Velocity Vector" + } + } + }, + "outputs": { + "wheel_velocity": { + "type": "double[]", + "description": "Wheel Velocity for six wheels of rover", + "metadata": { + "uiName": "Wheel Velocity" + }, + "default": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + }, + "steering_position": { + "type": "double[]", + "description": "Steering angle in radians", + "default": [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + "metadata": { + "uiName": "Steering Position" + } + } + } + } +} \ No newline at end of file diff --git a/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.py b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.py new file mode 100644 index 0000000..e66cea9 --- /dev/null +++ b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/RoverSimpleController.py @@ -0,0 +1,78 @@ +""" +This is the implementation of the OGN node defined in RoverSimpleController.ogn +""" + +# Array or tuple values are accessed as numpy arrays so you probably need this import +import math + + +def map_angular_to_steering(angular_speed) -> float: + """Map angular speed to steering angle.""" + if abs(angular_speed) < 1e-3: + return 0.0 + + # max 0.6 min -0.6 + angular_speed = min(0.6, max(angular_speed, -0.6)) + return (angular_speed / abs(angular_speed)) * (-25 * abs(angular_speed) + 17.5) + + +class RoverSimpleController: + """ + Receives Linear and Angular Velocity for rover and translates to wheel and steering + outputs + """ + + @staticmethod + def compute(db) -> bool: + """Compute the outputs from the current input""" + # Get the inputs + chassis_length = db.inputs.chassis_length + chassis_width = db.inputs.chassis_width + linear_velocity = db.inputs.linear_velocity + angular_velocity = db.inputs.angular_velocity + + # Compute wheel velocity + linear_velocity_x = linear_velocity[0] + wheel_velocity = [ + linear_velocity_x, + linear_velocity_x * 1.5, + linear_velocity_x, + -linear_velocity_x, + -linear_velocity_x * 1.5, + -linear_velocity_x, + ] + + # Compute steering angle + steer_position = [] + turn_rad = map_angular_to_steering(angular_velocity[2]) + + if abs(turn_rad) < 1e-3: + steer_position = [0.0, 0.0, 0.0, 0.0] + else: + turning_radius = abs(turn_rad) # R + + chassis_length = 2.08157 # L + chassis_width = 1.53774 # T + + alpha_i = math.atan(chassis_length / (turning_radius - (chassis_width / 2))) + alpha_o = math.atan(chassis_length / (turning_radius + (chassis_width / 2))) + + if alpha_i > 0.6: + alpha_i = 0.6 + + if alpha_o > 0.6: + alpha_o = 0.6 + + alpha_i = round(alpha_i, 2) + alpha_o = round(alpha_o, 2) + + if turn_rad > 0.0: + steer_position = [alpha_i, -alpha_i, alpha_o, -alpha_o] + else: + steer_position = [-alpha_o, alpha_o, -alpha_i, alpha_i] + + # Assign the outputs + db.outputs.wheel_velocity = wheel_velocity + db.outputs.steering_position = steer_position + + return True diff --git a/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/__pycache__/RoverSimpleController.cpython-310.pyc b/extensions/omni.spaceros.roversimplecontroller/omni/spaceros/roversimplecontroller/ogn/nodes/__pycache__/RoverSimpleController.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..64a8aca759f27c6f6e8be558e0615a67647ae03c GIT binary patch literal 1845 zcmZ`)&2QX96rUN7KVomTNeB%g1zCv8qBbjp#Hk{oIaEkAKwELJgp9K@>vdy$mKkr7 zMz(}V{sk_afD5V+XO8?2-0-!Bs7G#9g%sY*?uMoco*yKA#t-^xoDBFaDb$n$=9bC|@Kfit&yOlzD&SOK6lJz)tifhe>a2nx zRAF$DRbz8;PXB*w(z35>r21D^X8gEaE0S?M52F(CJWo0jrU|t#& zq-~h$9UvS5x==(Ijw}vX;x^#SV)T@r;K*S%l*egbztWZC!VhQTJ|FPxfUEFRfEAQ5 zDOvdOteju+Tnw`6aX1t*G%=g)3R0~gjqnnb`f$W~ZuIkD%-Ld6jVF~dyd>c6B@}i* z;lMN~Tmekh=n;B^8*En60RBL>8q2J-p|!>EI%@119!6G?UIU;3no0pmoyJK~<5qS} znY9j>ZoJB=J-{{vY=4*net*V|TYfUtHVe#YFmoGHxj%X}t`}-Z-v)XEZJ?iU<6qXU zO|?PoGjE^Dh7eXpmt2R+|