Hello I need some help I'm a novice when it comes to advanced python coding, I've been trying to solve this problem but I'm unable to find an answer. I'm trying to find a way so that when someone clicks on an object inside Maya for example a basic sphere it will print the object class and parent class into each of the textFields; then should a user select something else like a cube it will instead show that object class&parent class. I know I need a function which will activate when an object is selected then place the value into the textFields, but I can't figure out how to do it. If anyone has a solution it would be greatly appreciated :)
import maya.cmds as cmds
from functools import partial
class drawUI(): #Function that will draw the entire window
#check to see if window exists
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
ObjParClass = cmds.textField(editable = False)
cmds.showWindow("UI_MainWindow") #shows window
The main stumbling block is that you're using the class setup in a very unusual way. The usual pattern is to have an __init__ method that creates a new copy ("instance") of the class; in you case you're executing the code when the class is defined rather than when you invoke it. The usual class shell looks like:
class Something (object) # derive from object in python 2.7, ie, in maya
def __init__(self):
self.variable = 1 # set persistent variables for the object
do_something() # other setup runs now, when a new Something is created
def method(self):
print self.variable # you can use any variables defined in 'self'
The usual way to create a reactive UI in maya is to use a scriptJob. These will fire a script callback when certain events happen inside of Maya. The easiest event for your purpose is SelectionChanged which fires when the selection changes.
The other thing you want to do is figure out how to pack this into a class. Sometimes it's a good idea to create your class object and then give it a show() or layout() method that creates the actual UI -- a lot depends on how much you need to set the object up before showing the window.
In this case I put the behavior into the __init__ so it will create the UI when you make the object. The __init__ also creates the scriptJob and parents it to the window (so it will disappear when the window does).
class drawUI(object):
def __init__(self):
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
self.window = cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
self.ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
self.ObjParClass = cmds.textField(editable = False)
cmds.showWindow(self.window)
cmds.scriptJob (e = ("SelectionChanged", self.update_UI), p= self.window)
def update_UI(self, *_, **__):
sel = cmds.ls(selection=True) or []
c = ["-none-", "-none-"]
if sel:
c = cmds.nodeType(sel[0], i = True)
cmds.textField(self.ObjClass, e=True, text = c[-1])
cmds.textField(self.ObjParClass, e=True, text = ", ".join(c[:-1]))
test = drawUI()
By using self.update_UI without arguments in the scriptJob, we make sure that the function that fires knows which object it's updating. Using the self variables for the textfields lets us update the UI that goes with the window without worrying about remembering names in some other scope.
More details here
Related
Before I get into my question, I am (self) learning how Python and the .NET CLR interact with each other. It has been a fun, yet, at times, a frustrating experience.
With that said, I am playing around on a .NET WinForm that should just simply pass data that is typed into a text box and display it via a message box. Learning how to do this should propel me into other means of passing data. This simple task seems to elude me and I can't seem to find any good documentation on how tyo accomplish this. Has anyone attempted this? If so, I am willing to learn so if someone could point me in the right direction or give me a hint as to what I've done wrong?
PS - I have done some coding in C#.NET and VB.NET so the passing of the variables seems like it should be enough but apparently it isn't.
import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")
from System.Windows.Forms import *
from System.Drawing import *
class MyForm(Form):
def __init__(self):
# Setup the form
self.Text = "Test Form"
self.StartPosition = FormStartPosition.CenterScreen # https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.form.startposition?view=net-5.0
# Create label(s)
lbl = Label()
lbl.Parent = self
lbl.Location = Point(15,15) # From Left, From Top
lbl.Text = "Enter text below"
lbl.Size = Size(lbl.PreferredWidth, lbl.PreferredHeight)
# Create textbox(s)
txt = TextBox()
txt.Parent = self
txt.Location = Point(lbl.Left - 1, lbl.Bottom + 2) # From Left, From Top
# Create button(s)
btn = Button()
btn.Parent = self
btn.Location = Point(txt.Left - 1, txt.Bottom + 2) # From Left, From Top
btn.Text = "Click Me!"
btn.Click += self.buttonPressed
def buttonPressed(self, sender, args):
MessageBox.Show('This works.')
MessageBox.Show(txt.Text) # This does not
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
form = MyForm()
Application.Run(form)
txt is a local variable in __init__, meaning that you can't access it from any other function. To fix it, make it an instance variable by attaching it to self (which refers to the instance itself):
self.txt = TextBox()
self.txt.Parent = self
self.txt.Location = Point(lbl.Left - 1, lbl.Bottom + 2) # From Left, From Top
and
def buttonPressed(self, sender, args):
MessageBox.Show('This works.')
MessageBox.Show(self.txt.Text) # Now this does too
everyone
I used c++ program for a long time.
This days, I make a GUI program using python.
But, I don't understand about class of python.
The problem is like this.
At this Gui program, user can add many view. then click the button to change the view.
In my code. I put the element to a class like this
class LoadingView():
def __init__(self):
self.idx=0
self.view = pg.GraphicsLayoutWidget()
self.w1 = self.view.addPlot()
self.view.nextRow()
self.w2 = self.view.addPlot()
self.view.nextRow()
self.w3 = self.view.addPlot()
self.view.nextRow()
self.w4 = self.view.addPlot()
and I creat class instant and put it to array
self.TreeIdx = self.TreeIdx + 1
self.AddNodeToTree(SetDlg.EditBoxName.text())
Loding_view = LoadingView()
self.LView.append(LoadingView)
self.gridLayout_2.addWidget(Loding_view.view, 0, 0, 1, 1)
finally, when I push button, I want to implement to change the view
self.gridLayout_2.addWidget(self.LView[int(Text.split('.')[0]) - 1].view, 0, 0, 1, 1)
but, it returned AttributeError: class LoadingView has no attribute 'view'
I don't know how to implement it . need you help...
thank you!
You need to include 'view' in your __ init __ function. Right now you only have self.
Try this:
class LoadingView:
def __init__(self,
idx=None,
view=None,
w1=None,
w2=None,
w3=None,
w4=None,
Nrows=None,
):
self.idx=idx if idx is not None else 0
self.view=view if view is not None else pg.GraphicsLayoutWidget()
self.w1=w1
self.w2=w2
self.w3=w3
self.w4=w4
self.Nrows=Nrows
def datafill(self):
for i in self.Nrows:
self.__dict__['w'+str(i+1)]=self.view.addPlot()
Loading_view=LoadingView()
Loading_view.datafill()
You can make w a list instead of explicitly defined terms but the way I've shown will loop over the explicitly defined terms using a dictionary. This method also sets the default value of view and idx should you not provide those when you define an instance of the class.
I want to access variable of other Class.
Static variable of other Class was very good accessed.
But dynimic changed variable value of ther Class was not good accessed.
Why I can't get the changed variable value?
bl_info = {
"name": "New Object",
"author": "Your Name Here",
"version": (1, 0),
"blender": (2, 75, 0),
"location": "View3D > Add > Mesh > New Object",
"description": "Adds a new Mesh Object",
"warning": "",
"wiki_url": "",
"category": "Add Mesh",
}
import bpy
class SelectFace(bpy.types.Operator):
bl_idname = "object.d0"
bl_label = "Select Face"
selected_faces = 2
def __init__(self):
self.selected_faces = 3
def execute(self, context):
print("self.selected_faces: ", self.selected_faces)
self.selected_faces += 1
bpy.ops.object.d1('INVOKE_DEFAULT')
return {'FINISHED'}
class OperationAboutSelectedFaces(bpy.types.Operator):
""" Test dialog. """
bl_idname = "object.d1"
bl_label = "Test dialog"
F_num = bpy.props.IntProperty(name="be Selected face", default=1)
#classmethod
def poll(self, context):
obj = context.object
return(obj and obj.type == 'MESH' and context.mode == 'OBJECT')
def invoke(self, context, event):
# This block code is Not Work! --- TypeError: bpy_struct.__new__(type): expected a single argument.
testInstance = SelectFace() # why not work?
print("testInstance.selected_faces: ", testInstance.selected_faces)
self.F_num = testInstance.selected_faces
# This block code is nice Work!
testInstance = SelectFace.selected_faces
print("testInstance: ", testInstance)
self.F_num = testInstance
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
context.active_object.data.polygons [self.F_num].select = True
return {'FINISHED'}
def register():
bpy.utils.register_class(SelectFace)
bpy.utils.register_class(OperationAboutSelectedFaces)
def unregister():
bpy.utils.unregister_class(SelectFace)
bpy.utils.unregister_class(OperationAboutSelectedFaces)
if __name__ == "__main__":
register()
bpy.ops.object.d0()
An operator in blender is used to perform an action. While we use a class to define that action and related properties, we shouldn't treat them as normal python classes. The properties of an operator should be used to adjust the action peformed, not to hold variable data.
As the operators properties control the result of the operator, they are used by blender to perform undo/redo steps. These properties are also adjustable by the user using the operator properties panel by pressing F6 and can also be found at the bottom of the toolbar region.
Add bl_options = {'REGISTER', 'UNDO'} to your operator to allow a user to adjust your operator. You can also customise the display within this panel by giving your operator a draw(self,context) method.
To control how an operator performs it's task when we call it directly, we can add the properties to the operator call -
bpy.ops.object.d1(F_num=4, val2=3.6)
If you are adding an operator button to a panel you can use -
row.operator('object.d1').F_num = 4
or if you need to set multiple values you can use -
op = row.operator('object.d1')
op.F_num = 4
op.val2 = 3.6
The example you provided uses a property that appears to only be valid for one object, if the user selects another object it will no longer be valid. This property would work better as an object property, you can add a property to the object class (or several others listed as subclasses of ID) by adding it in your addons register() and removing it in unregister()
def register():
bpy.types.Object.selected_faces = bpy.props.IntProperty()
def unregister():
del bpy.types.Object.selected_faces
For that example you could even count the selected faces when you needed the value -
selected_faces_count = len([f for f in obj.data.polygons if f.select])
I assume that
testInstance = SelectFace() # why not work?
is the real question.
see:
https://www.blender.org/api/blender_python_api_2_60a_release/info_overview.html
seems it is not expected that you write code that creates an instance of an bpy.types.Operator. Perhaps Blender handles bpy.types.Operator sub class creation in its own way.
"Notice these classes don’t define an init(self) function. While init() and del() will be called if defined, the class instances lifetime only spans the execution. So a panel for example will have a new instance for every redraw, for this reason there is rarely a cause to store variables in the panel instance. Instead, persistent variables should be stored in Blenders data so that the state can be restored when blender is restarted."
see also, Property Definitions: https://www.blender.org/api/blender_python_api_2_66a_release/bpy.props.html
I'm trying to update my UI via a variable in another python file. Both are in there own class. Both saved in a folder called: System.
As I don't want to re-execute UI, I can't simply import the file.
My question: how does one change a variable from another class in another file, without re-executing?
toolsUI.py
class toolsUI:
def __init__(self):
# Store UI elements in a dictionary
self.UIElements = {}
if cmds.window("UI", exists=True):
cmds.deleteUI("UI")
self.UIElements["window"]=cmds.window("UI", width=200, height=600, title="UI")
self.createColumn() # Create Column
# Display window
cmds.showWindow(self.UIElements ["window"])
def createColumn(self):
self.UIElements["column"] = cmds.columnLayout(adj=True, rs=3)
self.UIElements["frameLayout"] = cmds.frameLayout(height=columnHeight, collapsable=False, borderVisible=True, label="To Change Label")
maintenance.py
class maintenance:
def __init__(self):
changeLabel = "Label is Changed"
self.changeLabelColumn(changeLabel) # Change Label Column
def changeLabelColumn(self, changeLabel):
import System.toolsUI as toolsUI """<--- probably not a good idea"""
cmds.frameLayout(toolsUI.UIElements["frameLayout"], edit=True, label=changeLabel)
The right way to do this afaict would be to create an object of the toolsUI type, and then operate on that instead.
import System
class maintenance:
def __init__(self):
changeLabel = "Label is Changed"
self.ui = System.toolsUI() # create a new object
self.changeLabelColumn(changeLabel)
def changeLabelColumn(self, changeLabel):
cmds.frameLayout(
self.ui.UIElements["frameLayout"], # use the object instead
edit=True,
label=changeLabel)
this way you can have multiple toolsUI objects that don't interfere with each other.
I've realized that there were similar questions located
here:
textfield query and prefix replacing
and
here:
Python - Change the textField after browsing - MAYA
However, these do not address the issue if you have two definitions and need the text in the textField to be queried (actually CHANGE the text in the textField).
I know from experience that doing what I have below in MelScript actually works, but for the sake of Python, and learning how to do it in Python, it seems to not work. Am I missing something? Do I need a lambda to get the name of the object selected and query the textField?
I have an example (a snip-bit of what needs to be fixed):
from pymel.core import *
def mainWindow():
window('myWin')
columnLayout(adj=1)
button('retopoplz', ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],
l='START RETOPOLOGY', c='Retopo(TextToMakeLive)')
TextToMakeLive = textField(ann='Mesh Selected', bgc=[.2,0,0],
edit=0, tx='NONE')
setParent('..')
showWindow('myWin')
def Retopo(TextToMakeLive):
#This tool selects the object to retopologize
MakeLiveField = textField(TextToMakeLive, q=1, tx=1)
MakeSelectionLive = (ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if MakeSelectionLive == 1:
TextToMakeLive = textField(TextToMakeLive, ed=1,
tx=MakeSelectionLive,
bgc=[0,.2,0])
shape = ls(s=MakeSelectionLive[0])
setAttr((shape + '.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer', num=1, nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
mainWindow()
GUI objects can always be edited -- including changing their commands -- as long as you store their names. So your mainWindow() could return the name(s) of gui controls you wanted to edit again and a second function could use those names to change the looks or behaviors of the created objects.
However, this is all much easier if you use a python class to 'remember' the names of the objects and any other state information: it's easy for the class to 'see' all the relevant info and state. Here's your original converted to classes:
from pymel.core import *
class RetopoWindow(object):
def __init__(self):
self.window = window('myWin')
columnLayout(adj=1)
button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY', c = self.do_retopo)
self.TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
def show(self):
showWindow(self.window)
def do_retopo(self, *_):
#This tool selects the object to retopologize
MakeLiveField= textField(self.TextToMakeLive,q=1,tx=1)
MakeSelectionLive=(ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if len( MakeSelectionLive) == 1:
TextToMakeLive=textField(self.TextToMakeLive,ed=1,tx=MakeSelectionLive,bgc=[0,.2,0])
shape=ls(s=MakeSelectionLive[0])
setAttr((shape+'.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer',num=1,nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
RetopoWindow().show()
As for the callbacks: useful reference here
You need to assign the command flag AFTER you have created your textField to be queried.
So you would do:
my_button = button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY')
TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
button(my_button, e=True, c=windows.Callback(Retopo, TextToMakeLive))
You were along the right thought chain when you suggested lambda. Pymel's Callback can be more advantageous over lambda here. Check out the docs: http://download.autodesk.com/global/docs/maya2014/zh_cn/PyMel/generated/classes/pymel.core.windows/pymel.core.windows.Callback.html