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
Related
I'm trying to write a custom operator for Blender that:
Gets the objects currently in the scene (may be lots)
Filters them based on some criteria
Prompts the user to select/deselect any of the filtered objects (using checkboxes or similar)
Does something with the final selection.
I'm stuck on number 3. I'd like to show the user a window with checkboxes beside each of the filtered options, but to do that I'd have to be able to generate the properties dynamically.
The closest thing I've found so far is a bpy.props.EnumProperty, which takes callable to set its items. But it only supports 1 selection, whereas I need the user to be able to select multiple options.
Example:
def filter_objects(self, context):
return [obj for obj in bpy.data.objects if obj.name.startswith('A')]
class TurnObjectsBlue(bpy.types.Operator):
'TurnObjectsBlue'
bl_idname = 'object.turnobjectsblue'
bl_label = 'TurnObjectsBlue'
bl_options = {'REGISTER'}
# MultiSelectCheckboxes doesn't exist :(
chosen_objects: bpy.props.MultiSelectCheckboxes(
name='Select Objects',
)
def execute(self, context):
from coolmodule import turn_blue
for obj in self.user_selected_objects:
turn_blue(obj)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
It looks like you want a CollectionProperty. These are basically native blender lists that can store blender properties, and can have as many items as you like.
To set one up, start by creating a new class that will represent one of the items in the CollectionProperty.
It should inherit from bpy.types.PropertyGroup, and in it you can define any properties that you want to be able to access (also make sure to register that class):
class MyCollectionItem(bpy.types.PropertyGroup):
# This will tell us which object this item is for
object: bpy.props.PointerProperty(type=bpy.types.Object)
# This is whether this item (and by extension the object) is enabled/disabled
is_item_selected: bpy.props.BoolProperty()
def register():
# Don't forget to register it!
bpy.utils.register_class(MyCollectionItem)
Then you can rewrite your operator to do something like this:
class TurnObjectsBlue(bpy.types.Operator):
'TurnObjectsBlue'
bl_idname = 'object.turnobjectsblue'
bl_label = 'TurnObjectsBlue'
bl_options = {'REGISTER'}
# This acts as a list, where each item is an instance of MyCollectionItem
chosen_objects: bpy.props.CollectionProperty(
type=MyCollectionItem,
)
def execute(self, context):
from coolmodule import turn_blue
# Loop through all items in the CollectionProperty, and if they are selected, do something
for item in self.chosen_objects:
if item.is_item_selected:
turn_blue(item.object)
return {'FINISHED'}
def invoke(self, context, event):
objects = filter_objects(context)
# For each object, add a new item to the CollectionProperty, and set it's object property
for object in objects:
# Note how items are created with CollectionProperty.add()
obj_item = self.chosen_objects.add()
obj_item.object = object
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
# Loop through all of the CollectionProperty items and draw the "is_item_selected" property
for item in self.chosen_objects:
layout.prop(item, "is_item_selected")
While collection properties can act like Blender lists, they work fairly differently to python lists, so I'd suggest reading a bit about them to try and understand how they work:
https://docs.blender.org/api/current/bpy.props.html#collection-example
Hope that helps!
In the project I'm assigned to we use pytransitions. Our states are created, get equipped with additional attributes and added to a list one by one as objects first. Then this list of State objects is passed to a Machine object.
Here's a simple example:
from transitions import State
states = []
state_initial = State("initial", on_exit="some_callback")
text = "this is some text"
state.text = text
states.append(state)
This is how a machine is created:
from transitions import Machine
from some_library import SomeClass
from my_module import user_transitions
class User:
states = states
initial_state = states[0]
def __init__(self, some_param: str, another_param: SomeClass = default_param):
self.machine = Machine(model=self,
states=User.states,
initial=User.initial_state,
transitions=user_transitions,
prepare_event="preparing_callback",
after_state_change="ending_callback")
What I'd like to do is to add tags to my states at the time of or after state object creation. I mean the tags in transitions.extensions.states, so I could get them with is_tag kind of methods like in docs. Something like state_initial.add_tags(["tag1", "tag2"]) or
state_initial = State("initial", on_exit="some_callback", tags=["tag1", "tag2"])
or in any other way considering my legacy setup. How do I go about this?
My first suggestion would be to check whether you can streamline the state creation process by using a dedicated TextState instead of just assigning an additional attribute. This way you can keep your state configuration a bit more comprehensible. Reading machine configurations from yaml or json files gets way easier as well.
from transitions import Machine, State
from transitions.extensions.states import Tags
# option a) create a custom state class and use it by default
# class TextState and CustomState could be combined of course
# splitting CustomState into two classes decouples tags from the
# original state creation code
class TextState(State):
def __init__(self, *args, **kwargs):
self.text = kwargs.pop('text', '')
super(TextState, self).__init__(*args, **kwargs)
class CustomState(Tags, TextState):
pass
class CustomMachine(Machine):
state_cls = CustomState
states = []
state_initial = CustomState("initial", text="this is some text")
# we can pass tags for initialization
state_foo = dict(name="foo", text="bar!", tags=['success'])
states.append(state_initial)
states.append(state_foo)
# [...] CustomMachine(model=self, states=User.states, initial=User.initial_state)
But your question was about how you can inject tag capability AFTER states have been created. Probably because it would need major refactoring and deep digging to alter state creation. Adding state.tags = ['your', 'tags', 'here'] is fine and should work out of the box for graph and markup creation. To get state.is_<tag> working you can alter its __class__ attribute:
from transitions import Machine, State
from transitions.extensions.states import Tags
# option b) patch __class__
states = []
state_initial = State("initial")
state_initial.text = "this is some text"
# we can pass tags for initialization
state_foo = State("foo")
state_foo.text = "bar!"
state_foo.tags = ['success']
states.append(state_initial)
states.append(state_foo)
# patch all states
for s in states:
s.__class__ = Tags
s.tags = []
# add tag to state_foo
states[1].tags.append('success')
class User:
states = states
initial_state = states[0]
def __init__(self):
self.machine = Machine(model=self,
states=User.states,
initial=User.initial_state)
user = User()
user.to_foo()
assert user.machine.get_state(user.state).is_success # works!
assert not user.machine.get_state(user.state).is_superhero # bummer...
But again, from my experience code becomes much more comprehensible and reusable when you strive to separate machine configuration from the rest of the code base. Patching states somewhere in the code and assigning custom paramters might be overlooked by the next guy working with your code and it surely is surprising when states change their class between two debugging breakpoints.
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
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
I am new to PySide. In my program, I encountered a problem that when I click one button, it triggers other button later added. Thanks!
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
def addContent(self):
'''
slot to add a row that include a lineedit, combobox, two buttons
'''
self.contentTabHBoxWdgt = QtGui.QWidget()
self.contentName = QtGui.QLineEdit('line edit')
self.conetentTypeBox = QtGui.QComboBox()
self.conetentTypeBox.addItem('elem1')
self.conetentTypeBox.addItem('elem2')
self.contentSave = QtGui.QPushButton('save',parent = self.contentTabHBoxWdgt)
self.contentSave.clicked.connect(self.contntSaveAct)
self.contentDelete = QtGui.QPushButton('delete',parent=self.contentTabHBoxWdgt)
self.contentDelete.clicked.connect(self.contntDel)
self.contentTabHBox = QtGui.QHBoxLayout()
self.contentTabHBox.addWidget(self.contentName)
self.contentTabHBox.addWidget(self.conetentTypeBox)
self.contentTabHBox.addWidget(self.contentSave)
self.contentTabHBox.addWidget(self.contentDelete)
self.contentTabHBoxWdgt.setLayout(self.contentTabHBox)
self.contentTabVBox.addWidget(self.contentTabHBoxWdgt)
def contntDel(self):
'''
slot to delete a row
'''
msgBox = QtGui.QMessageBox(QtGui.QMessageBox.Warning, '', 'Be sure to delete')
okBttn = msgBox.addButton('Yes', QtGui.QMessageBox.AcceptRole)
noBttn = msgBox.addButton('Cancel', QtGui.QMessageBox.RejectRole)
ret = msgBox.exec_()
if msgBox.clickedButton() == okBttn:
self.contentTabVBox.removeWidget(self.contentDelete.parentWidget());
When I Add one row and click its delete button, it does not work as expected.While I add two or three row , I click one delete button , it remove one row that is not the clicked delete button belong to. How could I achieve this function. Ths!
Your problem is because you aren't really taking advantage of object oriented programming properly.
All rows in your example call the same instance of the method contntDel. This method uses self.contentDelete which always contains a reference to the last row added.
What you need to do is separate out everything related to a row to a new class. When you add a row, create a new instance of this class and pass in the contentTabVBox. That way each row (or instance of the new class you will write) will have it's own delete method.
Without a complete code example, I can't provide a complete solution, but this should give you a rough idea:
class MyRow(object):
def __init__(self,contentTabVBox, rows):
self.contentTabVBox = contentTabVBox
self.my_list_of_rows = rows
self.addContent()
def addContent(self):
# The code for your existing addContent method here
def contntDel(self):
# code from your existing contntDel function here
# also add (if Ok button clicked):
self.my_list_of_rows.remove(self)
class MyExistingClass(??whatever you have here normally??):
def __init__(....):
self.addContentButton = QtGui.QPushButton('Add')
self.addContentButton.clicked.connect(self.addContent)
self.my_list_of_rows = []
def addContent(self):
my_new_row = MyRow(self.contentTabVBox,self.my_list_of_rows)
# You mustsave a reference to my_new_row in a list or else it will get garbage collected.
self.my_list_of_rows.append(my_new_row)
Hope that helps!