Jump to content

Quick Python Question


few_a_fx

Recommended Posts

Create a render button for my fx tool. When you click it mplay pops up and it starts rendering, but nothing shows up. However if I go into the output tab and select the mantra node and click render on it, the scene renders out. Can anyone tell me what I'm doing wrong, or have any suggestions to fix this. Here is the code I have.

def OnRender(self, event):
            renderIt = hou.node('/out/RENDER_IT')
            renderIt.render()

Link to comment
Share on other sites

Your set up works, but for some reason it doesn't work for me. I create a tool in wxPython/Python, and I've added a render button to the UI, now I'm trying to get it to work. If I run the script in the python sheel it works just fine, works just like your example. Here's the set up for the tool:

def _add_flex_grid(self):
        self._add_static_text("Render Effect")
        self._render_button= self._render_button("Render")

def _render_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnRender)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button

def OnRender(self, event):
            renderIt = hou.node('/out/RENDER_IT')
            renderIt.render()

Hope this helps with understanding my proble,. I'll continue to work on it and let you know if I figure it out. Still if anyone has anymore suggestions or tips that would be awesome.

Link to comment
Share on other sites

It wouldn't fall under 3rd party rendering since that only refers to using other render engines such as RenderMan or Mental Ray.

As for what the problem is, it is probably not possible to diagnose without a scene file and the full scripting setup.

Link to comment
Share on other sites

As for what the problem is, it is probably not possible to diagnose without a scene file and the full scripting setup.

Here is the python script.


###############################################################################
########################### RAIN SYSTEM TOOL | RST ############################
########################### Created by: Jason Fugh ############################
###############################################################################

# This tool was created to be used in Side Effects Houdini.
# It was written in python & wxPython.
# This fx tool allows the user to create and control a basic rain/particle system

import sys, threading, wx
if __name__ != "__main__":
    import hou
    import toolutils 

def exc_log():
    print "Exception: '%s' on line %d" % (sys.exc_value, sys.exc_traceback.tb_lineno)

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFxTool(None, -1, 'Rain System Tool')
        frame.Show(True)
        self.SetTopWindow(frame)
        return True


# Creates the Window
class MyFxTool(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size (405, 505),
                          style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.STAY_ON_TOP)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        self.panel= MyFxToolPanel(self)
        self.sizer.Add(self.panel, 1, flag= wx.EXPAND)
        self.Layout()


# Creates the Layout
class MyFxToolPanel(wx.Panel):
    def __init__(self, parent):
        super(MyFxToolPanel, self).__init__(parent)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self._add_flex_grid()
        self.SetSizer(self.sizer)

    def _add_flex_grid(self):
        self.flex_grid_sizer= wx.FlexGridSizer(cols= 2, vgap= 10, hgap= 70)
        self.sizer.Add(self.flex_grid_sizer, 1, border= 20, flag= wx.ALL|wx.EXPAND)
        self._add_static_text("Rain System")
        self.start_button= self._add_button("Make It Rain")
        self._add_static_text("Render Effect")
        self._render_button= self._render_button("Render")


###########################################################################  
####################### PARAMETER CONTROLS ################################
###########################################################################

    def _add_static_text(self, label_text):
        static_text= wx.StaticText(self, label= label_text,
        style= wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.flex_grid_sizer.Add(static_text, flag= wx.ALIGN_CENTER_VERTICAL)

    def _add_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnClickAdd)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button

    def _render_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnRender)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button



###########################################################################   
####################### PARAMETER EVENTS ##################################
###########################################################################


    #Allow you to create the acual rain system.
    #create both SOP and PO nodes.
    def OnClickAdd(self, event):
        geo = hou.node("/obj").createNode("geo", "Rain_System", run_init_scripts = False)
        grid = geo.createNode("grid", "EMIT_FROM")
        grid.setParms({"ty":10})
        popnet = grid.createOutputNode("popnet", "RAIN_EMITTER")
        popnet.setDisplayFlag(1)
        popnet.setRenderFlag(1)
        source = popnet.createNode("source", "RAIN_ORIGIN")
        defaultGeo = source.parm("usecontextgeo")
        defaultGeo.set("first")
        birthPlace = source.parm("emittype")
        birthPlace.set("surfacerandom")
        gravity = source.createOutputNode("force", "RAIN_DirCtrl")
        defaultRainDir = gravity.parm("forcey")
        defaultRainDir.set(-1)
        rSpeed = gravity.createOutputNode("drag", "RAIN_Spd_Ctrl")
        defaultRainSpd = rSpeed.parm("scale")
        defaultRainSpd.set(2)
        render = rSpeed.createOutputNode("render", "RENDER_ME")
        renderAs = render.parm("prtype")
        renderAs.set("line")
        renderSize = render.parm("prsize")
        renderSize.set(.0025)
        render.setRenderFlag(1)
        collide = render.createOutputNode("collision", "COLLIDE_WITH")
        out = hou.node("/out")
        renderIt = out.createNode("ifd", "RENDER_IT")


    def OnRender(self, event):
        try:
            hou.node('/out/RENDER_IT').render()


        except:
               exc_log




