-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* first pass * remove unnecessary dep * document component
- Loading branch information
1 parent
303f472
commit daf8d15
Showing
8 changed files
with
521 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Byte-compiled / optimized / DLL files | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,160 @@ | ||
# omni-component | ||
# HEAVY.AI | Omni.Ui Component System | ||
|
||
This component system was first presented during a NVIDIA Partner Spotlight with HEAVY.AI and can be found [here on YouTube](https://youtu.be/QhBMgx2G86g?t=1640) | ||
|
||
![Slide presented during NVIDIA Omniverse Partner Spotlight](./exts/heavyai.ui.component/resources/partner-spotlight-slide.png) | ||
|
||
## Goals | ||
|
||
1. Provide a means to encapsulate functions and `omni.ui` widgets to modularize UI code by its purpose to enforce boundaries using separation of concerns | ||
2. Standardize the creation and implementation of UI components in a manner that enforces self-documentation and a homogenous UI codebase | ||
3. Provide the utility of updating and re-rendering specific components without re-rendering components that have not changed | ||
|
||
|
||
|
||
## Examples | ||
|
||
### Hello World | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class SimpleLabel(Component): | ||
def render(self): | ||
ui.Label("Hello World") | ||
|
||
# USAGE | ||
SimpleLabel() | ||
|
||
class CustomLabel(Component): | ||
value: str = "Hello World" | ||
|
||
def render(self): | ||
ui.Label(self.value) | ||
|
||
# USAGE | ||
CustomLabel(value="Hello There") | ||
``` | ||
|
||
### Internal State | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class ButtonCounter(Component): | ||
value: int = 0 | ||
on_change: Optional[Callable[[int], None]] | ||
|
||
def _handle_click(self): | ||
self.value += 1 | ||
if self.on_change: | ||
self.on_change(self.value) | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.HStack): | ||
ui.Label(str(self.value)) | ||
ui.Button("Increment", clicked_fn=self._handle_click() | ||
|
||
# USAGE | ||
ButtonCounter(on_change=lambda val: print(val)) | ||
``` | ||
|
||
### Subscribing Components | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class UsdSubscribingComponent(Component): | ||
prim: pxr.Usd.Prim | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
self.subscription: carb.Subscription = ( | ||
usd.get_watcher().subscribe_to_change_info_path( | ||
path=self.prim.GetPath(), on_change=self._handle_prim_change | ||
) | ||
) | ||
|
||
def _handle_prim_change(self, prim_attr_path): | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.VStack): | ||
pass | ||
|
||
def destroy(self): | ||
self.subscription.unsubscribe() | ||
|
||
|
||
class AbstractModelSubscribingComponent(Component): | ||
model: sc.AbstractManipulatorModel | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
self.subscription: carb.Subscription = ( | ||
self.model.subscribe_item_changed_fn( | ||
self._handle_model_item_changed | ||
) | ||
) | ||
|
||
def _handle_model_item_changed(self, model, item): | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.VStack): | ||
pass | ||
|
||
def destroy(self): | ||
self.subscription.unsubscribe() | ||
``` | ||
|
||
## Re-rendering “In Place” | ||
|
||
Similar to the ability to update the `style` property of any `ui.Widget` element or the `text` property of `ui.Label` and `ui.Button` elements, `Components` can also be updated after they are created. Components can update themselves in response to subscribed events or user interactions, or components can be updated elsewhere via reference. This *drastically* increases the composability of UI elements. | ||
|
||
### Using Setters | ||
|
||
```python | ||
class CustomLabel(Component): | ||
value: str = "Hello World" | ||
|
||
def set_value(self, value: str): | ||
self.value = value | ||
self.update() | ||
|
||
def render(self): | ||
ui.Label(self.value) | ||
|
||
hello_world_label = CustomLabel() | ||
hello_world_label.set_value( | ||
"Goodbye, and see you later" | ||
) | ||
``` | ||
|
||
### Using a Reference | ||
|
||
```python | ||
hello_world_label = CustomLabel() | ||
|
||
hello_world_label.value = "Goodbye, and see you later" | ||
hello_world_label.update() | ||
``` | ||
|
||
## Caveat: Destroying a Component | ||
|
||
Components are not automatically deleted when cleared from a container (or should otherwise be destroyed), so if a `destroy()` method is provided, a reference to that component must be stored and destroyed by the component’s “parent” when appropriate, resulting in a chain of saved references and destroy calls. This is often the case when a component sets up a subscription; the subscription must be unsubscribed when the component is removed. See 'Subscribing Components' above for examples of components that require a `destroy` method. | ||
|
||
```python | ||
class ParentComponent(Component): | ||
def render(self): | ||
prim = # get prim reference | ||
|
||
with self.get_root(ui.VStack): | ||
self.child = UsdSubscribingComponent(prim=prim) | ||
|
||
def destroy(self): | ||
self.child.destroy() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
[package] | ||
# Semantic Versionning is used: https://semver.org/ | ||
version = "1.0.0" | ||
|
||
# Lists people or organizations that are considered the "authors" of the package. | ||
authors = ["HEAVY.AI", "Brendan Turner", "Chris Matzenbach"] | ||
|
||
# The title and description fields are primarily for displaying extension info in UI | ||
title = "Heavy Omni.UI Component System" | ||
description="Component class for UI organization and encapsulation" | ||
|
||
# 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://github.com/heavyai/omni-component" | ||
|
||
# One of categories for UI. | ||
category = "Rendering" | ||
|
||
# Keywords for the extension | ||
keywords = ["heavyai", "component", "react"] | ||
|
||
# Preview image is shown in "Overview" of Extensions window. Screenshot of an extension might be a good preview image. | ||
preview_image = "resources/partner-spotlight-slide.png" | ||
|
||
# Icon is shown in Extensions window, it is recommended to be square, of size 256x256. | ||
icon = "resources/logo_black.png" | ||
|
||
# Use omni.ui to build simple UI | ||
[dependencies] | ||
"omni.ui" = {} | ||
|
||
# Main python module this extension provides, it will be publicly available as "import omni.hello.world". | ||
[[python.module]] | ||
name = "heavyai.ui.component" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
# HEAVY.AI | Omni.Ui Component System | ||
|
||
This component system was first presented during a NVIDIA Partner Spotlight with HEAVY.AI and can be found [here on YouTube](https://youtu.be/QhBMgx2G86g?t=1640) | ||
|
||
![Slide presented during NVIDIA Omniverse Partner Spotlight](../resources/partner-spotlight-slide.png) | ||
|
||
## Goals | ||
|
||
1. Provide a means to encapsulate functions and `omni.ui` widgets to modularize UI code by its purpose to enforce boundaries using separation of concerns | ||
2. Standardize the creation and implementation of UI components in a manner that enforces self-documentation and a homogenous UI codebase | ||
3. Provide the utility of updating and re-rendering specific components without re-rendering components that have not changed | ||
|
||
|
||
|
||
## Examples | ||
|
||
### Hello World | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class SimpleLabel(Component): | ||
def render(self): | ||
ui.Label("Hello World") | ||
|
||
# USAGE | ||
SimpleLabel() | ||
|
||
class CustomLabel(Component): | ||
value: str = "Hello World" | ||
|
||
def render(self): | ||
ui.Label(self.value) | ||
|
||
# USAGE | ||
CustomLabel(value="Hello There") | ||
``` | ||
|
||
### Internal State | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class ButtonCounter(Component): | ||
value: int = 0 | ||
on_change: Optional[Callable[[int], None]] | ||
|
||
def _handle_click(self): | ||
self.value += 1 | ||
if self.on_change: | ||
self.on_change(self.value) | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.HStack): | ||
ui.Label(str(self.value)) | ||
ui.Button("Increment", clicked_fn=self._handle_click() | ||
|
||
# USAGE | ||
ButtonCounter(on_change=lambda val: print(val)) | ||
``` | ||
|
||
### Subscribing Components | ||
|
||
```python | ||
from heavyai.ui.component import Component | ||
|
||
class UsdSubscribingComponent(Component): | ||
prim: pxr.Usd.Prim | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
self.subscription: carb.Subscription = ( | ||
usd.get_watcher().subscribe_to_change_info_path( | ||
path=self.prim.GetPath(), on_change=self._handle_prim_change | ||
) | ||
) | ||
|
||
def _handle_prim_change(self, prim_attr_path): | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.VStack): | ||
pass | ||
|
||
def destroy(self): | ||
self.subscription.unsubscribe() | ||
|
||
|
||
class AbstractModelSubscribingComponent(Component): | ||
model: sc.AbstractManipulatorModel | ||
|
||
def __init__(self, **kwargs): | ||
super().__init__(**kwargs) | ||
|
||
self.subscription: carb.Subscription = ( | ||
self.model.subscribe_item_changed_fn( | ||
self._handle_model_item_changed | ||
) | ||
) | ||
|
||
def _handle_model_item_changed(self, model, item): | ||
self.update() | ||
|
||
def render(self): | ||
with self.get_root(ui.VStack): | ||
pass | ||
|
||
def destroy(self): | ||
self.subscription.unsubscribe() | ||
``` | ||
|
||
## Re-rendering “In Place” | ||
|
||
Similar to the ability to update the `style` property of any `ui.Widget` element or the `text` property of `ui.Label` and `ui.Button` elements, `Components` can also be updated after they are created. Components can update themselves in response to subscribed events or user interactions, or components can be updated elsewhere via reference. This *drastically* increases the composability of UI elements. | ||
|
||
### Using Setters | ||
|
||
```python | ||
class CustomLabel(Component): | ||
value: str = "Hello World" | ||
|
||
def set_value(self, value: str): | ||
self.value = value | ||
self.update() | ||
|
||
def render(self): | ||
ui.Label(self.value) | ||
|
||
hello_world_label = CustomLabel() | ||
hello_world_label.set_value( | ||
"Goodbye, and see you later" | ||
) | ||
``` | ||
|
||
### Using a Reference | ||
|
||
```python | ||
hello_world_label = CustomLabel() | ||
|
||
hello_world_label.value = "Goodbye, and see you later" | ||
hello_world_label.update() | ||
``` | ||
|
||
## Caveat: Destroying a Component | ||
|
||
Components are not automatically deleted when cleared from a container (or should otherwise be destroyed), so if a `destroy()` method is provided, a reference to that component must be stored and destroyed by the component’s “parent” when appropriate, resulting in a chain of saved references and destroy calls. This is often the case when a component sets up a subscription; the subscription must be unsubscribed when the component is removed. See 'Subscribing Components' above for examples of components that require a `destroy` method. | ||
|
||
```python | ||
class ParentComponent(Component): | ||
def render(self): | ||
prim = # get prim reference | ||
|
||
with self.get_root(ui.VStack): | ||
self.child = UsdSubscribingComponent( | ||
prim=prim | ||
) | ||
|
||
def destroy(self): | ||
self.child.destroy() | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .extension import * |
Oops, something went wrong.