-
Notifications
You must be signed in to change notification settings - Fork 0
Release Candidate 1 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rc1
Are you sure you want to change the base?
Changes from 16 commits
d4f92cf
b4aeccc
9e6a655
7ccc8b5
895fdad
916771c
6f43c0f
2269514
af20e32
a5da970
4083ab1
1fd4c53
7d6a03a
8d15bf6
63ce80b
214ecc6
3f4ce52
3a964ee
dbca51a
1056f15
e948bb0
4299b3f
94f584e
9c3929a
1ec4eab
2fcc7e9
a6623a4
d525710
55ad2da
53fe69c
65e9b0e
365e00b
8e39865
0999edc
2a94f44
7447284
958fdb3
ab65d60
c76d647
130e03d
21f6f66
5b450bb
611ff97
c055b44
9d23a3a
c9d5ee1
bc98ff3
78832b2
602a08a
d84386a
43a4f24
4f29128
11d6979
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# Beckhoff Bridge Extension for IsaacSim | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Python: Attach", | ||
"type": "python", | ||
"request": "attach", | ||
"port": 3000, | ||
"host": "127.0.0.1", | ||
"justMyCode": false | ||
}, | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
[core] | ||
reloadable = true | ||
order = 0 | ||
|
||
[package] | ||
version = "0.1.0" | ||
category = "Connector" | ||
title = "Beckhoff Bridge" | ||
description = "A client for connecting to Beckhoff PLCs" | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
authors = ["Loupe"] | ||
repository = "https://github.com/loupeteam/IsaacSim_Beckhoff_Bridge_Extension" | ||
keywords = [] | ||
changelog = "docs/CHANGELOG.md" | ||
readme = "docs/README.md" | ||
preview_image = "data/preview.png" | ||
icon = "data/icon.png" | ||
|
||
|
||
[dependencies] | ||
"omni.kit.uiapp" = {} | ||
"omni.isaac.ui" = {} | ||
"omni.isaac.core" = {} | ||
|
||
[python.pipapi] | ||
requirements = ['pyads'] | ||
use_online_index = true | ||
|
||
[[python.module]] | ||
name = "loupe.beckhoff_bridge" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import carb.events | ||
shanereetz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
import omni.kit.app | ||
|
||
EXTENSION_EVENT_SENDER_ID = 500 | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
EVENT_TYPE_DATA_INIT = carb.events.type_from_string("loupe.beckhoff_bridge.DATA_INIT") | ||
EVENT_TYPE_DATA_READ = carb.events.type_from_string("loupe.beckhoff_bridge.DATA_READ") | ||
EVENT_TYPE_DATA_READ_REQ = carb.events.type_from_string("loupe.beckhoff_bridge.DATA_READ_REQ") | ||
EVENT_TYPE_DATA_WRITE_REQ = carb.events.type_from_string("loupe.beckhoff_bridge.DATA_WRITE_REQ") | ||
|
||
class BeckhoffBridge: | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
def __init__( self ): | ||
self._event_stream = omni.kit.app.get_app().get_message_bus_event_stream() | ||
self._callbacks = [] | ||
|
||
def __del__(self): | ||
for callback in self._callbacks: | ||
self._event_stream.remove_subscription(callback) | ||
|
||
def register_init_callback(self, callback): | ||
self._callbacks.append(self._event_stream.create_subscription_to_push_by_type(EVENT_TYPE_DATA_INIT, callback)) | ||
callback( None ) | ||
|
||
def register_data_callback(self, callback): | ||
self._callbacks.append(self._event_stream.create_subscription_to_push_by_type(EVENT_TYPE_DATA_READ, callback)) | ||
|
||
def add_cyclic_read_variables(self, variableList): | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
self._event_stream.push(event_type=EVENT_TYPE_DATA_READ_REQ, payload={'variables':variableList}) | ||
|
||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
def write_variable(self, name, value): | ||
payload = {"variables":[{'name': name, 'value': value}]} | ||
self._event_stream.push(event_type=EVENT_TYPE_DATA_WRITE_REQ, payload=payload) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .extension import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import pyads | ||
|
||
|
||
class AdsDriver(): | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
def __init__(self, ams_net_id): | ||
|
||
self.ams_net_id = ams_net_id | ||
self._read_names = list() | ||
self._read_struct_def = dict() | ||
|
||
def add_read(self, name : str, structure_def = None): | ||
|
||
if(name not in self._read_names): | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
self._read_names.append(name) | ||
|
||
if structure_def is not None: | ||
if name not in self._read_struct_def: | ||
self._read_struct_def[name] = structure_def | ||
|
||
def write_data(self, data : dict ): | ||
self._connection.write_list_by_name( data ) | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
def read_data(self): | ||
# self._connection. | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
if self._read_names is not None: | ||
data = self._connection.read_list_by_name( self._read_names, structure_defs=self._read_struct_def) | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
parsed_data = dict() | ||
for name in data.keys(): | ||
parsed_data = self._parse_name(parsed_data, name, data[name]) | ||
else: | ||
parsed_data = dict() | ||
return parsed_data | ||
|
||
def _parse_name(self, name_dict, name, value): | ||
Joshpolansky marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
name_parts = name.split(".") | ||
if len(name_parts) > 1: | ||
if name_parts[0] not in name_dict: | ||
name_dict[name_parts[0]] = dict() | ||
if "[" in name_parts[1]: | ||
array_name, index = name_parts[1].split("[") | ||
index = int(index[:-1]) | ||
if array_name not in name_dict[name_parts[0]]: | ||
name_dict[name_parts[0]][array_name] = [] | ||
if index >= len(name_dict[name_parts[0]][array_name]): | ||
name_dict[name_parts[0]][array_name].extend([None] * (index - len(name_dict[name_parts[0]][array_name]) + 1)) | ||
name_dict[name_parts[0]][array_name][index] = self._parse_name(name_dict[name_parts[0]][array_name], "[" + str(index) + "]" + ".".join(name_parts[2:]), value) | ||
else: | ||
name_dict[name_parts[0]] = self._parse_name(name_dict[name_parts[0]], ".".join(name_parts[1:]), value) | ||
else: | ||
if "[" in name_parts[0]: | ||
array_name, index = name_parts[0].split("[") | ||
index = int(index[:-1]) | ||
if index >= len(name_dict): | ||
name_dict.extend([None] * (index - len(name_dict) + 1)) | ||
name_dict[index] = value | ||
return name_dict[index] | ||
else: | ||
name_dict[name_parts[0]] = value | ||
return name_dict | ||
|
||
def connect(self): | ||
self._connection = pyads.Connection(self.ams_net_id, pyads.PORT_TC3PLC1) | ||
self._connection.open() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
# This software contains source code provided by NVIDIA Corporation. | ||
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. | ||
# | ||
# NVIDIA CORPORATION and its licensors retain all intellectual property | ||
# and proprietary rights in and to this software, related documentation | ||
# and any modifications thereto. Any use, reproduction, disclosure or | ||
# distribution of this software and related documentation without an express | ||
# license agreement from NVIDIA CORPORATION is strictly prohibited. | ||
# | ||
|
||
import weakref | ||
import asyncio | ||
import gc | ||
import omni | ||
import omni.ui as ui | ||
import omni.usd | ||
import omni.timeline | ||
import omni.kit.commands | ||
from omni.kit.menu.utils import add_menu_items, remove_menu_items | ||
from omni.isaac.ui.menu import make_menu_item_description | ||
from omni.usd import StageEventType | ||
import omni.physx as _physx | ||
|
||
from .global_variables import EXTENSION_TITLE, EXTENSION_DESCRIPTION | ||
from .ui_builder import UIBuilder | ||
|
||
""" | ||
This file serves as a basic template for the standard boilerplate operations | ||
that make a UI-based extension appear on the toolbar. | ||
|
||
This implementation is meant to cover most use-cases without modification. | ||
Various callbacks are hooked up to a seperate class UIBuilder in .ui_builder.py | ||
Most users will be able to make their desired UI extension by interacting solely with | ||
UIBuilder. | ||
|
||
This class sets up standard useful callback functions in UIBuilder: | ||
on_menu_callback: Called when extension is opened | ||
on_timeline_event: Called when timeline is stopped, paused, or played | ||
on_stage_event: Called when stage is opened or closed | ||
cleanup: Called when resources such as physics subscriptions should be cleaned up | ||
build_ui: User function that creates the UI they want. | ||
""" | ||
|
||
|
||
class TestExtension(omni.ext.IExt): | ||
def on_startup(self, ext_id: str): | ||
"""Initialize extension and UI elements""" | ||
|
||
# Events | ||
self._usd_context = omni.usd.get_context() | ||
|
||
# Build Window | ||
self._window = ui.Window( | ||
title=EXTENSION_TITLE, width=600, height=500, visible=False, dockPreference=ui.DockPreference.LEFT_BOTTOM | ||
) | ||
self._window.set_visibility_changed_fn(self._on_window) | ||
|
||
# UI | ||
self._models = {} | ||
self._ext_id = ext_id | ||
self._menu_items = [ | ||
make_menu_item_description(ext_id, EXTENSION_TITLE, lambda a=weakref.proxy(self): a._menu_callback()) | ||
] | ||
|
||
add_menu_items(self._menu_items, EXTENSION_TITLE) | ||
|
||
# Filled in with User Functions | ||
self.ui_builder = UIBuilder() | ||
|
||
# Events | ||
self._usd_context = omni.usd.get_context() | ||
self._physxIFace = _physx.acquire_physx_interface() | ||
self._physx_subscription = None | ||
self._stage_event_sub = None | ||
self._timeline = omni.timeline.get_timeline_interface() | ||
|
||
def on_shutdown(self): | ||
self._models = {} | ||
remove_menu_items(self._menu_items, EXTENSION_TITLE) | ||
if self._window: | ||
self._window = None | ||
self.ui_builder.cleanup() | ||
gc.collect() | ||
|
||
def _on_window(self, visible): | ||
if self._window.visible: | ||
# Subscribe to Stage and Timeline Events | ||
self._usd_context = omni.usd.get_context() | ||
events = self._usd_context.get_stage_event_stream() | ||
self._stage_event_sub = events.create_subscription_to_pop(self._on_stage_event) | ||
stream = self._timeline.get_timeline_event_stream() | ||
self._timeline_event_sub = stream.create_subscription_to_pop(self._on_timeline_event) | ||
|
||
self._build_ui() | ||
else: | ||
self._usd_context = None | ||
self._stage_event_sub = None | ||
self._timeline_event_sub = None | ||
|
||
def _build_ui(self): | ||
with self._window.frame: | ||
with ui.VStack(spacing=5, height=0): | ||
self._build_extension_ui() | ||
|
||
async def dock_window(): | ||
await omni.kit.app.get_app().next_update_async() | ||
|
||
def dock(space, name, location, pos=0.5): | ||
window = omni.ui.Workspace.get_window(name) | ||
if window and space: | ||
window.dock_in(space, location, pos) | ||
return window | ||
|
||
tgt = ui.Workspace.get_window("Viewport") | ||
dock(tgt, EXTENSION_TITLE, omni.ui.DockPosition.LEFT, 0.33) | ||
await omni.kit.app.get_app().next_update_async() | ||
|
||
self._task = asyncio.ensure_future(dock_window()) | ||
|
||
################################################################# | ||
# Functions below this point call user functions | ||
################################################################# | ||
|
||
def _menu_callback(self): | ||
self._window.visible = not self._window.visible | ||
self.ui_builder.on_menu_callback() | ||
|
||
def _on_timeline_event(self, event): | ||
self.ui_builder.on_timeline_event(event) | ||
|
||
def _on_stage_event(self, event): | ||
if event.type == int(StageEventType.OPENED) or event.type == int(StageEventType.CLOSED): | ||
# stage was opened or closed, cleanup | ||
self._physx_subscription = None | ||
|
||
self.ui_builder.on_stage_event(event) | ||
|
||
def _build_extension_ui(self): | ||
# Call user function for building UI | ||
self.ui_builder.build_ui() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# This software contains source code provided by NVIDIA Corporation. | ||
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved. | ||
# | ||
# NVIDIA CORPORATION and its licensors retain all intellectual property | ||
# and proprietary rights in and to this software, related documentation | ||
# and any modifications thereto. Any use, reproduction, disclosure or | ||
# distribution of this software and related documentation without an express | ||
# license agreement from NVIDIA CORPORATION is strictly prohibited. | ||
# | ||
|
||
|
||
EXTENSION_TITLE = "beckhoff_bridge" | ||
EXTENSION_NAME = "loupe.beckhoff_bridge" | ||
EXTENSION_DESCRIPTION = "Connector for Beckhoff PLCs" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I propose we either remove this file, or include a link inside it to the actual README.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I don't think it should be removed. This readme would be for the developers of the extension, where the inner one is for the users and gets packaged with the extension. This readme should probably have instructions for development.