Mat Preset to Change ONLY Colors on a Blender Node?



  • @perpetualrevision
    Oups! Sorry I didn't notice ;)
    I hope it will work on your MAC.

    @anomalaus
    Ah I see! thank you for the heads-up!
    I only hope I can remember that until the next time... :D :D

    import ego
    import brain
    true = forget.tooManyThings()
    root = oldAge
    
    if true:
        try:
            flogMeWithCookedNoodle()
        except:
            ignore()
                
    
    root_destroy()
    
    root.mainloop()
    

    Now let's see if I can do it :)

    Cheers!
    K



  • @karina said in Mat Preset to Change ONLY Colors on a Blender Node?:

    I hope it will work on your MAC.

    As I think you suspected, Python scripts that use Tkinter are not compatible with the Mac version of Poser, so when I tried to use your script, I got this error message:

    0_1535330998863_Karina-ColorScriptError-screenshot.png

    But once you've tested it to the point you're willing to share it somewhere like ShareCG, then I can add a note to my "Messy Painter" set directing Windows users to give the script a try!



  • Hold out Lady Paintalot! Rescue is near!

    Actually, a wxPython script I dangled together the last days.
    It's not the most elegant one, but it should work for now.
    However, there's still a caveat:

    wxPython experts to the rescue, please!

    When you run the script, the "Simple_Color" nodes will be set as expected. So the script does it's job.

    But Poser doesn't!

    • The preview window won't refresh, still showing the previous colour .
    • Same for the Material Room UI.
      It only refreshes when you use the pipette tool in the Material Room and click on the prop!
    • Or you switch to another material, and then back again. Or you click on the "eye" in the node...

    Strangely the tkInter version of the script works OK!

    I'll attach a raw version of the script so hopefully you can tell me what is missing.

    Thank you!
    Karina

    import poser, os
    import wx, wx.py, wx.aui
    revisor = poser.WxAuiManager()
    root = revisor.GetManagedWindow()
    
    #####  VARDEFS	###########################################################################################
    scene = poser.Scene()
    figura = scene.CurrentFigure() #figura == singular
    figuri = scene.Figures() #figuri == plural
    objekt = scene.CurrentActor() #objekt with "k"!
    feedbackString = "<PROP> " + objekt.Name()
    if objekt.IsBodyPart():
    	feedbackString = "<FIGURE> " + figura.Name()
    DiffuseList = [-1,-1,-1]
    rgb = (235,235,235)
    newColour = rgb
    hexcolour = "#ebebeb"
    variant = 1
    connectedToNode = 27 #27 is "Custom_output_1" socket in "PoserSurface"
    
    #####  MAIN CLASS  ########################################################################################
    
    
    class wxGUI(wx.ScrolledWindow):
    
    	def __init__(self, parent):
    		wx.ScrolledWindow.__init__(self,parent=parent,id=wx.NewId(),pos=wx.Point(-100,250))
    		self.parent = parent
    		self.panel = wx.ScrolledWindow(self)
    
    		self.labelDonor = wx.StaticText(self, id=-1, label="NOTICE: Works on ALL materials of selected Prop(s)/Figure(s).", pos=(10,2))
    				
    		self.radio1 = wx.RadioButton(self,11, label = feedbackString, pos = (10,30), style = wx.RB_GROUP) 
    		self.radio2 = wx.RadioButton(self,22, label = 'ALL PROPS',pos = (10,55)) 
    		self.radio3 = wx.RadioButton(self,33, label = 'ALL FIGURES',pos = (10,80)) 
    		self.Bind(wx.EVT_RADIOBUTTON, self.onBtn)
    		
    		self.buttonColourPick = wx.Button(self, id=-1, label="[ OPEN COLOUR PICKER ]", pos=(10,110), size=(130,50))
    		self.Bind(wx.EVT_BUTTON, self.colourPick, self.buttonColourPick)
    		
    		self.buttonQuit = wx.Button(self, id=-1, label="[  ABORT  ]", pos=(210,140), size=(80,20))
    		self.Bind(wx.EVT_BUTTON, self.closeAll, self.buttonQuit)
    		self.buttonRun = wx.Button(self, id=-1, label="[ MAKE IT SO ]", pos=(300,140), size=(80,20))
    		self.Bind(wx.EVT_BUTTON, self.makeItSo, self.buttonRun)
    		
    		self.colourBox = wx.StaticBox(self, wx.ID_ANY, "Your New Colour:", pos=(210,50), size=(170,80))
    
    
    		self.Centre() 
    		self.Show(True)    
    		
    		
    	def onChangeBackground(self, hexColour): # but this shall only set the colour of the "colourBox in line 62
    		self.SetBackgroundColour(hexColour)
    		self.Refresh()	
    		
    		
    	def onBtn(self, event): # not necessary atm. - IGNORE
    		global variant
    		if self.radio1.GetValue():
    			#feedback.config(text = "Apply this colour to:\n" + feedbackString)
    			variant = 1
    		if self.radio2.GetValue():
    			#feedback.config(text = "Apply this colour to:\n<ALL PROPS> in the scene.")
    			variant = 2
    		if 	self.radio3.GetValue():
    			#feedback.config(text = "Apply this colour to:\n<ALL FIGURES> in the scene.")
    			variant = 3
    		
    		
    	def colourPick(self, event):
    		global newColour
    		data = wx.ColourData()
    		data.SetChooseFull(1)
    		for i in range(17):
    			cCol = 255 - 17 * i
    			data.SetCustomColour(i, (cCol, cCol, cCol))
    		
    		data.SetColour((newColour)) # set the currently selected colour in the chooser
    		dialog = wx.ColourDialog(self, data)
    		if dialog.ShowModal() == wx.ID_OK:
    			data = dialog.GetColourData()
    			newColour = data.GetColour().Get()
    			hexColour = self.rgb_to_hex(newColour)
    			#self.onChangeBackground(hexColour)
    			
    	def rgb_to_hex(self, rgb):
    		return '#%02x%02x%02x' % (rgb)
    		
    		
    	def makeItSo(self, event): # Aaand Action!
    		self.iterate(variant)
    		scene.DrawAll()
    		self.closeAll(0)
    	
    	
    	def iterate(self, variant): #parses all props, figures and their mat zones as defined by the radiobuttons
    		for bce in scene.Actors(): #bce == "BCe"(all) in Russian :p
    			if variant == 3:
    				for fig in figuri:
    					self.setColour(fig)
    			if variant == 2:
    				if bce.IsProp():
    					self.setColour(bce)
    			if variant == 1:
    				obj = scene.CurrentActor()
    				if obj.IsProp:
    					self.setColour(obj)
    
    					
    	def setColour(self, currentObj): #parses the shader tree
    		for mat in currentObj.Materials():
    			tree = mat.ShaderTree()
    			self.setValues(tree, connectedToNode)
    
    			
    	def setValues(self, tree, nodeNr):
    		r = min(255, max(0, float(newColour[0]) / 255))
    		g = min(255, max(0, float(newColour[1]) / 255))
    		b = min(255, max(0, float(newColour[2]) / 255))
    		whichNode = tree.Node(0).Input(27)
    		pluggedIn = whichNode.InNode()
    		if pluggedIn is None:
    			1 == 1
    		else:	
    			colourChip = pluggedIn.InputByInternalName("Color")
    			colourChip.SetColor(r,g,b)
    		
    		
    	def doNothing(self):
    		_void = 0
    	
    	
    	def closeAll(self,event):
    		global scene
    		poser.WxAuiManager().DetachPane(self)
    		self.Close(True)
    		scene.DrawAll()
    
    
    
    #####  MAIN LOOP  #########################################################################################
    
    
    app = wxGUI(root)
    revisor.AddPane(app, wx.aui.AuiPaneInfo().
                Caption("Karina's Batch Colour Gadget V1.1").CaptionVisible().
                Float().Resizable().DestroyOnClose().Dockable(False).
                FloatingSize(wx.Size(400, 200)).CloseButton(True))
    info = revisor.GetPane(app)
    info.Show()
    revisor.Update()
    


  • @karina said in Mat Preset to Change ONLY Colors on a Blender Node?:

    ...
    info = revisor.GetPane(app)
    info.Show()
    revisor.Update()

    I don't see a material.ShaderTree().UpdatePreview() anywhere, so the material room can't update it's preview. Perhaps if that is done before the scene.DrawAll() calls, it will make a difference.

    I've also been frustrated with lack of updates in the material room, but the Update calls still have to be made.



  • @anomalaus:

    aaaaand: That did the trick!

    def setColour(self, currentObj): #parses the shader tree
    		for mat in currentObj.Materials():
    			tree = mat.ShaderTree()
    			self.setValues(tree, connectedToNode)
    			mat.ShaderTree().UpdatePreview()
    

    The materials AND the scene preview window now update as expected!
    All problems solved; thank you a ton! :)
    K



  • @karina Now that @Snarlygribbly has made the source for EZSkin (and others) public domain you can get a lot of examples of Wx and Other stuff especially to do with the mat room.



  • @karina oh, some other material related things I've recently had to deal with in my own scripts:

    Some actors have no materials, so you can't safely iterate over actor.Materials() without try:except: or a pre-test. This is just bad API programming IMVHO. Materials() should return an empty list if there are no materials, not None!

    For completeness' sake, you need to iterate through the layers within each material, as they have a separate ShaderTree with it's own UpdatePreview() method.

    On occasion, you also strike materials which present with no ShaderTree, so I use:

    for actor in scene.Actors():
        if actor.ItsFigure() is None:
            if actor.Materials(): # Some actors like "UNIVERSE" return None, so we can't just iterate
                for mat in actor.Materials():
                    for layer in mat.Layers():
                        try:
                            tree = layer.ShaderTree() # Some materials raise poser.error on this call
                            if not tree:
                                continue
                        except: # Ignoring implicit poser.error from ShaderTree() call
                            continue
                        # Parse tree
    else: # Now parse figures' materials
        for figure in scene.Figures():
            try: # An alternative way to protect from a None on calling Materials()
                for mat in figure.Materials():
                    for layer in mat.Layers():
                        try:
                            tree = layer.ShaderTree() # Some materials raise poser.error on this call
                            if not tree:
                                continue
                        except: # Ignoring implicit poser.error from ShaderTree() call
                            continue
                        # Parse tree
            except:
                pass # Ignore inability to iterate over potential None returned by Materials() call
    

    These are not prescriptive examples, just ways I've come up with to quash unexpected script errors. I also note that my sample loop may not deal appropriately with props on figures that have their own materials. I've frequently noted to myself that it's terribly wasteful to iterate over all of the actors in a figure, since the materials they return are just the ones you get from the figure directly, except when an actor or prop has customMaterials.



  • @amethystpendant said in Mat Preset to Change ONLY Colors on a Blender Node?:

    @karina Now that @Snarlygribbly has made the source for EZSkin (and others) public domain you can get a lot of examples of Wx and Other stuff especially to do with the mat room.

    Oh did he? I didn't know, so thank you for the heads-up!

    @anomalaus said in Mat Preset to Change ONLY Colors on a Blender Node?:

    @karina oh, some other material related things I've recently had to deal with in my own scripts:

    Some actors have no materials, so you can't safely iterate over actor.Materials() without try:except: or a pre-test. This is just bad API programming IMVHO. Materials() should return an empty list if there are no materials, not None!

    For completeness' sake, you need to iterate through the layers within each material, as they have a separate ShaderTree with it's own UpdatePreview() method.

    On occasion, you also strike materials which present with no ShaderTree, so I use:
    (see post above)
    These are not prescriptive examples, just ways I've come up with to quash unexpected script errors. I also note that my sample loop may not deal appropriately with props on figures that have their own materials. I've frequently noted to myself that it's terribly wasteful to iterate over all of the actors in a figure, since the materials they return are just the ones you get from the figure directly, except when an actor or prop has customMaterials.

    Thank you for looking further into the code!

    To my defense let me say that it's still much experimenting - wrote my last line of Py code over a year ago :/
    You're right, omitting error management is bad style, but if I add it in the development phase I wouldn't get immediate error messages in the "print" window. And I get a lot of error messages, believe me :)

    Thank you for the heads-up about the problem with missing mat zones and other caveats in the "iterate" routine, and especially for your example.
    I'll have a look at it this weekend and rewrite my own code accordingly. Unfortunately I'm too busy today...

    Thank you for your help!

    Karina



  • To give at least a little back, here is something I figured out

    Because I wanted to add the ability to change the current actor in my script I thought I could use the eventCallback()
    for Poser.
    During my searches I stumbled upon this thread and post:

    @fbs7 said in Metaballs script:

    Hmm.. check this:

    def eventCallback(scene, code):
    	if code <> 16:
    		print 'Code = ',code
    
    ...
    scene.SetEventCallback(eventCallback)
    

    It will display a continuous series of code = 16, even when nothing's happening. No idea yet what is that 16. Whenever you change frames manually, it sends a code = 17

    So I tinkered with it but got the same result, a continous kEventCode_ "16" running ad nauseum until I killed it with
    "scene.ClearEventCallback()"

    Here's what I tried to make it work:

    """
    # Notes on the EventCallBack():
    #	 - Poser returns kEventCode "16" all the time, endlessly, unless an event happens.
    #	 - IF an event happens (like selecting another actor), 
    #	   Poser still returns "16", >>>but this time ADDS the kEventCode of the actual event<<<:
    #	   SO an event like poser.kEventCodeACTORSELECTIONCHANGED (which is 8) 
    #	   will return: 16 + 8
    # 
    # Since kEventCode "16" is undocumented, I think it stands for "idle" and is only constantly
    # sent due to a glitch in the code 
    # (because, it doesn't make sense to waste processing power for a non-event)
    """
    
    def eventCallbackFunc(thisScene, thisEventType):
    	if thisEventType <> 16: # filter out the constant "16" events
    		if thisEventType == 16 + poser.kEventCodeACTORSELECTIONCHANGED: # add 16 to the kEventCode
    			print "A new actor was selected. Code: ", thisEventType # print only for testing
    	
    scene.SetEventCallback(eventCallbackFunc)
    

    Maybe it's useful.
    K



  • @karina Announcing that Poser has nothing important to do makes sense. I use this often to delay my own functions to make sure Userinput has priority.



  • @anomalaus It's Python. You can create your very own special iterator's.

    Returning None if an actor has no materials is correct. An empty list means something different (again: it's Python 😉).



  • @karina Have a look into Posers Python folder with demo scripts. There is a script called "eventCallback.py".
    Here is the better way to handle events:

    def EventCB(iScene, iEventType):
    	string = ''
    	if( (iEventType & poser.kEventCodeKEYSCHANGED) != 0):
    		string = string + 'KeysChanged, '
    	if( (iEventType & poser.kEventCodePARMCHANGED) != 0):
    		string = string + 'ParmChanged, '
    	if( (iEventType & poser.kEventCodeACTORDELETED) != 0):
    		string = string + 'ActorDeleted, '
    	if( (iEventType & poser.kEventCodeACTORSELECTIONCHANGED) != 0):
    		string = string + 'ActorSelectionChanged, '
    	if( (iEventType & poser.kEventCodePARMDELETED) != 0):
    		string = string + 'ParmDeleted, '
    	if( (iEventType & poser.kEventCodeACTORADDED) != 0):
    		string = string + 'ActorAdded, '
    	if( (iEventType & poser.kEventCodePARMADDED) != 0):
    		string = string + 'ParmAdded, '
    	if( (iEventType & poser.kEventCodeITEMRENAMED) != 0):
    		string = string + 'ItemRenamed, '	
    	if( (iEventType & poser.kEventCodeANIMSETSCHANGED) != 0):
    		string = string + 'AnimSetsChanged, '
    	if( (iEventType & poser.kEventCodeSETUPMODE) != 0):
    		string = string + 'SetupMode, '
    ...
    

    Reason is that iEventType may contain more then one event, e.g.:

    iEventType = kEventCodeACTORSELECTIONCHANGED | kEventCodeACTORADDED
    


  • @karina is it not Poser's own equivalent of ProcessSomeEvents(). I.e. a way of sharing processing time with subprocesses. Since Poser deprecates (if not prevents) Python scripts being multi-threaded in any real sense.



  • @adp I understand that, but I don't agree with the distinction. Python is subordinate to Poser, in this case, so Poser's Python API should reflect the requirements of Poser, not Python's style guide.

    Anyway, it's not worth arguing over :-). I just felt that to make an unnecessary exception for a single actor in the scene, "UNIVERSE" which never has, and can never have any materials, is an unfortunate trap for simple iterations over all actors, forcing special handling, rather than "empty list, so nothing to do here". When someone digitizes Guido's brain, then we'll all be more enlightened over the whys and wherefores of Python. (Not that I imagine Poser will ever support Python 3+)


  • Poser Ambassadors

    Most of what Poser Python does is cpu bound, so having a threading module would more often than not just slow it down.



  • @anomalaus You don't need and exeption.

    The "pythonic way" is:

    for mat in Materials() or []:
        print mat
    


  • @adp kewl! New toy to play with ;-) Haven't seen that one before. I've been gradually trawling through my scripts and replacing abort flags and code blocks with asserts, where appropriate.

    Python's funny in the way a piece of code can run happily for ages, until you run into that one condition with an unhandled exception and BOOM! everything falls over. ;-)



  • @shvrdavid that's fair. I just picked that observation in relation to processes, not because I have any issue with it. Multi-threaded bending is certainly a worthy case of something entirely within Poser's control, whereas Python scripts can never be.

    Speaking of processes and Python, it would truly be wonderful if there were a way to record the details of a PythonCallback valueOperation (which may not have been installed within the purview of a currently executing script, so that it could be temporarily removed and then restored along with all of the other valueOperations affecting a parameter. I've run into this case a few times when trying to implement procedures which need to extract parameter dial values at a range of frames, without valueOperation influence. Even if the callback is only monitoring a parameter, and not modifying it's value, there's no way to restore it if you didn't apply it in the first place. I'm still waiting for an UnaffectedValueFrame() method to avoid having to iterate through actual animation frames and use UnaffectedValue() at each step.



  • Oups, looks like I've opened a can of worms here, at least for me :D

    Meaning, I don't even guess what you people are discussing here in the last few threads.
    To me this is all like Ron Moore (STTNG witer) lately admitted:

    At his recent keynote speech at the New York Television Festival, former Star Trek writer and creator of the re-imagined Battlestar Galactica Ron Moore revealed the secret formula to writing for Trek.
    He described how the writers would just insert "tech" into the scripts whenever they needed to resolve a story or plot line, then they'd have consultants fill in the appropriate words (aka technobabble) later.
    "It became the solution to so many plot lines and so many stories," Moore said. "It was so mechanical that we had science consultants who would just come up with the words for us and we'd just write 'tech' in the script. You know, Picard would say 'Commander La Forge, tech the tech to the warp drive.' I'm serious. If you look at those scripts, you'll see that."
    Moore then went on to describe how a typical script might read before the science consultants did their thing:
    La Forge: "Captain, the tech is overteching."
    Picard: "Well, route the auxiliary tech to the tech, Mr. La Forge."
    La Forge: "No, Captain. Captain, I've tried to tech the tech, and it won't work."
    Picard: "Well, then we're doomed."
    "And then Data pops up and says, 'Captain, there is a theory that if you tech the other tech ... '" Moore said. "It's a rhythm and it's a structure, and the words are meaningless. It's not about anything except just sort of going through this dance of how they tech their way out of it."

    Full blog entry here: http://www.antipope.org/charlie/blog-static/2009/10/why_i_hate_star_trek.html
    and I DISAGREE with "Charlie" (Charles Stross) rant as far as Star Trek is concerned (discussing this would lead us to OT, so I'lll skip)

    What I want to say is:

    Most of your replies are far, far beyond my own knowledge of Python.
    I hope it makes sense to me some time later.
    --> Is there a way to save this whole thread to my local disk for future reference?

    Besides that, I want to thank you all VERY MUCH for all the input you made.
    And though I didn't understand many of it, I still learned some things - and that's what this is about.
    I'll keep watching this thread.

    Karina



  • @karina "Semper Tech" ;-)

    Having worked with colleagues who could not ever admit that they didn't know or understand something, until caught out blatantly BS'ing, I am a great fan of admitting that one doesn't know everything and is always happy to learn, even that one has mis-spoken through ignorance. That way, people are happier to share both their mistakes and successes. Enlightenment (and Potassium Nitrate!) can be found in the darkest dung-piles ;-)