class AppThread(threading.Thread):
    def run(self):
        app = MyApp(0)
        app.MainLoop()


if __name__ == "__main__":
    AppThread().start()

Save it as a .py file and place it in the houdini python folder. Then create a new tool and add this script to it.

import sys

try:
    del sys.modules["fx_rst"]
except:
    pass

import fx_rst
fx_rst.AppThread().start()

Once thats done you can click on the tool and a window will pop up, and you can start using the tool.

Link to comment
Share on other sites

Thanks for posting. For whatever reason that problem seems to be due to the threading. This is a bit weird because if works fine from the shell it should be fine in another thread. The shelf runs in the application thread, while the shell runs in a separate one.

Upon first glance I couldn't see anything wrong and wasn't sure what to do about it since threading issues with Houdini, if it was actually the issue, are beyond what I can deal with. Since there wasn't much I could do I figured I'd tweak it a bit in to use Houdini's event loop callback since that doesn't involve threading. After making a few adjustments I was able to get it running and rendering without issue.

To get it running and working good I basically got rid of all the threading stuff as well as the MyApp class. I copied/pasted the code from the cookbook example, added in a window.Show(True) call and then added the code to a function called rain(). From the shelf tool I just imported the module and called rain().

def runWxApp(app, window):
    def processWxEvents():
        """Houdini runs this callback to from its event loop to process wx
           events."""
        while event_loop.Pending():
            event_loop.Dispatch()
            app.ProcessPendingEvents()

    def onClose(event):
        """We bind this callback to the window's close event to destroy the
           window and stop the wx event loop."""
        window.Show(False)
        window.Destroy()
        processWxEvents()
        hou.ui.removeEventLoopCallback(processWxEvents)

    window.Bind(wx.EVT_CLOSE, onClose)

    window.Show(True)
    event_loop = wx.EventLoop()
    wx.EventLoop.SetActive(event_loop)
    hou.ui.addEventLoopCallback(processWxEvents)

def rain():
    app = wx.PySimpleApp()
    frame = MyFxTool(None, -1, 'Rain System Tool')
    runWxApp(app, frame)

Link to comment
Share on other sites

Thanks graham. One thing though, I didn't know if you need the entire script or just the render section. I also have several other controls (wxSliders, checkboxes and combo boxes). Is there a way to bring this into my current file with threading, but not have the threading effect what you have posted? (hope that makes since). I guess I should have posted the entire code.


###############################################################################
########################### RAIN SYSTEM TOOL | RST ############################
########################### Created by: Jason Fugh ############################
###############################################################################

# This tool was created to be used in Side Effects Houdini.
# It was written in python & wxPython.
# This fx tool allows the user to create and control a basic rain/particle system

import sys, threading, wx
if __name__ != "__main__":
    import hou
    import toolutils 

def exc_log():
    print "Exception: '%s' on line %d" % (sys.exc_value, sys.exc_traceback.tb_lineno)

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFxTool(None, -1, 'Rain System Tool')
        frame.Show(True)
        self.SetTopWindow(frame)
        return True


# Creates the Window
class MyFxTool(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size (405, 505),
                          style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.MINIMIZE_BOX | wx.STAY_ON_TOP)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        self.panel= MyFxToolPanel(self)
        self.sizer.Add(self.panel, 1, flag= wx.EXPAND)
        self.Layout()


