Related
I am writing a tool that allows me to track some tasks along a path of predifined stages, from something on a backlog, to ToDo, through WIP, Review and finally to done.
I created a custom widget, that will eventually be yellow, not unlike a postit note and perhaps with a bit of formatting it to give it a nice frame, etc... but stopped before getting far enough to make it look right because of this issue.
The idea is that each of these yellow Task widgets will have a stage they are at, and that I can select them in a Table Widget, and move them onto the next or previous stage, which will update taht objects stage, then refresh the TableWidget, read all the widget and where thay should be and set them in their new place.
So I have it kind of working to some degree (below), where I can move the tasks forward and they update location, but I noticed when I click the cells that the widget was previously in, print statement still says that the cell still has a widget there (which kind of makes sense, as code below isn't removing the previous one, but I'd expect to visually still see it). And I can move them forward and backwards, and the information on the tasks does update correctly, but the table won't refresh unless the task moves to a cell that never had a cellWidget in it. Test this by moving it backwards. It works, movnig forward visually does nothing, but moving again, does show up.
I tried clearing the TableWidget and rebuilding from scratch and that crashes. The main issue I am having is that with all these crashes, which is an issue in itself as it makes debugging very tough... When I try and clear the TableWidget (with .clear()) before repopulating, I get this.
Process finished with exit code -1073741819 (0xC0000005)
Same error code if I try removing the old cells by setting the Table Widget to 0 rows before adding the correct number of rows.
A known issue that is less important is when I select a cell without a widget and try and move it, gies me this, but don't worry too much about that fix, as it's known issue.
Process finished with exit code -1073740791 (0xC0000409)
Also tried cleaning up by iterating every cell and if it has a cell widget, remove cell widget before re-setting them to correct place and it still crashes. I'm out of ideas.
Task Widget
import sys
from PyQt5.QtWidgets import (QApplication, QTableWidget, QWidget, QFrame, QHBoxLayout, QLabel,
QPushButton,QVBoxLayout)
class Task(QWidget):
def __init__(self, ID, name, est):
super(Task, self).__init__()
# Creates a small widget that will be added to a table widget
self.ID = ID
self.name = name
self.est = est
# These cell widgets represent tasks. So each task has a particular 'stage' it is at
self.stage = 'ToDo'
self.stages = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
self.objects_labels = {}
self.initUI()
def initUI(self):
# adds a bunch of labels to the widget
layout = QVBoxLayout()
frame = QFrame()
frame.setFrameShape(QFrame.StyledPanel)
frame.setStyleSheet('background-color: red')
frame.setLineWidth(2)
layout.addWidget(frame)
info = [self.ID, self.name, self.est]
for section in info:
self.objects_labels[section] = QLabel(str(section))
layout.addWidget(self.objects_labels[section])
self.setLayout(layout)
self.setStyleSheet('background-color: yellow')
def task_move(self, forward = True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
print(self.stages)
print(index)
if forward:
print('--->')
if self.stage == self.stages[-1]:
print('Already at the end of process')
return
self.stage = self.stages[index + 1]
else:
print('<---')
if self.stage == self.stages[0]:
print('Already at the start of process')
return
self.stage = self.stages[index - 1]
MainWidget
class MainWidget(QWidget):
def __init__(self):
super().__init__()
self.tasks = self.make_tasks()
self.init_ui()
self.update_tw()
def make_tasks(self):
# Create a few tasks
a = Task(0, 'Name_A', 44)
b = Task(0, 'Name_B', 22)
c = Task(0, 'Name_C', 66)
d = Task(0, 'Name_D', 90)
return [a, b, c, d]
def init_ui(self):
layout_main = QVBoxLayout()
self.tw = QTableWidget()
self.tw.cellClicked.connect(self.cell_clicked)
self.tw.horizontalHeader().setDefaultSectionSize(120)
self.tw.verticalHeader().setDefaultSectionSize(120)
layout_main.addWidget(self.tw)
layout_bottom_button_bar = QHBoxLayout()
self.btn_task_backward = QPushButton('<--- Task')
self.btn_task_backward.clicked.connect(lambda: self.move_task(forward=False))
self.btn_task_forward = QPushButton('Task --->')
self.btn_task_forward.clicked.connect(lambda: self.move_task())
for widget in [self.btn_task_backward, self.btn_task_forward]:
layout_bottom_button_bar.addWidget(widget)
layout_main.addLayout(layout_bottom_button_bar)
self.setLayout(layout_main)
self.setGeometry(300, 300, 800, 600)
self.setWindowTitle('MainWidget')
self.show()
#property
def tw_header(self):
return {'Backlog': 0, 'ToDo': 1, 'WIP': 2, 'Review': 3, 'Done': 4}
#property
def selected_indices(self):
return [(x.row(), x.column()) for x in self.tw.selectedIndexes()]
#property
def selected_widgets(self):
selected_widgets = [self.tw.cellWidget(x[0], x[1]) for x in self.selected_indices]
print(selected_widgets)
return selected_widgets
def move_task(self, forward=True):
# Crashes if you select a non-widget cell, but thats a known issue
# Moves the task forward or backward and then prompts to update the TableWidget
for object in self.selected_widgets:
object.task_move(forward=forward)
self.tw.clearSelection()
self.update_tw()
def cell_clicked(self, row, column):
if self.tw.cellWidget(row, column):
print(self.selected_indices)
print(self.selected_widgets)
else:
print('No Cell Widget here')
def update_tw(self):
#I wanted to clear the Table widget and rebuild, but this crashes
# self.tw.clear()
self.tw.setHorizontalHeaderLabels(self.tw_header.keys())
rows = len(self.tasks)
columns = len(self.tw_header)
self.tw.setRowCount(rows)
self.tw.setColumnCount(columns)
# Looks through each task, and then gets it's stage, and then adds the widget to the correct column
for index, object in enumerate(self.tasks):
column = self.tw_header[object.stage]
print('Setting stage {} for {}\n...to r={}, c={}\n***'.format(object.stage, object, index, column))
self.tw.setCellWidget(index, column, object)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWidget()
sys.exit(app.exec_())
From my previous experience, I always found using setCellWidget clunky, underperforming and buggy.
Most of the times my Widgets were lost or misplaced, while refreshing the table similarly to the way you are doing it.
In addition, I guess you would want to use this "Task Mover" on a larger scale, and from what I could see, setting separate Widgets inside QWidgetItems becomes quite slow when done on loads of items.
My suggestion would be to use style delegates, so that you can customize the look of your items to your liking, without having to deal with the setCellWidget stuff which is giving you problem.
Once you have your own delegate, and paint the items the way you want, you can just keep updating that item data and moving the items around the table by using "take" and "set".
I am not sure if this would be the best way of executing this specific task, but moving towards this direction would probably give you greater flexibility and customisation power in the long run.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TaskProperty():
properties = ["ID", "name", "est", "stage"]
count = 4
ID, Name, Est, Stage = [Qt.UserRole + x for x in range(count)]
STAGES = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.tasks = self.make_tasks()
self.init_ui()
self.update_tw()
def make_tasks(self):
# Create a few tasks
a = Task(0, 'Name_A', 44)
b = Task(0, 'Name_B', 22)
c = Task(0, 'Name_C', 66)
d = Task(0, 'Name_D', 90)
return [a, b, c, d]
def init_ui(self):
layout_main = QVBoxLayout()
self.tw = QTableWidget()
# create and set the delegate to the TableWidget
self.delegate = TaskDelegate(self.tw )
self.tw.setItemDelegate(self.delegate)
self.tw.cellClicked.connect(self.cell_clicked)
self.tw.horizontalHeader().setDefaultSectionSize(120)
self.tw.verticalHeader().setDefaultSectionSize(120)
layout_main.addWidget(self.tw)
layout_bottom_button_bar = QHBoxLayout()
self.btn_task_backward = QPushButton('<--- Task')
self.btn_task_backward.clicked.connect(lambda: self.move_task(forward=False))
self.btn_task_forward = QPushButton('Task --->')
self.btn_task_forward.clicked.connect(lambda: self.move_task())
for widget in [self.btn_task_backward, self.btn_task_forward]:
layout_bottom_button_bar.addWidget(widget)
layout_main.addLayout(layout_bottom_button_bar)
self.setLayout(layout_main)
self.setGeometry(300, 300, 800, 600)
self.setWindowTitle('MainWidget')
self.show()
#property
def tw_header(self):
return {'Backlog': 0, 'ToDo': 1, 'WIP': 2, 'Review': 3, 'Done': 4}
#property
def selected_indices(self):
return [(x.row(), x.column()) for x in self.tw.selectedIndexes()]
def move_task(self, forward=True):
'''
To move the task to the next step, we iterate all the items selected.
If the task can be moved, we take the corresponding item from its current cell and move it to the destination.
:param forward:
:return:
'''
selected =self.tw.selectedItems()
for item in selected:
item.setSelected(False)
result = item.task_move(forward=forward)
if result:
next = 1 if forward else -1
row = item.row()
column = item.column()
moveItem = self.tw.takeItem(row, column)
self.tw.setItem(row, column + next, moveItem)
moveItem.setSelected(True)
def cell_clicked(self, row, column):
item = self.tw.item(row, column)
if not isinstance(item, TaskItem):
print "No Task Item Here"
def update_tw(self):
# I wanted to clear the Table widget and rebuild, but this crashes
# self.tw.clear()
self.tw.clear()
self.tw.setHorizontalHeaderLabels(self.tw_header.keys())
rows = len(self.tasks)
columns = len(self.tw_header)
self.tw.setRowCount(rows)
self.tw.setColumnCount(columns)
# Looks through each task, and then gets it's stage, and then adds the widget to the correct column
for row, object in enumerate(self.tasks):
# create items of our custom type only for the column that need to be filled.
# the other cells will be filled with null items.
column = STAGES.index(object.stage)
print('Setting stage {} for {}\n...to r={}, c={}\n***'.format(object.stage, object, row, column))
item = TaskItem(object)
self.tw.setItem(row, column, item)
class TaskDelegate(QStyledItemDelegate):
'''
This delegate take care of Drawing our cells the way we want it to be.
'''
def paint(self, painter, option, index):
'''
Override the Paint function to draw our own cell.
If the QTableWidgetItem does not have our Data stored in it, we do a default paint
:param painter:
:param option:
:param index:
:return:
'''
painter.save()
rect = option.rect
status = index.data(TaskProperty.Stage)
if status is None:
return super(TaskDelegate, self).paint(painter, option, index)
else:
id = STAGES.index(status)
pen = painter.pen()
pen.setBrush(Qt.black)
painter.setPen(pen)
if id == index.column():
rect.translate(3, 3)
newRect = QRect(rect.x(), rect.y(), rect.width() - 6, 20)
infos = [index.data(TaskProperty.ID), index.data(TaskProperty.Name), index.data(TaskProperty.Est)]
painter.setBrush(Qt.red)
painter.drawRect(newRect)
painter.setBrush(Qt.yellow)
for info in infos:
newRect.translate(0, 25)
painter.drawRect(newRect)
painter.drawText(newRect, Qt.AlignHCenter | Qt.AlignVCenter,
str(info))
class TaskItem(QTableWidgetItem):
'''
Subclass QTableWidgetItem.
Probably not needed, since we can set the property when we create the item instead of in the init,
and keep track of which item is attached to which task object using the Column Index of the table.
However, this can be useful if you want to attach more specific procedures to your items
'''
def __init__(self, task):
super(TaskItem, self).__init__()
self._task = task
self.setData(TaskProperty.ID, task.ID)
self.setData(TaskProperty.Name, task.name)
self.setData(TaskProperty.Est, task.est)
self.setData(TaskProperty.Stage, task.stage)
self.objects_labels = {}
def task_move(self, forward=True):
result = self._task.task_move(forward=forward)
self.setData(TaskProperty.Stage, self._task.stage)
return result
class Task(object):
'''
The Task class is now just an object, not a widget.
'''
def __init__(self, ID, name, est):
# Creates a small widget that will be added to a table widget
self.ID = ID
self.name = name
self.est = est
# These cell widgets represent tasks. So each task has a particular 'stage' it is at
self.stage = 'ToDo'
self.stages = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
self.objects_labels = {}
def task_move(self, forward=True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
if forward:
print('--->')
if self.stage == self.stages[-1]:
#print('Already at the end of process')
return False
self.stage = self.stages[index + 1]
else:
print('<---')
if self.stage == self.stages[0]:
#print('Already at the start of process')
return False
self.stage = self.stages[index - 1]
return True
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWidget()
sys.exit(app.exec_())
It is not necessary to clean and create everything again, instead just move the widget for it we must know if it can be moved or not and for that task_move must indicate if the movement is valid or not. Considering the above, the solution is:
def task_move(self, forward=True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
print(self.stages)
print(index)
if forward:
print("--->")
if self.stage == self.stages[-1]:
print("Already at the end of process")
return False
self.stage = self.stages[index + 1]
else:
print("<---")
if self.stage == self.stages[0]:
print("Already at the start of process")
return False
self.stage = self.stages[index - 1]
return True
def move_task(self, forward=True):
for row, column in self.selected_indices:
widget = self.tw.cellWidget(row, column)
if isinstance(widget, Task) and widget.task_move(forward):
next_column = column + (1 if forward else -1)
# create new task widget
task = Task(widget.ID, widget.name, widget.est)
# remove all task widget
self.tw.removeCellWidget(row, column)
# move task widget
self.tw.setCellWidget(row, next_column, task)
self.tw.clearSelection()
The crashed is because when using clear you are also removing the Task widget so "self.tasks" has objects deleted from C++ that you should not use.
I am doing a rigging tool and I want to assign a different color to the controllers for right and left using a slider. the idea is to select en item from the menu and get the color for that. but for now it is working just the first item and none the others.
My code is inside a class, at this point I have the window, the function to select a joint and a function to change the color. I am querying the selection for the option menu. This information is use by the three conditionals to set a color for each menu item. However, it works just the first option and none the other two. I tried to do the same code but without a class just defining the function and it works. What can I be doing wrong inside the class? I am kind of new in python and this is my first time doing a tool for ringing.
import maya.cmds as cmds
import maya.OpenMaya as om
import maya.mel as mel
import sys
import math
class rigCreator:
def __init__(self, *args):
#self.startFunction()
self.window = "uiWindow"
self.title = "Rigging Tool Bipeds"
self.winSize = (150, 200)
self.createUI()
def createUI(self, *args):
#check if window and prefs exist. If yes, delete
if cmds.window(self.window, ex=True):
cmds.deleteUI(self.window, wnd=True)
elif cmds.windowPref(self.window, ex=True):
cmds.windowPref(self.window, r=True)
self.window = cmds.window(self.window, t=self.title, wh = self.winSize, s=1, mnb=1, mxb=1)
self.mainForm = cmds.formLayout(nd=100)
self.tagLine= cmds.text(label = "Rig Tool")
cmds.frameLayout(label="1. Choose the root joint")
self.Layout = cmds.columnLayout(adj=1)
#Add a saparator to the window
cmds.separator()
# button to select the first joint
cmds.rowColumnLayout (nc = 2, cs=[(1,6), (2,6)])
rootBt = cmds.textField ('rootJnt', tx = 'First joint of your Arm chain', w = 250)
#cmds.textFieldButtonGrp('rootJnt', width=380, cal=(8, "center"), cw3=(100, 200, 75), h=40, pht="First joint of your Arm chain", l="First Joint", bl="Select", bc = lambda x: findJnt(), p=self.Layout)
cmds.button(l = 'Select', c = lambda x:self.findJnt())
cmds.setParent(self.Layout)
frame = cmds.frameLayout("2. Name options", lv=1, li=1, w=250)
#cmds.text(label="", align = "left")
cmds.rowColumnLayout(nc=4, cs=[(1,6), (2,6), (3,6), (4,6)])
#cmds.text('Side', l='Side:')
cmds.optionMenu('Part_Side', l='Side:', cc=lambda x:self.colorChange(), acc=1, ni=1)
cmds.menuItem(label='L_')
cmds.menuItem(label='R_')
cmds.menuItem(label='_')
cmds.text('Part', l='Part:')
cmds.optionMenu('part_Body')
cmds.menuItem(label='Arm')
cmds.menuItem(label='Leg')
cmds.menuItem(label='Spine')
cmds.setParent(self.Layout)
frame2 = cmds.frameLayout("3. Type of rig", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=3, cs=[(1,6), (2,6), (3,6)])
cmds.radioCollection("limb side")
cmds.radioButton(label='IK/FK', select=True)
cmds.radioButton(label='IK')
cmds.radioButton(label='FK')
cmds.setParent(self.Layout)
frame3 = cmds.frameLayout("4. Thick if you want to apply stretch", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=1, cs=[(1,6)])
cmds.checkBox( label='Stretchy limb', align='center' )
cmds.setParent(self.Layout)
cmds.setParent(self.Layout)
frame4 = cmds.frameLayout("5. Pick icon color", lv=True, li=1, w=250)
cmds.gridLayout(nr=1, nc=5, cwh=[62,20])
cmds.iconTextButton('darkBlue_Btn', bgc=[.000,.016,.373])
cmds.iconTextButton('lightBlue_Btn', bgc=[0,0,1])
cmds.iconTextButton('Brown_Btn', bgc=[.537,.278,.2])
cmds.iconTextButton('red_Btn', bgc=[1,0,0])
cmds.iconTextButton('Yellow_Btn', bgc=[1,1,0])
cmds.setParent(self.Layout)
colorBt = cmds.colorIndexSliderGrp('rigColor', w=250, h=50, cw2=(150,0), min=0, max=31, v=7)
#This button will creat the chain of joins with streatch and squach
cmds.button('b_create', label='Create', h=30, c='create()')
#show the window
cmds.showWindow(self.window)
def findJnt(self, *args):
self.root = cmds.ls(sl=True)
if len(self.root) == 1:
selRoot = self.root[0]
rootBt = cmds.textField ('rootJnt', e=1, tx=selRoot)
else:
cmds.warning ('Please select only the first joint!')
def colorChange(self, *args):
self.limbSide = cmds.optionMenu('Part_Side', q=1, sl=1)
if self.limbSide ==1:
self.sideColor = 7
if self.limbSide == 2:
self.sideColor = 14
if self.limbSide ==3:
self.sideColor = 18
colorBt = cmds.colorIndexSliderGrp('rigColor', e=1, v=self.sideColor)
def partBody(self, *args):
pass
def rigType(self, *args):
pass
rigCreator()
I did just clean the lambda and comma function and it was working as expected for me.
Because you don't seem to be familiar with maya ui/class, I've cleaned your whole script with some line comments beginning with DW :
So you can find how to manage maya ui more easily !
Don't hesitate to look at partial, I've lots of posts with some tips, hope it helps :
import maya.cmds as cmds
import maya.OpenMaya as om
import maya.mel as mel
import sys
import math
from functools import partial
def create(rigType, sideColor, isStretch, *args):
# DW : refreshed every time by property that is executing before being passed
if rigType == 0:
print('IK/FK mode')
elif rigType == 1:
print('IK mode')
elif rigType == 2:
print('FK mode')
# DW : in this state of the script, it will return always 7 because there is no refresh query
# so you can see to pass outside your class some arguments
# below an example for an alternative of property
print('color picked is {}'.format(sideColor))
# DW : in this example the function for getting the status is passed
# it is different from property because we still use to execute the command
if isStretch():
print('rig is stretchy')
else:
print('no stretch')
# Dw : note that if you create an instance of your window inside a variable :
# ui = rigCreator()
# ui.ikfkPick is something you can print at any moment to debug
# any variable with 'self' would be replaced by 'ui' in this case
class rigCreator:
# DW : default colour, self don't need to be specify but it is like so
# personally I put the important parameters or parameters that set default values on top
# it is easier when parsing the code few month later
sideColor = 7 #: DW : don't hesitate to do inline comment to say this is RED color (im followng google doctstring https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html)
isStretch = 0
ikfkPick = 0 #: pick IK/FK option, 1 for IK, 2 for FK
def __init__(self, *args):
#self.startFunction()
self.window = "uiWindow"
self.title = "Rigging Tool Bipeds"
self.winSize = (150, 200)
self.createUI()
def createUI(self):
# DW : only put *args if you are using maya command flags, otherwise no need
#check if window and prefs exist. If yes, delete
if cmds.window(self.window, ex=True):
cmds.deleteUI(self.window, wnd=True)
elif cmds.windowPref(self.window, ex=True):
cmds.windowPref(self.window, r=True)
self.window = cmds.window(self.window, t=self.title, wh = self.winSize, s=1, mnb=1, mxb=1)
self.mainForm = cmds.formLayout(nd=100)
self.tagLine= cmds.text(label = "Rig Tool")
cmds.frameLayout(label="1. Choose the root joint")
self.Layout = cmds.columnLayout(adj=1)
#Add a saparator to the window
cmds.separator()
# button to select the first joint
cmds.rowColumnLayout (nc = 2, cs=[(1,6), (2,6)])
self.tf_rootBt = cmds.textField ('rootJnt', tx = 'First joint of your Arm chain', w = 250)
#cmds.textFieldButtonGrp('rootJnt', width=380, cal=(8, "center"), cw3=(100, 200, 75), h=40, pht="First joint of your Arm chain", l="First Joint", bl="Select", bc = lambda x: findJnt(), p=self.Layout)
# DW : use lambda only to parse arguments, the better way to not make confusion is to use partial module
cmds.button(l = 'Select', c = self.findJnt)
cmds.setParent(self.Layout)
frame = cmds.frameLayout("2. Name options", lv=1, li=1, w=250)
#cmds.text(label="", align = "left")
cmds.rowColumnLayout(nc=4, cs=[(1,6), (2,6), (3,6), (4,6)])
#cmds.text('Side', l='Side:')
# DW : string naming can be dangerous, if another script create this type of name, it will conflict
# put your ctrl name into a variable or a class variable if you are using it somewhere else
# Same don't use lambda if you are not parsing arguments
self.om_partside = cmds.optionMenu('Part_Side', l='Side:', cc=self.colorChange, acc=1, ni=1)
cmds.menuItem(label='L_')
cmds.menuItem(label='R_')
cmds.menuItem(label='_')
cmds.text('Part', l='Part:')
cmds.optionMenu('part_Body')
cmds.menuItem(label='Arm')
cmds.menuItem(label='Leg')
cmds.menuItem(label='Spine')
cmds.setParent(self.Layout)
frame2 = cmds.frameLayout("3. Type of rig", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=3, cs=[(1,6), (2,6), (3,6)])
# DW :conforming to the default user settings on top of the class
# demonstrate property in class
self.rc_ikfk = cmds.radioCollection("limb side")
for x, lb in enumerate(['IK/FK', 'IK', 'FK']):
if x == self.ikfkPick:
defvalue = True
else:
defvalue = False
cmds.radioButton(label=lb, select=defvalue)
cmds.setParent(self.Layout)
frame3 = cmds.frameLayout("4. Thick if you want to apply stretch", lv=True, li=1, w=250)
cmds.rowColumnLayout(nc=1, cs=[(1,6)])
# DW : adding default value, class variable naming
self.ckb_stretch = cmds.checkBox( label='Stretchy limb', align='center', value=self.isStretch, cc=self.getStretch)
cmds.setParent(self.Layout)
cmds.setParent(self.Layout)
frame4 = cmds.frameLayout("5. Pick icon color", lv=True, li=1, w=250)
cmds.gridLayout(nr=1, nc=5, cwh=[62,20])
cmds.iconTextButton('darkBlue_Btn', bgc=[.000,.016,.373])
cmds.iconTextButton('lightBlue_Btn', bgc=[0,0,1])
cmds.iconTextButton('Brown_Btn', bgc=[.537,.278,.2])
cmds.iconTextButton('red_Btn', bgc=[1,0,0])
cmds.iconTextButton('Yellow_Btn', bgc=[1,1,0])
cmds.setParent(self.Layout)
self.sl_colorBt = cmds.colorIndexSliderGrp('rigColor', w=250, h=50, cw2=(150,0), min=0, max=31, v= self.sideColor)
#This button will creat the chain of joins with streatch and squach
# DW : never use comma for executing some function, if you use this script as a module afterward, you will have problems dealing with python namespacing
# maya is always passing a default argument True, so put *args in any command that is used by your ui controls
cmds.button('b_create', label='Create', h=30, c=partial(create, self.rigType, self.sideColor, self.getStretch))
#show the window
cmds.showWindow(self.window)
def findJnt(self, *args):
self.root = cmds.ls(sl=True, type='joint', l=True)
# DW : just becaue I was toying around the concept of root, sorry... ignor below
rootAbove = [i for i in self.root[0].split('|')[1:] if cmds.nodeType(i) == 'joint']
if rootAbove[0] != self.root[0].split('|')[-1]:
cmds.warning('you didn\'t choose the top joint !')
if len(self.root) == 1:
selRoot = self.root[0]
# DW : you dont have to store your edit, use class variable instead of string name
cmds.textField (self.tf_rootBt, e=1, tx=selRoot.split('|')[-1])
else:
cmds.warning ('Please select only the first joint!')
def colorChange(self, *args):
# DW : you don't need to put self on limbSide has you are not using it anywhere else
limbSide = cmds.optionMenu(self.om_partside, q=1, sl=1)
# DW : putting some elif
if limbSide == 1:
self.sideColor = 7
elif limbSide == 2:
self.sideColor = 14
elif limbSide ==3:
self.sideColor = 18
# DW : conforming editing controls
cmds.colorIndexSliderGrp(self.sl_colorBt, e=1, v=self.sideColor)
def partBody(self, *args):
pass
#property
def rigType(self):
'''Returns:
int: option selected in ui'''
# DW : demonstrating property usage and partial
rb = cmds.radioCollection(self.rc_ikfk, q=True, select=True)
cia = [i.split('|')[-1] for i in cmds.radioCollection(self.rc_ikfk, q=True, cia=True)]
return cia.index(rb)
def getStretch(self, *args):
"""function to get if the user need stretchy things
Note:
*args is just here to bypass maya default added True argument
Returns:
bool: use stretch or not
"""
# dw making some example function with some docstring
self.isStretch = cmds.checkBox(self.ckb_stretch, q=True, value=True)
return self.isStretch
rigCreator()
I have an existing project that I'm trying to build a GUI around (using PyGI + Gtk3). There are some native objects that I need to extend slightly to make them renderable. I've boiled the problem down to the simplified code here:
# Simplified Equivalent Code
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GdkPixbuf
# Pre-existing, complex object
class Move(object):
def __init__(self, color):
self.color = color
# Pre-existing, complex object
class Block(object):
def __init__(self,move=None,**kwds):
self.move = move
# New object created to help render a Block
class BlockGui(Block):
pixbufs = {
'empty' : GdkPixbuf.Pixbuf.new_from_file('block_empty.png'),
'red' : GdkPixbuf.Pixbuf.new_from_file('block_red.png'),
'blue' : GdkPixbuf.Pixbuf.new_from_file('block_blue.png'),
}
def __setattr__(self, name, value):
super(BlockGui, self).__setattr__(name, value)
if name == 'move':
print "Need to emit a signal here"
def get_pixbuf(self):
try:
return BlockGui.pixbufs[self.move.color]
except AttributeError:
return BlockGui.pixbufs['empty']
class BlockRenderer(Gtk.CellRendererPixbuf):
__gproperties__ = {
'block' : (GObject.TYPE_PYOBJECT,
'block to render',
'the block object to be rendered',
GObject.PARAM_READWRITE)
}
def __init__(self):
GObject.GObject.__init__(self)
self.block = None
def do_set_property(self, prop, value):
# What is a GParamBoxed? Should I be checking if prop == 'block' from it somehow?
if isinstance(value, BlockGui):
self.block = value
self.set_property('pixbuf', self.block.get_pixbuf())
GObject.type_register(BlockRenderer)
def destroy(widget, data=None):
Gtk.main_quit()
# Normally do not have access to this assignment
def on_clicked(widget, liststore, treeview):
treeiter = liststore.get_iter(2)
block = liststore.get_value(treeiter, 1)
block.move = Move('red')
def main():
# 3x5 so this demo window has some size
fmt = [GObject.TYPE_PYOBJECT] * 3
liststore = Gtk.ListStore(*fmt)
for r in xrange(5):
liststore.append([BlockGui() for x in xrange(3)])
treeview = Gtk.TreeView(liststore)
for c in xrange(3):
col = Gtk.TreeViewColumn(str(c))
treeview.append_column(col)
cell = BlockRenderer()
col.pack_start(cell, True)
col.add_attribute(cell, 'block', c)
button = Gtk.Button("Change Color!")
button.connect('clicked', on_clicked, liststore, treeview)
vbox = Gtk.VBox()
vbox.add(treeview)
vbox.add(button)
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.connect('destroy', destroy)
window.add(vbox)
window.show_all()
Gtk.main()
if __name__ == '__main__':
main()
When the current code is run, clicking the button yields no immediate result, but running the mouse over the changed row will cause the center square to turn red (as the hover over the row triggers a refresh). Normally, when a 'proper' GObject has a set_attribute called, it will emit some signals to notify the widgets containing it to re-render.
I need to know which signal that emits, to whom it's emitted, and how to emulate that behavior.
If you know the widget that must be redrawn, then you can just call queue_draw(), queue_draw_region() or queue_draw_area() for that widget. That will invalidate that window area and it will be redrawn. If you want more fine grained control, you might want to use Gtk.DrawingArea.
You might want to check the documentation for The GTK+ Drawing Model.
I am making a Qt GUI with python and i am getting the error: QObject::startTimer: timers cannot be started from another thread. It occurs when I run the readModemSnap method. I've been working on this for almost a week trying many different design patterns for threading in Qt that i've found on the web but nothign works.
class ModemScopeWindow(QMainWindow, Ui_ModemScope):
def __init__(self, parent=None):
super(ModemScopeWindow, self).__init__(parent)
# Set up the user interface from Designer.
self.setupUi(self)
self.thread = MainThread()
"""
signal connections
"""
self.thread.newSnap.connect(self.updateScene)
self.thread.updateStatus.connect(self.setStatus)
self.thread.connectionLock.lock()
self.thread.runLock.lock()
self.connect(self.runButton, SIGNAL("clicked()"), self.thread.runLock.unlock, Qt.QueuedConnection)
self.connect(self.connectButton, SIGNAL("clicked()"), self.thread.connectionLock.unlock, Qt.QueuedConnection)
class MainThread(QThread):
newSnap = pyqtSignal(QGraphicsScene)
updateStatus = pyqtSignal(str)
initConnect = pyqtSignal()
def __init__(self, parent = None):
super(MainThread, self).__init__(parent)
self.samples = []
self.connectionLock = QMutex()
self.runLock = QMutex()
self.cliMute = QMutex()
self._displayCrosshairs = True
self._displayGrid = True
self.persistantMode = False
self.sampleDepth = 1
self._currentHaam = "4"
color = QColor(10,255,71)
self.plotPen = QPen(color)
self._leftXscene = -VIEW_SIZE/2
self._topYscene = -VIEW_SIZE/2
self._rightXscene = VIEW_SIZE/2
self._bottomYscene = VIEW_SIZE/2
self._leftXworld = -10.0
self._topYworld = 10.0
self._rightXworld = 10.0
self._bottomYworld = -10.0
self._scene = QGraphicsScene(self._leftXscene, self._topYscene, VIEW_SIZE, VIEW_SIZE, self)
self.start(QThread.HighestPriority)
def run(self):
self.updateStatus.emit("Enter target IP address and press Connect")
self.connectionLock.lock()
self.connectModem()
while(1):
self.runLock.lock()
#compile scene
self.readModemSnap()
self.newSnap.emit(self._scene)
self.runLock.unlock()
def readModemSnap(self):
self.updateStatus.emit("Reading Modem Snap...")
print len(self.samples)
if len(self.samples) >= self.sampleDepth:# and not self.persistantMode:
self.samples.pop(0)
self.cliMute.lock()
temp = cli.getModemSnap()
self.cliMute.unlock()
self.samples.append(temp)
self.cliMute.lock()
modType = cli.modemRead(80)
self.cliMute.unlock()
if((modType | 0x0FFFFFFF) == 0x0FFFFFFF):
modType = "0";
else:
modType = "%x"%modType
modType = str(modType)
modType = "0"
self.updateStatus.emit("Done")
self.refresh()
self._currentHaam = modType[0]
if self._displayGrid:
self.plotModulation(self._currentHaam)
self.handleSnapshotResponse()
self.updateStatus.emit("Ready to Run")
def refresh(self):
#delete scene
items = self._scene.items()
for x in items:
self._scene.removeItem(x)
#repaint the crosshairs
if self._displayCrosshairs:
self.plotLine(-VIEW_SIZE,0,+VIEW_SIZE,0, self.plotPen)
self.plotLine(0, -VIEW_SIZE,0, +VIEW_SIZE, self.plotPen)
self.plotScaleTicks()
#repaint grid
if self._displayGrid:
self.plotModulation(self._currentHaam)
self.newSnap.emit(self._scene)
def handleSnapshotResponse(self):
for x in range(len(self.samples)):
for sample in self.samples[x]:
upper = (sample >> 16) & 0xffff;
lower = sample & 0xffff
if (upper & 0x8000):
upper -= 0x10000
if (lower & 0x8000):
lower -= 0x10000
upper = float(upper)/128.0
lower = float(lower)/128.0
self.plot(upper, lower)
as you can see Im not starting any thread from another thread. i use the main to start the UI which creates a MainThread that starts itself upon construction. When i commented lines out to localize the problem I found that its when i call self.refresh() and self.handleSnapshotResponse() in the readModemSnap method. Can anyone point me in the direction of what im doing wrong? or any tutorials on QThreading? thanks in advance
This is the rule: you cannot call any GUI functions from any thread other than the main thread running the Qt event loop. When you see errors about QTimer, it's probably because something in the GUI uses a timer internally and it is being triggered from another thread.
The most likely culprit in your case is that you are operating on a QGraphicsScene from the worker thread. I would try rearranging such that the code in MainThread.reload is called in response to the newSnap signal, rather than before it.
I'm trying to remap several navigation keys:
ENTER: to work like standard TAB behavior (focus to next control)
SHIFT+ENTER: to work like SHIFT+TAB behavior (focus to previous control)
UP / DOWN arrows: previous /next control
etc
I tried with a couple of options but without luck:
from javax.swing import *
from java.awt import *
class JTextFieldX(JTextField):
def __init__(self, *args):
# Thanks, Jack!!
JTextField.__init__(
self,
focusGained=self.onGotFocus,
focusLost=self.onLostFocus,
*args)
def onGotFocus (self, event):
print "onGotFocus "
self.selectionStart = 0
self.selectionEnd = len(self.text)
def onLostFocus (self, event):
print "onLostFocus ", self.name
class Test(JFrame):
def __init__(self):
JFrame.__init__(self,
'JDesktopPane and JInternalFrame Demo',
size=(600, 300),
defaultCloseOperation=JFrame.EXIT_ON_CLOSE)
self.desktop = JDesktopPane()
self.contentPane.add(JScrollPane(self.desktop)) # This is the same as self.getContentPane().add(...)
frame = JInternalFrame("Frame", 1, 1, 1, 1, size=(400, 400), visible=1)
panel = JPanel()
self.label = JLabel('Hello from Jython')
panel.add(self.label)
self.textfield1 = JTextFieldX('Type something here', 15)
panel.add(self.textfield1)
self.textfield2 = JTextFieldX('and click Copy', 15)
panel.add(self.textfield2)
panel.add(copyButton)
frame.add(panel)
frame.pack()
self.desktop.add(frame)
# ENTER=SPACE remapping for buttons (works ok, but only for buttons)
# inputMap = UIManager.getDefaults().get("Button.focusInputMap")
# pressedAction = inputMap.get(KeyStroke.getKeyStroke("pressed SPACE"));
# releasedAction = inputMap.get(KeyStroke.getKeyStroke("released SPACE"));
# # pressedAction = self.noAction
# inputMap.put (KeyStroke.getKeyStroke("pressed ENTER"), pressedAction)
# inputMap.put (KeyStroke.getKeyStroke("released ENTER"), releasedAction)
# # Attemp to remap ENTER=TAB for TextFields (didn't work, no errors)
# inputMap = UIManager.getDefaults().get("TextField.focusInputMap")
# pressedAction = inputMap.get(KeyStroke.getKeyStroke("pressed TAB"));
# releasedAction = inputMap.get(KeyStroke.getKeyStroke("released TAB"));
# inputMap.put (KeyStroke.getKeyStroke("pressed W"), pressedAction)
# inputMap.put (KeyStroke.getKeyStroke("released W"), releasedAction)
# # Attemp to remap ENTER=TAB for all controls (didn't work, no errors)
# spaceMap = self.textfield1.getInputMap().get(KeyStroke.getKeyStroke(event.KeyEvent.VK_TAB, 0, True));
# self.textfield1.getInputMap().put(KeyStroke.getKeyStroke(event.KeyEvent.VK_ENTER, 0, True),spaceMap);
frame.setSelected(1)
frame.moveToFront()
def noAction (self, event):
print "noAction"
pass
if __name__ == '__main__':
test = Test()
test.setLocation(100, 100)
test.show()
I made a new post for readability.
self.textfield1 = JTextField('Type something here',15,focusGained=self.myOnFocus,keyPressed=self.myOnKey)
#create textfield2...must be created before can be referenced below.
self.textfield1.setNextFocusableComponent(self.textfield2)
then in your event handler:
def myOnKey(self,event):
print str(event) # see all other info you can get.
key_code = event.keyCode
if key_code == 10:
print "you pressed enter"
# simulate the "tab" just focus next textbox...
gotFocus = event.getComponent()
nextToFocus = gotFocus.nextFocusableComponent
nextToFocus.requestFocus()
Should do it.
Finally used part of Jack's answer (the keyPressed event) but without manually setting setNextFocusableComponent:
keyFocusMgr = KeyboardFocusManager.getCurrentKeyboardFocusManager()
keyFocusMgr.focusNextComponent()
Add keyPressed to the swing competent that you want to listen for the key press on
self.textfield1 = JTextField('Type something here',15,focusGained=self.myOnFocus,keyPressed=self.myOnKey)
myOnKey can be named anything in that method do something like:
def myOnKey(self,event):
print str(event) # see all other info you can get.
key_code = event.keyCode
if key_code == 10:
print "you pressed enter"
# simulate the "tab" by just focusing the next textbox...
Then you should just be able to play around with the print str(event) command to get all the proper keycodes that you want.