Maya UI Python change NumberOfCollums dynamically - python

I want to change (kind of dynamically) the NumberOfColumns of a rowColumnLayout inside a Maya Window. Depending on the value given by 'SLiBThumbSizeComboBox' i want to change the count for the columns.
Here is the problem: when running my 2 functions inside the Script Editor everything is working fine. I execute the first one - the ScrollLayout is created. I execute the second and the rowColumnLayout is created with the right count of columns.
But when I try run it only by the first function, meaning the second function is called at the end of the first one - it's not working anymore?! I get the error message, that NumberOfCollumns has to be greater than 0 ?!
def FunctionA():
if cmds.scrollLayout('SLiBScrollLayout', query = True, exists = True):
cmds.deleteUI('SLiBScrollLayout', layout = True)
cmds.scrollLayout('SLiBScrollLayout', p="SLiB_thumbsframe")
def FunctionB():
iconLayout = cmds.optionMenu('SLiBThumbSizeComboBox', query = True, value = True)
iconSize = iconLayout.split('x')
iconDiv = int(iconSize[0])
n = int(cmds.scrollLayout("SLiBScrollLayout", query=True, saw=1)/iconDiv)
cmds.rowColumnLayout('Icons', numberOfColumns=n, p="SLiBScrollLayout")
Thanks in advance
Daniel

I'm not surprised the rowColumnlayout complains if you give it zero columns: you'll always need 1. Depending on how the are setting up the gui , your query on saw might return 0, which would explain your problem.
Here's a very basic example of what it looks like your trying to do:
w = cmds.window()
c = cmds.columnLayout(adj=True)
v_slider = cmds.intSlider(min = 1, max =10, value=2)
h_slider = cmds.intSlider(min = 1, max =10, value=2)
scroll = cmds.scrollLayout()
form = cmds.formLayout()
def update_row(*_):
# get the row and column counts
rows = cmds.intSlider(v_slider, q=True, v= True)
columns = cmds.intSlider(h_slider, q=True, v= True)
# delete the old layout and rebuild.
# the 'or []` below lets you loop even if there are no children....
for n in cmds.formLayout(form, q=True, ca=True) or []:
cmds.deleteUI(n)
cmds.setParent(form)
# make a new rowColumn
new_row = cmds.rowColumnLayout(nc = columns)
for n in range(rows * columns):
cmds.button(label="button_%i" % n)
cmds.formLayout(form, e=True, af = [(new_row,'top',0), (new_row, 'bottom', 0 ), (new_row, 'left', 0 ), (new_row, 'right', 0)])
# both sliders call the update function
cmds.intSlider(h_slider, e=True, cc =update_row)
cmds.intSlider(v_slider, e=True, cc =update_row)
update_row() # do it once to get started
cmds.showWindow(w)
The key here is the order in which it's declared: the function knows what v_slider, h_slider and form are because it's declared after them and doesn't need to do extra work to find them (you can get a more controlled version of the same effect with a class. You'll notice I don't use the names at all: names are unreliable if there's an old layout lying around, it's simpler to use variables.
I've also set the minimum values for the row and columns to 1 so there's no zero division problem.

Related

Writing to databases on Python

