Website Youtube GitHub

mGear Framework Forum

Sharing a couple of functions i wrote for anim picker

i wanted a bit more control over ui button placement and handles, scaleing is broken in 2020 so was having to do a lot with handles and it was awkward getting things to line up.
i wanted some snapping and the ability to undo. so i started thinking nurbs curves?
so i wrote a couple of functions for getting button as curves, figured it might be useful for others too.

the first function gets the data dictionary from the ui and using the data build a nurbs curve for each button, these curves can then be edited as normal, shapes nodes can be swapped and the the second function over writes the picker data node in the scene, cvs are baked relative to pivots so scale and rotate data on the transform is applied.
i am pretty new to this code so i think i captured all the data and re-apply it on the other end but i may have missed some stuff.
this has only been tested a little on maya 2020.2

import pymel.core as pm
from PySide2 import QtGui
import mgear.core.attribute as attr
import mgear.anim_picker
from mgear.core import pyqt
import json

def extract_picker_data(factor = 0.1, background_fade = 0.5, showCVs=True):
    animPickerUI = pyqt.get_instance(pyqt.get_main_window(), mgear.anim_picker.gui.MainDockWindow)
    data = animPickerUI.get_character_data()

    grp = pm.group(em=True, n='pickerData_extraction')
    for tab in data['tabs']:
        picker_grp = pm.group(em=True, n=tab['name'], p=grp)
        if 'background' in tab['data'].keys():
            ip = pm.imagePlane(n='{}_background'.format(tab['name']))
            ip[0].tz.set(-1)
            ip[0].overrideEnabled.set(1)
            ip[0].overrideDisplayType.set(2)
            ip[1].alphaGain.set(background_fade)
            pm.parent(ip[0], picker_grp)
            q_image = QtGui.QImage(tab['data']['background'])

            ip[1].imageName.set(tab['data']['background'])
            ip[1].width.set(q_image.size().width() * factor)
            ip[1].height.set(q_image.size().height() * factor)

        for item in tab['data']['items']:
            handles = item['handles']
            pos_x, pos_y = item['position']

            item_curve = pm.circle(d=1, s=len(item['handles']), ch=False)[0]
            pm.parent(item_curve, picker_grp)
            pm.closeCurve(item_curve, ch=False, ps=2, rpo=True)
            item_curve.displayHandle.set(1)
            if showCVs:
                item_curve.getShape().dispCV.set(1)

            q_color = QtGui.QColor(*item['color'])
            attr.addColorAttribute(item_curve, 'color', q_color.getRgbF()[:3])
            attr.addAttribute(item_curve, 'alpha', 'long', item['color'][3], minValue=0, maxValue=255)

            item_curve.overrideEnabled.set(1)
            item_curve.overrideRGBColors.set(1)
            item_curve.color >> item_curve.overrideColorRGB
            item_curve.rotatePivot >> item_curve.selectHandle

            if 'controls' in item.keys():
                attr.addAttribute(item_curve, 'controls', 'string', json.dumps(item['controls']))

            if 'action_mode' in item.keys():
                attr.addAttribute(item_curve, 'action_mode', 'bool', item['action_mode'])
                attr.addAttribute(item_curve, 'action_script', 'string', item['action_script'])

            if 'menus' in item.keys():
                attr.addAttribute(item_curve, 'menus', 'string', json.dumps(item['menus']))

            if 'text' in item.keys():
                attr.addAttribute(item_curve, 'text_data', 'string', json.dumps({'text': item['text'],
                                                                                 'text_color': item['text_color'],
                                                                                 'text_size': item['text_size']}))

            for i, (x, y) in enumerate(handles):
                item_curve.getShape().controlPoints[i].set(x * factor, y * factor, 0)
            item_curve.t.set(pos_x * factor, pos_y * factor, 0)

def overwrite_picker_data(factor = 0.1, data_node='PICKER_DATA', deleteCurves=True):
    grp = pm.PyNode('pickerData_extraction')
    new_data = {'tabs': []}
    upscale = lambda x, y: [x * (1 / factor), y * (1 / factor)]

    for tab_grp in grp.listRelatives():
        new_data['tabs'].append({'name': tab_grp.name()})
        new_data['tabs'][-1]['data'] = {'items': []}
        bg_imagePlane = tab_grp.listRelatives(type='imagePlane', ad=True)
        if bg_imagePlane:
            new_data['tabs'][-1]['data']['background'] = bg_imagePlane[0].imageName.get()

        for item_curve in [ic for ic in tab_grp.listRelatives() if ic.getShape().type() != 'imagePlane']:
            item_data = {}
            if item_curve.hasAttr('action_mode'):
                item_data['action_mode'] = True
                item_data['action_script'] = item_curve.action_script.get()

            # color
            q_color = QtGui.QColor()
            q_color.setRgbF(*item_curve.color.get())
            q_color.setAlpha(item_curve.alpha.get())
            item_data['color'] = q_color.getRgb()

            # controls
            if item_curve.hasAttr('controls'):
                item_data['controls'] = json.loads(item_curve.controls.get())

            # position
            item_data['position'] = upscale(*list(item_curve.getPivots(ws=True))[0][:2])

            # handles
            item_curve.f.get()
            handles = []
            for cv in item_curve.cv[:-1 if item_curve.f.get() == 0 else None]:
                x, y = upscale(*cv.getPosition(space='world')[:2])
                handles.append([x - item_data['position'][0], y - item_data['position'][1]])
            item_data['handles'] = handles

            # menus
            if item_curve.hasAttr('menus'):
                item_data['menus'] = json.loads(item_curve.menus.get())
            # text
            if item_curve.hasAttr('text_data'):
                item_data.update(json.loads(item_curve.text_data.get()))

            new_data['tabs'][-1]['data']['items'].append(item_data)
    if deleteCurves:
        pm.delete(grp)
    data_node = pm.PyNode(data_node)
    data_node.picker_datas.set(l=False)
    data_node.picker_datas.set(json.dumps(new_data).replace('true', 'True'))
    data_node.picker_datas.set(l=True)

    animPickerUI = pyqt.get_instance(pyqt.get_main_window(), mgear.anim_picker.gui.MainDockWindow)
    animPickerUI.refresh()