# Creates the Layout
class MyFxToolPanel(wx.Panel):
    def __init__(self, parent):
        super(MyFxToolPanel, self).__init__(parent)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self._add_flex_grid()
        self.SetSizer(self.sizer)

    def _add_flex_grid(self):
        self.flex_grid_sizer= wx.FlexGridSizer(cols= 2, vgap= 10, hgap= 70)
        self.sizer.Add(self.flex_grid_sizer, 1, border= 20, flag= wx.ALL|wx.EXPAND)
        self._add_static_text("Rain System")
        self.start_button= self._add_button("Make It Rain")
        self._add_static_text("Amount of Rain")
        self.amount_slider= self._addAmount_slider()
        self._add_static_text("Rain Speed")
        self.r_speed_slider= self._addSpeed_slider()
        self._add_static_text("Wind Speed")
        self.w_speed_slider= self._addScale_slider()
        self._add_static_text("Life")
        self.life_slider= self._addLife_slider()
        self._add_static_text("Collisions")
        self._add_colisions()
        self._add_static_text("Bounce")
        self.bounce_slider= self._addBounce_slider()
        self._add_static_text("Render Effect")
        self._render_button= self._render_button("Render")


###########################################################################  
####################### PARAMETER CONTROLS ################################
###########################################################################

    def _add_static_text(self, label_text):
        static_text= wx.StaticText(self, label= label_text,
        style= wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.flex_grid_sizer.Add(static_text, flag= wx.ALIGN_CENTER_VERTICAL)

    def _add_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnClickAdd)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button

    def _addAmount_slider(self):
        slider_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(slider_sizer, flag= wx.ALIGN_CENTER_VERTICAL)
        self.slider = wx.Slider(self, value= 100, minValue= 1, maxValue= 500, style= wx.SL_LABELS)
        self.slider.Bind(wx.EVT_SLIDER, self.sliderUpdate1)
        slider_sizer.Add(self.slider, flag= wx.ALIGN_CENTER_VERTICAL)
        return self.slider

    def _addSpeed_slider(self):
        slider_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(slider_sizer, flag= wx.ALIGN_CENTER_VERTICAL)
        self.slider1= wx.Slider(self, value= 2, minValue= 0, maxValue= 5, style= wx.SL_LABELS)
        self.slider1.Bind(wx.EVT_SLIDER, self.sliderUpdate1)
        slider_sizer.Add(self.slider1, flag= wx.ALIGN_CENTER_VERTICAL)
        return self.slider1


    def _addScale_slider(self):
        slider_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(slider_sizer, flag= wx.ALIGN_CENTER_VERTICAL)
        self.slider2= wx.Slider(self, value= 1, minValue=0, maxValue= 10, style= wx.SL_LABELS)
        self.slider2.Bind(wx.EVT_SLIDER, self.sliderUpdate1)
        slider_sizer.Add(self.slider2, flag= wx.ALIGN_CENTER_VERTICAL)
        return self.slider2

    def _addLife_slider(self):
        slider_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(slider_sizer, flag= wx.ALIGN_CENTER_VERTICAL)
        self.slider3= wx.Slider(self, value= 100, minValue= 0, maxValue= 100, style= wx.SL_LABELS)
        self.slider3.Bind(wx.EVT_SLIDER, self.sliderUpdate1)
        slider_sizer.Add(self.slider3, flag= wx.ALIGN_CENTER_VERTICAL)
        return self.slider3

    def _add_colisions(self):
        colisions_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(colisions_sizer, flag= wx.ALIGN_CENTER_VERTICAL)
        self.collide_on= wx.CheckBox(self, label= "On/Off")
        colisions_sizer.Add(self.collide_on, 0, flag= wx.ALIGN_CENTER_VERTICAL)
        self.collide_on.Bind(wx.EVT_CHECKBOX, self.CheckOn)
        self.collide_button= wx.Button(self, label= "Collide With")
        colisions_sizer.Add(self.collide_button, 0, border= 10,
            flag= wx.LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.collide_button.Bind(wx.EVT_BUTTON, self.AddCollision)
        options = ['Die', 'Bounce', 'Stop', 'Stick', 'Slide','Continue']
        self.collide_op = wx.ComboBox(self, value= "", choices = options, id=5)
        self.collide_op.Bind(wx.EVT_COMBOBOX, self.SelectBehavior)
        colisions_sizer.Add(self.collide_op, 0, border= 10,
            flag= wx.LEFT|wx.ALIGN_CENTER_VERTICAL)

    def _addBounce_slider(self):
        slider_sizer= wx.BoxSizer(wx.HORIZONTAL)
        self.flex_grid_sizer.Add(slider_sizer, flag= wx.ALIGN_CENTER_VERTICAL,)
        self.slider4= wx.Slider(self, value= 10, minValue= 0, maxValue= 10, style= wx.SL_LABELS)
        self.slider4.Bind(wx.EVT_SLIDER, self.sliderUpdate1)
        slider_sizer.Add(self.slider4, flag= wx.ALIGN_CENTER_VERTICAL)
        return self.slider4

    def _render_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnRender)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button