I have been trying to write to a database and am having trouble setting data using two different classes in the one function.
Firstly, all values are being passed through a GUI and in this case, only the following entries were passed: 'Categories' = C, 'Usage' = Charter, 'DispTHR' = 5000.
Here you can see that I have two classes I am wanting to access (airport and runway) where the function set_opsdatabase_details() will go to the appropriate class and write to our database. This is all well and good when the airport and runway are separate from each other; however, when integrating them in the same function I can't seem to get the corresponding airportvalueDict = {} and runwayvalueDict = {] to display the values I want. Could someone help me understand how to write the correct entry box values into the corresponding valueDict dictionaries?
Thank you in advance! (a screenshot of the output from the print statements is attached)
Function in python
Output of function with the print statements
Example of code in text format:
#edit function for first part of operational window
def edit_details1(self):
airport = self.manager.get_chosen_airport()
runway = airport.get_chosen_runway()
airportlabels = ['Category', 'Usage', 'Curfew',
'Surveyor', 'LatestSurvey',
'FuelAvail', 'FuelPrice', 'TankerPort', 'RFF']
runwaylabels = ['DispTHR', 'VASI', 'TCH']
airportvalueDict = {}
runwayvalueDict = {}
print(self.entryDict['Category']["state"])
if self.entryDict['Category']["state"] == 'disabled':
for entry in self.entryDict.values():
entry.config(state=tk.NORMAL)
self.editButton.config(text="Confirm")
elif self.entryDict['Category']['state'] == 'normal':
airport = self.manager.get_chosen_airport()
runway = airport.get_chosen_runway()
values = [x.get() for x in self.varDict.values()]
for label, value in zip(airportlabels, values):
airportvalueDict[label] = value
airport.set_opsdatabase_details(airportvalueDict)
print(f'airport: {airportvalueDict}')
for label, value in zip(runwaylabels, values):
runwayvalueDict[label] = value
runway.set_opsdatabase_details(runwayvalueDict)
print(f'runway: {runwayvalueDict}')
for entry in self.entryDict.values():
entry.config(state=tk.DISABLED)
self.editButton.config(text="Edit...")

Function works until called with an ipywidgets event

I have been working on this for who knows how long but I have two functions: one that is activated through ipywidgets special event and the next that is called within the first function.
function number one:
def lipas_button_clicked(b):
'''
Def used to select and fetch dataframe from LIPAS
------------------------------------------------------------
PARAMETERS
- b <button default>
RETURNS
- Automatic append of selected YKR IDs <list element> (global variable)
- Message in display
'''
code = str(select_lipas.value)
lipas = GetLipasData(code)
return print(lipas)
function number two:
def GetLipasData(typecode):
code_list = [typecode]
lipas = pd.DataFrame()
for x in code_list:
typecode = x
r = requests.get("""http://lipas.cc.jyu.fi/api/sports-places?fields=type.name&fields=location.sportsPlaces&fields=location.geometries&fields=name&fields=type.typeCode&fields=location.locationId&fields=location.city.name&fields=location.postalCode&fields=location.address&typeCodes=""" + typecode + """&cityCodes=91""").json()
df = pd.json_normalize(r, record_path=['location', 'geometries', 'features'], meta=[['location', 'sportsPlaces'], ['location', 'address'], ['location', 'postalCode'], ['name']])
df['typeCode'] = typecode
lipas = lipas.append(df)
return lipas
Whenever I run this code using the widgets button the first function returns an empty dataframe to me. However, when I call the second function like this:
# example typecode
temp = '1120'
lipas = GetLipasData(temp)
print(lipas)
The function works and I end up with the desired dataframe. Here is a snippet of the dataframe:
type geometry.type geometry.coordinates \
0 Feature Point [25.0014104043243, 60.1858473428042]
1 Feature Point [25.0191651714749, 60.2636222119604]
2 Feature Point [25.0908944598678, 60.2191648730633]
3 Feature Point [24.9503407234607, 60.1634158059106]
4 Feature Point [24.9437999467401, 60.162526333009]
properties.pointId location.sportsPlaces location.address \
0 577095 [603807] Ståhlbergintie 2
1 577194 [520138] Rajatie 7
2 577098 [85737] Kukkaniityntie 2
3 73310 [82865] Unioninkatu 2
4 577037 [603746] Ratakatu 6
location.postalCode name typeCode
0 00570 Brändö lågstadieskolan / Lähiliikuntapaikka 1120
1 00730 Hiidenkiven peruskoulu / Lähiliikuntapaikka 1120
2 00900 Botby grundskola / Lähiliikuntapaikka 1120
3 00130 Grundskolan Norsen / Lähiliikuntapaikka 1120
4 00120 Helsingin normaalilyseo / Lähiliikuntapaikka 1120
I cannot for the life of me understand why calling the same function through the ipywidgets event does not work. Here is the code for the ipywidgets.
# adding the Select widget. Select means that you can select the correct one.
select_lipas = widgets.Select(placeholder='Valitse palvelu(t)',
options=lipas_options,
layout = {'width':'max-content'},
ensure_option=True,
disabled=False)
# adding the button widget. Click Me Tooltip is an action which run after clicking on the button.
b = widgets.Button(description='Valitse LIPAS palvelu',
disabled=False,
button_style='info',
tooltip='Click me',
icon='check')
# adding the click event to the button. To add event you do it as a function
b.on_click(lipas_button_clicked)
Any ideas? Please let me know if more information is required. Much appreciated for any help in advance!
UPDATE:
I was using AppLayout() to display my widgets and for whatever reason this was not sending the selected code back to my functions. I tested it out by switching back to displaying each widget separately and it worked immediately. Any ideas on the difference?
This is a typical problem with on_click.
You are returning an object at the end of the function, but it is not possible to assign it to a variable in the main namespace. You need the lipas_button_clicked function to have a side effect, not return a value.
One way around this is to create a global variable, and within your lipas_button_clicked function, instead of returning the data from your GetLipasData function, assign it to that global variable. It will then be accessible from outside your lipas_button_clicked function.

