Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - Michael McCartney 0

Pages: [1]
1
Short of spawning a subprocess and using IPC, you don't have many, if any, options. The Qt gui loop and the sd functions need the main thread, if one takes a long running task, that's that.

One way to accomplish the IPC, is to use the QLocalServer and QLocalSocket that come with QtCore. The following was scratched out here and by no means will work out of the box, but it might get you started

Code: [Select]
# your sd plugin
import subprocess
from PySide2 import QtCore, QtWidgets

class MyServer(QtCore.QObject):

    connected = QtCore.Signal()

    def __init__(self):
        self._sockets = []
        self.wants_to_quit = False
        self._server = QtCore.QLocalServer(QtWidgets.QApplication.instance())
        self._server.listen("my-pipe") # Named pipe to communicate, might need to make unique if multiple servers
        self._server.newConnection.connect(self._new_connection)

    def _new_connection(self, socket):
        self._sockets.append(socket)
        socket.readyRead.connect(self._read_from_socket)
        self.connected.emit()

    def _read_from_socket(self, socket):
        message = socket.readAll().decode('utf-8')
        if message = 'CANCEL':
            self.wants_to_quit = True

    def write_to_sockets(self, message):
        for s in self._sockets:
            s.write(message)
            s.flush()

SOCKET_SERVER = MyServer()

# -- In the plugin, for some long running task
def long_running_task(self):

    # Use an event loop to wait while the process starts before running the task
    timer = QtCore.QTimer()
    timer.setInterval(3000) # 3 second timeout
    loop = QtCore.QEventLoop()
    SOCKET_SERVER.connected.connect(loop.quit)
    timer.timeout.connect(loop.quit)
    loop.quit.connect(timer.stop)

    # Finally, start the rocess
    subprocess.Popen(["python", "/path/to/progress/script.py", "my-pipe"])
    loop.exec_() # Run the event loop until connected

    # -- start work, periodically send a message of progress
    SOCKET_SERVER.write_to_sockets("progress:10")
    if SOCKET_SERVER.wants_to_quit:
        SOCKET_SERVER.deleteLater() # Deleting the qobject should clean up connections and sockets
        return


Then, in the client, you open a socket connection to that main process

Code: [Select]
# /path/to/progress/script.py
import sys
from PySide import QtCore, QtWidgets

app = QtWidgets.QApplication(sys.argv)
PIPE_NAME = sys.argv[-1]

class MyProgress(QtWidgets.QDialog):
    # ... Layout

    def process_message(self, message):
        message = message.decode('utf-8') # Comes in as bytes
        if 'progress' in message:
            self._progress = int(message.replace('progress:', '')

    def on_cancel_clicked(self):
        self.socket.write("CANCEL") # Send back to the server that we want to cancel
        self.socket.flush()
        self.reject() # Clean up this dialog, let the process die and clean up the environment

my_progress = MyProgress()
socket = QtCore.QLocalSocket(app)
my_progress.socket = socket

socket.connectToServer(PIPE_NAME)
if (socket.waitForConnected(1000)):
    # We're connected, we can receive information from the server now
    socket.readyRead.connect(my_process.process_message)

my_process.exec_() # Show out dialog

2
So, while it's not perfect, I was able to divine that a node can be set to the 3D View through the node "usages" - as long as you know the material the 3D View defaults to and can grep to the correct outputs.

Code: [Select]
# Build output
node = graph.newNode('sbs::compositing::output')

# Find the usage property
usage_prop = None
for p in node.getProperties(SDPropertyCategory.Annotation):
    if p.getLabel() == 'Usages':
        usage_prop = p
        break
else:
    print ("-- No property --")

# Create a new usage for this node
usage = sd.api.sdusage.SDUsage.sNew(
    'baseColor',         # !!! This is the real magic. Change to be metallic, specular, etc.
    'RGBA',
    '' # colorspace... TODO?
)
# Wrap in a usage
value_usage = sd.api.sdvalueusage.SDValueUsage.sNew(usage)

# Find the array of usages, append to it and set the prop value again
usage_array = node.getPropertyValue(usage_prop)
usage_array.pushBack(value_usage)
node.setPropertyValue(usage_prop, usage_array)

This accomplished, for all intents and purposes, what we're after for a interchange -> designer workflow. Hope this may one day help someone.

The python API, while better than some applications, is a bit non-pythonic. It could probably use a beauty pass. One important use would be to add a proxy layer to node property management. Something like:

Code: [Select]
# Generate a new node in graph 'graph'
uni = Node("uniform", graph)
output = Node("output", graph)

# Hook Up, use SDNode.outputs and SDNode.inputs as a property that wraps
# an underlying call to getting the properties and allowing for a simple connection
uni.outputs.unique_filter_output.connect(output.inputs.inputNodeOutput)

This is a node-based application after all. You don't have to have that functionality on the C++ or shim layer, just at the top of the python and monkey-patch the various proxy objects/methods. If I find time, perhaps I'll tinker with it.

3
I'm interested in something similar - generating graphs from an interchange format. We have the node creation and linking done, we are just caught on setting up the 3D View.

Our use case is converting UE4 Template Materials into a serialized format (JSON, YAML, etc.) with the various textures required. Based on that structure, we generate output nodes (with reasonable defaults).

Some code:
Code: [Select]
layout = ShaderDescriptor('ue4/shader/descriptor.json')
app = sd.getContext().getSDApplication()

# Generate a fresh package
new_package = app.getPackageMgr().newUserPackage()
graph = sd.api.sbs.sdsbscompgraph.SDSBSCompGraph.sNew(new_package)

# Set up the layout with all the required parts
layout.initialize_graph(graph)

# With the new graph, and the layout, we generate
for output_descriptor in layout:
    output_node = graph.newNode('sbs::compositing::output')

    # !! Ideally, in here, we can set this node to the appropriate view based on the material
    layout.intialize_output(output_descriptor, output_node)

If this isn't possible currently, that would be excellent to know too.

Pages: [1]