Viewport drag drop function

Hi,
I am trying to build an extension where I have multiple images and each of them are draggable and point to usd files. I want to be able to drag the image into the viewport and then based on the usd I want to execute some action in the scene, this could be attaching a material to a prim or adding a usd onto the scene with the right scale.

I have been unable to do this for quite some time and would love your help. I have created a test demo, so you can also test it on your end and help me fix it.

I tried:

  1. The ViewportWindow() does have a function called external_drag_drop_support() but it does not seem to work as intended. The code gets executed but it never enters the viewport drop function.

  2. Looking at how the omni kit browser material does the same task, by looking at its source code. They use something called create_drop_helper which was not at all clear since the docs for this are hard to find and complicated.

Can someone please help me make more progress on this. Here is the test code for the extension that I created:

import omni.ext
import omni.ui as ui
from omni.kit.viewport.utility import get_active_viewport_window
import omni.kit.commands
from pxr import Sdf, Usd
from pathlib import Path
from omni.ui import color as cl
import os
from pxr import UsdShade
from os.path import dirname
import sys



class TestViewportDrop(omni.ext.IExt):

    def drag_data(self):
        print(f"This function should pass some data to the viewport_drop function")
        print(f"Lets return a test string")
        url = "USD_DIR/wallpaper1.usd"
        return url
        # return 42
        # return "Hello World"

    def viewport_drop(self, event_type, drag_data):
        """
        This function should be called when the user drops a file into the viewport.
        It should have the mouse event type and the dragged data (url of the file) as arguments.
        """
        print(f"Viewport drop event: {event_type}")
        print(f"Drag data: {drag_data}")
        print(f"Viewport drop event: Succesful")
        return None


    def on_startup(self, ext_id):
        print("[kaari.extensions.tutorial] TestViewportDrop startup")
        self._viewport_window = get_active_viewport_window()
        self._viewport_window.add_external_drag_drop_support(self.viewport_drop)

        # Create a window with a draggable label
        self._window = ui.Window("TestViewportDrop", width=300, height=200)
        self._frame = ui.Frame(width=20, height=50, style={"border": "1px solid blue"})
        with self._window.frame:
            # with self._frame:
            #     self._label = ui.Button("TestViewportDrop Text", style={"color": "blue"})
            # self._frame.set_drag_fn(lambda: self.drag_data("/Environment/Looks/Grid"))
            with ui.VStack():
                ui.Spacer(height=10)
                self._frame = ui.Frame(width=120, height=50, style={"border": "5px solid blue"})
                with self._frame:
                    ui.Label("TestViewportDrop", style={"color": "blue", \
                                                        "background_color": cl("#DDDD00"), \
                                                        "debug_color": cl("#FF000022")})
        self._frame.set_drag_fn(lambda: self.drag_data())


    def on_shutdown(self):
        print("[kaari.extensions.tutorial] TestViewportDrop shutdown")
        if self._window:
            self._window.destroy()
            self._window = None

testextension.zip (1.0 KB)

This is quite a complex help request. I can try to see if someone can help, but it may take a while.

Yes that would be very helpful. Thank you so much @Richard3D.

To be clear, I only want help on the viewport drop function, and not how to deal with usds in the scene.
I looked at the Widget drop function, as shown in this image, and I am sure there must be a way to do something similar on the viewport too. I think it can be a big help to developers trying to build on top of omniverse and really customize the viewport to their liking.

This function would ideally look like the one shown in the code I shared where it takes as input the event_type and drag_data and can perform some actions on it.

Hoping to hear from you soon.

I got this reply from the dev team !

“The script below demonstrates two types of drag and drop into viewport (internal and external, according to whether the file is in omniverse app or not) I’ve created a stringfield to show the path of the image”

import omni.ui as ui

def _on_accept_drop(url):
    # only accept .png files
    if ".png" in url:
        return True
    else:
        return False

def _on_value(field):
    field.model.get_value_as_string()

def _on_drag(image_path):
    ui.Image(image_path, height=50, width=50)
    return image_path


window = ui.Window(title="Drag and Drop", width= 500, height=500)

with window.frame:
    with ui.VStack():
        image_path = "/img/path/im.png"
        image = ui.Image(image_path, height=100, width=100, drag_fn=lambda: _on_drag(image_path))

        path_widget = path_widget = ui.StringField(height=30)
        path_widget.model.add_value_changed_fn(lambda _: _on_value(path_widget))

        drop_area = ui.Workspace.get_window("Viewport").frame
        drop_area.set_accept_drop_fn(lambda url: _on_accept_drop(url))
        
        def _on_drop(e: ui.WidgetMouseDropEvent):
            path = e.mime_data
            path_widget.model.set_value(path)
        
        drop_area.set_drop_fn(_on_drop)  # internal drop


external_drag_drop = None
def _on_ext_drag_drop(e, payload, drop_area, path_widget, ):
    path = payload[0]
    path_widget.model.set_value(path)

def setup_external_drag_drop(window_name :str, drop_area, path_widget):
    global external_drag_drop
    if external_drag_drop:
        external_drag_drop.destroy()
    try:
        from omni.kit.window.drop_support import ExternalDragDrop
        external_drag_drop = ExternalDragDrop(window_name=window_name,
            drag_drop_fn=lambda e, p, drop_area=drop_area, path_widget=path_widget: _on_ext_drag_drop(e, p, drop_area, path_widget))
    except ImportError:
        pass

# if the image lives outside of the omniverse app, e.g filr explorer
setup_external_drag_drop("Viewport", drop_area, path_widget) # external drop

Thank you so much for the help and the detailed response with a video @Richard3D
This works like a charm.

The documentation on this is quite unclear though. When I tried finding these docs myself. These are the things you get:
get_active_viewport_window() → Get the Active Viewport Window — Omniverse Developer Guide latest documentation (nvidia.com)

From this you can get:

But neither of them actually points to the set_accept_drop_fn function that we have used above.
To add to that the ViewportWindow is a different object then the window you get from ui.Workspace.get_window(“Viewport”)

This is quite confusing as a developer not on the NVIDIA team. I would highly recommend the dev team to consolidate the documentations and tutorials better so these things are clearer and not legacy information withing the dev teams. Please let me know if I am missing out on something or looking at incorrect documentations.

Thank you

Thanks for the valuable feedback.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.