Creating a Hierachy in Maya

I've written some code for a tool that duplicates and moves controllers/objects to joints. Its really just code to copy and move one object to a list of other objects. Its very basic but it does work.
The pasted code is a snippet that I've taken out from the rest but will work if run in Maya.
This code in particular will create a nurbsCurve, put it inside a group, then move that group to the first object on the list. I then used a loop to do it again, but then parent the group underneath the nurbsCurve from the previous group to create a parent-hierarchy all the way down.
import maya.cmds as cmds
def setZero(target):
cmds.setAttr("%s.translateX" % target, 0)
cmds.setAttr("%s.translateY" % target, 0)
cmds.setAttr("%s.translateZ" % target, 0)
cmds.setAttr("%s.rotateX" % target, 0)
cmds.setAttr("%s.rotateY" % target, 0)
cmds.setAttr("%s.rotateZ" % target, 0)
selJoint = cmds.ls(selection = True, shortNames = True)
firstCtrl = cmds.circle(normal =( 1,0,0))
firstGrp = cmds.group(firstCtrl)
cmds.parent(firstGrp,selJoint[0])
setZero(firstGrp)
cmds.parent(firstGrp, world = True)
#Use loop for the rest of the joints
for joint in selJoint:
#Skip the first joint since its already done
if joint == selJoint[0]:
continue
circleCtrl = cmds.circle(normal =( 1,0,0))
offsetGrp = cmds.group(circleCtrl)
cmds.parent(offsetGrp, joint)
setZero(offsetGrp)
cmds.parent(offsetGrp, world = True)
cmds.parent(offsetGrp, firstCtrl) #Parent new offset Group to the old controller
firstCtrl = circleCtrl #The new controller is now the target for the next offset/ctrl to be parented under
It works as intended but I get this warning:
Warning: Cannot parent components or objects in the underworld
I've tried looking this issue up, but I haven't found anything on how to fix the problem. But I do know that the "underworld" is another space in Maya, and that it can cause issues down the line.
The warning is being thrown at the end on this line cmds.parent(offsetGrp, firstCtrl), and if you print out firstCtrl it'll output something like this:
[u'nurbsCircle1', u'makeNurbCircle1']
So what it's doing is it's trying to parent firstCtrl and 'nurbsCircle1' to 'makeNurbCircle1'. Since 'makeNurbCircle1' is a node with no transform, it throws that warning, because obviously you can't parent to something with no transform.
Luckily it's very easy to fix. Now that we know that it's a list, we just parent to the first index of it like so: cmds.parent(offsetGrp, firstCtrl[0])
If you want more info on this same warning then you can also check out this question.