7 Likes

@JimBo_Drew
My man!

That is awesome!

Thank you for sharing this. Lets see how we can integrate this.

Also, on the scaling issue referenced.

1 Like

this is great! Thanks @JimBo_Drew!!!

you’re welcome guys :smiley:

1 Like

Awesome addition! The way we dealt with pickers at Disney was something similar: we can alter some basic transforms in the picker itself, but for more advanced shapes, the button was copied in the viewport to be altered, and then back to the picker.

Thanks for sharing!

so i was having a look at adding the text, and i thought i’d check to see if the text is the same between 4k and 1080p i was right to be suspicious, the text draws different on 4k as you can see below

so i had a quick look in code in:
anim_picker.widgets.picker_widgets.GraphicText().set_size()

font.setPointSizeF(value / pm.mayaDpiSetting(q=True, realScaleValue=True))

this fixed it

i saw in another thread i started about a similar issue @JxE said that pm.mayaDpiSetting is not available on mac and suggested using devicePixelRatio so i tried

pyqt.maya_main_window().devicePixelRatio()

however this returns 1 not the expected 1.5 as set in windows.

2 Likes

That’s odd, was your Maya main window on your 4k monitor when you ran the code? We’re seeing it return the correct scaling on our macs when we run that, but maybe it doesn’t work on Windows?

yeah i ran these two code snippets one after the other same maya session

pyqt.maya_main_window().devicePixelRatio()
# Result: 1 # 

pm.mayaDpiSetting(q=True, realScaleValue=True)
# Result: 1.5 #

It’s possible it’s only telling the difference between retina and non-retina displays and maybe your 4k monitor doesn’t count? https://doc.qt.io/qtforpython/PySide2/QtGui/QScreen.html#PySide2.QtGui.PySide2.QtGui.QScreen.devicePixelRatio

There may be a hint here, but I can’t dig into it too much right now: https://doc.qt.io/qt-5/qscreen.html#details

On iMac 5K screen and on Macbook Pro Retina, I get 2.

For fun, in case it helps, here is everything it returns on the 5K screen.

import maya.OpenMayaUI as omui
from mgear.vendor.Qt import QtCompat
from mgear.vendor.Qt import QtWidgets
main_window_ptr = omui.MQtUtil.mainWindow()
maya_main_window = QtCompat.wrapInstance(long(main_window_ptr), QtWidgets.QWidget)

maya_main_window.colorCount()
// Result: 256 //
maya_main_window.depth()
// Result: 24 //
maya_main_window.devicePixelRatio()
// Result: 2 //
maya_main_window.devicePixelRatioF()
// Result: 2.0 //
maya_main_window.heightMM()
// Result: 329 //
maya_main_window.logicalDpiX()
// Result: 72 //
maya_main_window.logicalDpiY()
// Result: 72 //
maya_main_window.paintingActive()
// Result: False //
maya_main_window.physicalDpiX()
// Result: 109 //
maya_main_window.physicalDpiY()
// Result: 108 //
maya_main_window.widthMM()
// Result: 599 //

so my results here are windows 10 some lg 4k monitor these setting
image

maya_main_window.colorCount()
# Result: 256 # 
maya_main_window.depth()
# Result: 32 # 
maya_main_window.devicePixelRatio()
# Result: 1 # 
maya_main_window.devicePixelRatioF()
# Result: 1.0 # 
maya_main_window.heightMM()
# Result: 334 # 
maya_main_window.logicalDpiX()
# Result: 144 # 
maya_main_window.logicalDpiY()
# Result: 144 # 
maya_main_window.paintingActive()
# Result: False # 
maya_main_window.physicalDpiX()
# Result: 163 # 
maya_main_window.physicalDpiY()
# Result: 161 # 
maya_main_window.widthMM()
# Result: 600 # 

the logicalDpi seems to match with what maya setting says maya is but then you get 72, i dunno :crazy_face:

Hey guys, thanks for the information on different machines.

I am testing this solution on anim_pickers and channel master

I have not applied to it to every aspect of the anim_picker ui or channel master, just the parks looking the most affected by the dpi.

2 Likes