###########################################################################   
####################### PARAMETER EVENTS ##################################
###########################################################################


    #Allow you to create the acual rain system.
    #create both SOP and PO nodes.
    def OnClickAdd(self, event):
        geo = hou.node("/obj").createNode("geo", "Rain_System", run_init_scripts = False)
        grid = geo.createNode("grid", "EMIT_FROM")
        grid.setParms({"ty":10})
        popnet = grid.createOutputNode("popnet", "RAIN_EMITTER")
        popnet.setDisplayFlag(1)
        popnet.setRenderFlag(1)
        source = popnet.createNode("source", "RAIN_ORIGIN")
        defaultGeo = source.parm("usecontextgeo")
        defaultGeo.set("first")
        birthPlace = source.parm("emittype")
        birthPlace.set("surfacerandom")
        gravity = source.createOutputNode("force", "RAIN_DirCtrl")
        defaultRainDir = gravity.parm("forcey")
        defaultRainDir.set(-1)
        rSpeed = gravity.createOutputNode("drag", "RAIN_Spd_Ctrl")
        defaultRainSpd = rSpeed.parm("scale")
        defaultRainSpd.set(2)
        render = rSpeed.createOutputNode("render", "RENDER_ME")
        renderAs = render.parm("prtype")
        renderAs.set("line")
        renderSize = render.parm("prsize")
        renderSize.set(.0025)
        render.setRenderFlag(1)
        collide = render.createOutputNode("collision", "COLLIDE_WITH")
        out = hou.node("/out")
        renderIt = out.createNode("ifd", "RENDER_IT")




    # Next 3 functions are to create a collision.
    # Once checked render flag(display) switches from drag to collision node.
    def CheckOn(self, event):
        collide = hou.node('/obj/Rain_System/RAIN_EMITTER/COLLIDE_WITH')
        render = hou.node('/obj/Rain_System/RAIN_EMITTER/RENDER_ME')
        if self.collide_on.GetValue():
            collide.setRenderFlag(1)
        else: render.setRenderFlag(1)

    def OnRender(self, event):
        try:
            hou.node('/out/RENDER_IT').render()

        except:
               exc_log

Or is it possible to have it when you click the render button it runs a separate script without without threading?

Link to comment
Share on other sites

GUIs and threading are tricky. Like I said, we thought it should have worked fine but apparently not. Back in the day (9.0 alpha) when Luke and I first experimented with using Python GUI toolkits in Houdini, putting the apps in another thread was the only way to go unless you wanted the interface to be locked up when the dialog was open. Once he added the ability to integrate things into Houdini's UI event loop using threading with GUIs became totally unnecessary and still somewhat problematic. Using the even loop approach I posted above is the best and what I'd consider the 'right' way to building a Python GUI in Houdini. Trying to run a script on the button press in another thread, while possible but not straightforward is not guaranteed to work properly. Is there some reason why you need your interface in a different thread that can't be accomplished by building it into the event loop?

Link to comment
Share on other sites

Still a little new to python, so I'm sorry for not following you. In your previous post you said you got rid of the threading and MyApp stuff. What threads do I have to delete and do I just deleted the MyApp class? Also do I have to rebuild the UI once I do this, or can my current UI work with this set up? It would be nice to get this render button working, so the user can use the fx tool and not have to dive into houdini all the time; however if its to complicated for a python newbie, I'll tackle this problem later. Thanks for your tips graham, you've been very helpful.

Link to comment
Share on other sites

You don't have to rebuild anything or make any changes. Here is it working using the first example (just create and render buttons). To add you other stuff you just add it in.

I've removed the MyApp class from near the top and gotten rid of all the AppThread stuff at the bottom. To run the dialog you just import the module and call rain().

import sys

import wx

import hou
import toolutils 