Getting variables and calling functions with UI. Maya python

I am currently working on a little script that creates a crane-like rig automatically in Autodesk Maya, the user gets to choose the amount of joints by a UI.
My question is how do I take the integer input of the user and use it as the variable value for my "jointAmount"?
I am also wondering how I would be able to call my function(AutoCraneRig) to actually run the script from the UI. I have a "apply"-button but I am unsure how to connect it to my function.
I have seen similar posts like mine but I feel that the solutions shown are somewhat hard for me to understand and/or I can't really relate what is shown to my own problem.
If anything is unclear or more information is needed from me please don't hesitate to call me out.
Here is what my current UI look like
import maya.cmds as cmds
import pymel.core as pm
def jntctrl():
number = pm.intField(jnt, q=1, v=1)
print(number)
if pm.window("stuff", exists = True):
pm.deleteUI("stuff")
pm.window("stuff", t = "Crane Rig Generator", w=400, h=200)
pm.columnLayout(adj = True)
pm.text(label="Joint Amount:")
jnt = pm.intField(changeCommand = 'jntctrl()')
pm.button(label="Create Crane")
pm.showWindow()
#Defining how many joints the user want to have for their crane rig
jointAmmount = 5
#Defining how many controllers the user want to have to orient the crane.
#May not exceed the joint amount
controllerAmount = 5
def autoCraneRig():
#Creating the joints
for i in range(jointAmmount):
pm.joint()
pm.move(0, i, 0)
#Creating the controllers
for i in range(controllerAmount):
pm.circle()
pm.rotate (0,90,0)
pm.makeIdentity (apply= True)
#Creating the groups
for i in range(controllerAmount):
pm.group()
#Somehow one of the nurbs get parented to a group when running the script, here i select both the groups and then unparent them.
pm.select("group*", "nurbsCircle*")
pm.parent(world = True)
#Creating lists/dictionaries for the groups
#Since I wanted to parent my objects by their number I had to put all objects in lists/dictionries to get access.
groups = pm.ls('group*')
nbs = [int(n.split('group')[-1]) for n in groups]
groupDic = dict(zip(nbs, groups))
#Create a list/dictionary for the joints
joint = pm.ls('joint*', type='joint')
nbs = [int(n.split('joint')[-1]) for n in joint]
jointDic = dict(zip(nbs, joint))
common = list(set(groupDic.keys())&set(jointDic.keys()))
#Parenting the groups to the joints
for i in common:
pm.parent(groupDic[i], jointDic[i])
#Reseting the transformations of the groups and then unparenting them to still have the transformation data of the joints
pm.select("group*")
pm.makeIdentity()
pm.parent(world = True)
#Creating a list/dictionary for the nurbs aswell that will be parented to the groups in numeric order
nurbs_sh = pm.ls('nurbsCircle*', type='nurbsCurve')
#I had to get the transformation information from the nurbs before parenting them with anything would work(took a long time to get it right).
nurbs_tr = pm.listRelatives(nurbs_sh, p=1)
nbs = [int(n.split('nurbsCircle')[-1]) for n in nurbs_tr]
curveDic = dict(zip(nbs, nurbs_tr))
common = list(set(groupDic.keys())&set(curveDic.keys()))
#Parent the nurbs to the groups
for i in common:
pm.parent(curveDic[i], groupDic[i])
#Select the nurbs and reset transformations and then freeze transform
pm.select("nurbsCircle*")
pm.makeIdentity()
#Orient constrain the controllers/nurbs to the joints
for i in common:
pm.orientConstraint(curveDic[i], jointDic[i])
#Parent the 2nd group with the first controller. Do this for the whole hierarchy.
for i in common:
pm.parent(groupDic[i+1], curveDic[i])
#I'm getting keyError after I put the "+1" in my groupDic and I don't know why, although it still works, I guess.
autoCraneRig()
Here's an example for how to call a specific function/command when a button is clicked, and how to get the value of an int field. The key is in naming the fields, so you can reference the UI control later.
import pymel.core as pm
def ui():
if (pm.window("myWindow", exists=True)):
pm.deleteUI("myWindow")
window = pm.window("myWindow", t="My Window", w=400, h=200)
pm.columnLayout(adj=True)
pm.intField("myIntField")
pm.button("Button", aop=True, command="action()")
pm.showWindow(window)
def action():
print("Button clicked!")
value = pm.intField("myIntField", q=True, v=True)
print(value)
ui()
If you want to get more into making UI's, I would recommend you watch these two videos:
PySide UI Creation in Maya: Video One
PySide UI Creation in Maya: Video Two