def exc_log():
    print "Exception: '%s' on line %d" % (sys.exc_value, sys.exc_traceback.tb_lineno)

# Creates the Window
class MyFxTool(wx.Frame):
    def __init__(self, parent, id, title):
        wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size (405, 505),
                          style = wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.STAY_ON_TOP)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)
        self.panel= MyFxToolPanel(self)
        self.sizer.Add(self.panel, 1, flag= wx.EXPAND)
        self.Layout()


# Creates the Layout
class MyFxToolPanel(wx.Panel):
    def __init__(self, parent):
        super(MyFxToolPanel, self).__init__(parent)
        self.sizer= wx.BoxSizer(wx.VERTICAL)
        self._add_flex_grid()
        self.SetSizer(self.sizer)

    def _add_flex_grid(self):
        self.flex_grid_sizer= wx.FlexGridSizer(cols= 2, vgap= 10, hgap= 70)
        self.sizer.Add(self.flex_grid_sizer, 1, border= 20, flag= wx.ALL|wx.EXPAND)
        self._add_static_text("Rain System")
        self.start_button= self._add_button("Make It Rain")
        self._add_static_text("Render Effect")
        self._render_button= self._render_button("Render")


###########################################################################  
####################### PARAMETER CONTROLS ################################
###########################################################################

    def _add_static_text(self, label_text):
        static_text= wx.StaticText(self, label= label_text,
        style= wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
        self.flex_grid_sizer.Add(static_text, flag= wx.ALIGN_CENTER_VERTICAL)

    def _add_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.onclickAdd)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button

    def _render_button(self, label_text):
        button= wx.Button(self, label= label_text)
        button.Bind(wx.EVT_BUTTON, self.OnRender)
        self.flex_grid_sizer.Add(button, flag= wx.ALIGN_CENTER_VERTICAL)
        return button



###########################################################################   
####################### PARAMETER EVENTS ##################################
###########################################################################


    #Allow you to create the acual rain system.
    #create both SOP and PO nodes.
    def onclickAdd(self, event):
        geo = hou.node("/obj").createNode("geo", "Rain_System", run_init_scripts = False)
        grid = geo.createNode("grid", "EMIT_FROM")
        grid.setParms({"ty":10})
        popnet = grid.createOutputNode("popnet", "RAIN_EMITTER")
        popnet.setDisplayFlag(1)
        popnet.setRenderFlag(1)
        source = popnet.createNode("source", "RAIN_ORIGIN")
        defaultGeo = source.parm("usecontextgeo")
        defaultGeo.set("first")
        birthPlace = source.parm("emittype")
        birthPlace.set("surfacerandom")
        gravity = source.createOutputNode("force", "RAIN_DirCtrl")
        defaultRainDir = gravity.parm("forcey")
        defaultRainDir.set(-1)
        rSpeed = gravity.createOutputNode("drag", "RAIN_Spd_Ctrl")
        defaultRainSpd = rSpeed.parm("scale")
        defaultRainSpd.set(2)
        render = rSpeed.createOutputNode("render", "RENDER_ME")
        renderAs = render.parm("prtype")
        renderAs.set("line")
        renderSize = render.parm("prsize")
        renderSize.set(.0025)
        render.setRenderFlag(1)
        collide = render.createOutputNode("collision", "COLLIDE_WITH")
        out = hou.node("/out")
        renderIt = out.createNode("ifd", "RENDER_IT")


    def OnRender(self, event):
        try:
            hou.node('/out/RENDER_IT').render()


        except:
               exc_log



def runWxApp(app, window):
    def processWxEvents():
        """Houdini runs this callback to from its event loop to process wx
           events."""
        while event_loop.Pending():
            event_loop.Dispatch()
            app.ProcessPendingEvents()

    def onClose(event):
        """We bind this callback to the window's close event to destroy the
           window and stop the wx event loop."""
        window.Show(False)
        window.Destroy()
        processWxEvents()
        hou.ui.removeEventLoopCallback(processWxEvents)

    window.Bind(wx.EVT_CLOSE, onClose)

    window.Show(True)
    event_loop = wx.EventLoop()
    wx.EventLoop.SetActive(event_loop)
    hou.ui.addEventLoopCallback(processWxEvents)

def rain():
    app = wx.PySimpleApp()
    frame = MyFxTool(None, -1, 'Rain System Tool')
    runWxApp(app, frame)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...