How to make this code less repetitive? (openpyxl)

I'm a teacher and I'm making a program to facilitate myself to catalog my students' grades. This isn't so much of a problem, the deal is, I'm doing it mostly to practice programming.
# Name the first line
sheet['A1'] = 'Index'
sheet['B1'] = 'Name'
sheet['C1'] = 'Grade'
# Changes the style of the first line to bold
sheet['A1'].font = font_bold
sheet['B1'].font = font_bold
sheet['C1'].font = font_bold
# Widens the columns
sheet.column_dimensions['A'].width = 10
sheet.column_dimensions['B'].width = 30
sheet.column_dimensions['C'].width = 30
# Aligns to center
sheet.cell('A1').alignment = Alignment(horizontal='center', vertical='center')
sheet.cell('B1').alignment = Alignment(horizontal='center', vertical='center')
sheet.cell('C1').alignment = Alignment(horizontal='center', vertical='center')
# Freeze the first row
sheet.freeze_panes = 'A2'
# Index number of the lines
i = 2
--
--
# function to calculate the grade
def grade():
As you may notice, it is all, to some extent, repetitive. The code functions exactly as I want it to, but I would like to know some other way to make it more... succint.
It is important to remember the variables reach, because up next a function will start, and, soon after, a While loop.
The irrelevant parts of the code for this question have been omitted with a ---. If they are somehow needed I'll edit them in, but to my knowledge, they are not.
Thank you in advance.
Objects and DRY (Don't Repeat Yourself) Principle
In general, whenever you have some parallel arrays of the same length that keep some related attribute, it is a good indication that you can combine those attributes and make an object out of them.
For your case, I see that suggest to define an object Sheet with the following attributes:
title
font
column_dimensions
cell
...
I'd take the whole thing and break it down into functions.
def name_column(cell, name):
sheet[cell] = name
def style_name_column(cell, style):
sheet[cell].font = style
def change_width(column, width):
sheet.column_dimensions[column].width = width
def align_column(cell):
sheet.cell(cell).alignment = Alignment(horizontal='center', vertical='center')
then use some sort of data structure to loop over and do this stuff.
indexes_and_names = [['A1','Index']
['B1','Name' ]
['C1','Grade']]
for item in indexes_and_names:
name_column(item[0], item[1])
and then repeat for the other functions, or use a bigger data structure, like Jack's dictionary.
Your code will be readable, and easily maintainable.
Here's a good start:
items = [
{"sheet": "A1", "column": "A", "column_width": 10, "title": "Index"},
{"sheet": "B1", "column": "B", "column_width": 30, "title": "Name"},
{"sheet": "C1", "column": "C", "column_width": 30} "title": "Grade"},
]
for item in items:
sheet[item["sheet"]] = item["title"]
sheet[item["sheet"]].font = font_bold # always bold
sheet.column_dimensions[item["column"]] = item["column_width"] # shared value
sheet.cell(item["sheet"]).alignment = Alignment(horizontal='center', vertical='center') # shared value
openpyxl provides all the necessary functions to do this quickly and easily. The best way to do this is to use named styles. You can create a single style for the header cells and apply it using a loop.
Create the first row:
ws.append(['Index', 'Name', 'Grade'])
Create the relevant style:
header = NamedStyle(…)
Apply the style to the first row:
for cell in ws[1]:
cell.style = header

Categories

Resources