Skocz do zawartości

[Inne]Scripting in Python[Maya]


mathix

Rekomendowane odpowiedzi

headerqa5.jpg

 

W wersji 8.5 programu Autodesk Maya pojawiło się wiele nowych, ciekawych możliwości. Nowy mental ray, nowe niezwykle wygodne shadery z rodziny architectural, nowy system symulacji tkanin oraz wsparcie dla wiodącego (nie tylko w świecie oprogramowania graficznego) języka skryptowego – Pythona.

 

Większość skryptów pisanych dla Mayi, to niewielkie narzędzia, często tworzone przez jedną osobę. Natywny język skryptowy tej aplikacji graficznej – MEL, w zupełności wystarcza do pisania krótkich, zgrabnych kodów. Dlaczego, więc warto używać Pythona, skoro MEL oferuje wszystko czego potrzeba? Uważam, że są głównie dwa argumenty przemawiające na korzyść tego pierwszego, a są nimi: obiektowość i popularność. O obiektowości za chwilę, najpierw wyjaśnię dlaczego to, co powszechne jest dobre.

 

Oczywistym faktem, jest to, że im bardziej znany język, tym łatwiej znaleźć programistów posługujących się nim. W wypadku Pythona dla Mayi, fakt ten jednak nie gra dużej roli, bo implementacja jest dziwna i wymaga znajomości wewnętrznej filozofii działania programu. O wiele bardziej interesujące jest to, że inne wiodące aplikacje graficzne, takie jak na przykład: Houdini, Vue, Realflow czy Nuke, zapewniają wsparcie dla Pythona, co znacznie upraszcza przenoszenie danych między nimi.

 

Obiektowość nie jest oczywiście cechą niezbędną, do pisania skryptów dla Mayi, jednak znacznie ułatwia pracę w grupie, oraz pozwala na wygodne testowanie poszczególnych elementów kodu. Podejście obiektowe (informatycy niech mi wybaczą pobieżne wyjaśnienie tej kwestii) polega na napisaniu klasy, która będzie realizowała pewien zestaw funkcjonalności za pomocą wewnętrznych komend Mayi. Ułatwia to testowanie i ponowne wykorzystanie kodu.

 

W tym tekście pokażę jak wygodnie pisać i testować własne skrypty, wykorzystując obiektowe możliwości języka Python.

 

Zakładam, że znasz podstawy Pythona, gdyż w tym tutorialu nie będę przedstawiał jego składni. Jeśli nie miałeś wcześniej do czynienia z tym językiem, to najpierw przejrzyj tutorial dołączony do oficjalnej dokumentacji: http://docs.python.org/tut/ . Następnie warto by było przeczytać rozdział Python z dokumentacji Mayi, jednak nie będzie to niezbędne do zrozumienia tekstu.

 

Konfiguracja Mayi

Na początku musisz wyedytować plik Maya.env, który znajduje się w folderze \Moje dokumenty\maya\ tak, żeby zmienna PYTHONPATH wskazywała na główny folder ze skryptami pythona. Np.:

PYTHONPATH =C:\Documents and Settings\mtx\Moje dokumenty\maya\python

tam właśnie Maya będzie szukać skryptów.

 

Żeby sprawdzić czy wszystko ładnie działa załaduj Mayę i utwórz w swoim folderze Python, plik test.py:

def main():
   print "test1"

Następnie ustaw w command line, język na Python (wystarczy kliknąć na nazwę, aby przełączać się między MEL-em i pythonem) i wpisz poniższe instrukcje (poprzedzone znakami >>>, których nie wpisuj):

>>>import test
>>>test.main()
test1

Konsola zgodnie z oczekiwaniami wypisze string "test1". Co jednak jeśli chcemy zmienić zawartość skryptu? Wyedytuj plik test.py tak aby wypisywał słowo "test2" zamiast "test1". Po wywołaniu funkcji test.main() maya znów wypisze wyraz "test1". Dzieje się tak, gdyż moduł test jest ładowany do pamięci i program nie wie, że zawartość pliku się zmieniła. Nic nie pomoże też ponowne napisanie: import test, gdyż już załadowany moduł nie zostanie załadowany powtórnie. Na szczęście po każdej zmianie nie trzeba wcale restartować Mayi. Wystarczy napisać reload(test), a moduł zostanie przeładowany. Najlepiej wpisz w script edytorze:

import test
reload(test)
test.main()

następnie zaznacz kod i przeciągnij na shelfa, to pozwoli na testowanie skryptu za pomocą jednego kliknięcia.

 

Podstawowe założenia skryptu

Teraz zabierzemy się za pisanie modułu implementującego obsługę czterech podstawowych kontrolek, bez których ciężko sobie wyobrazić interfejs: forma (Window), przycisk (Button), pole edycji (TextEdit) i etykiata (TextLabel). Każdy z tych obiektów ma pewne cechy wspólne, każdy ma rozmiar, widzialność i co najważniejsze unikatową nazwę stosowaną wewnątrz Mayi, coś jak uchwyt dla okien w WinAPI.

 

Klasa Window

Utwórz nowy plik w folderze Python np. o nazwie mxwindow.py (mam dziwne zamiłowanie do długich nazw, ale prawdziwie się mi to objawia, gdy programuję w C# :P). Na początku pliku dodaj linię:

import maya.cmds as cmds

aby skrypt zimportował moduł maya.cmds, dzięki czemu będą dostępne standardowe komendy Mayi. Słowo kluczowe as tworzy alias nazwy modułu, więc nie będzie trzeba pisać maya.cmds.columnLayout(adjustableColumn = True), a jedynie cmds.columnLayout(adjustableColumn = True). Oto schemat zależności klas, które będziemy pisać:

lesson12ct5.th.jpg

Założenie jest takie, że klasy Window, Button, TextLabel i TextEdit, są potomkami klasy Control. Kod klasy Control wygląda tak:

class Control(object):
   """Represents basic maya control. Shoud be used as abstract class."""
   __melName = ""
   __visible = True
   __size = (0,0)
   def getMelName(self):
       """Gets name representing control inside of Maya"""
       return self.__melName
   def setMelName(self, name):
       """Sets name representing control inside of Maya"""
       self.__melName = name
   def isVisible(self):
       """Checks whether control is visible"""
       return self.__visible
   def setVisibility(self, visibility):
       """Sets visibility of control"""
       self.__visible = visibility
   def getSize(self):
       """Gets size of control as (width,height)"""
       return self.__size
       """Sets size of control (width,height)"""
   def setSize(self, size):
       self.__size = size

Klasa ta jest dość prosta, gdyż zawiera trzy prywatne atrybuty i akcesory do nich. W Pythonie istnieje ładniejszy system akcesorów, ale postanowiłem w tym tekście go pominąć i zrobić dostęp do atrybutów w stylu C++.

 

Jeszcze słówko na temat prywatnych atrybutów. Python nie zawiera żadnego mechanizmu ukrywania prywatnych atrybutów czy metod. Tutaj coś takiego nie istnieje i w gruncie rzeczy wszystko jest widoczne dla użytkownika klasy. Powołujemy się po prostu na rozsądek programisty, który naszego obiektu będzie używał, licząc na to, że nie zacznie babrać tam gdzie nie powinien. Zauważ jednak, że wszystkie prywatne atrybuty zaczynam od znaków __. W Pythonie istnieje mechanizm, zwany name mangling, który powoduje zmianę tak nazywanych zmiennych na postać: _NazwaKlasy__NazwaAtrybutu, co utrudnia (lecz nie uniemożliwia), dostęp do prywatnych danych.

 

Dodaj do tworzonego modułu funkcję:

 
def main2():
   ctrl = Control()
   ctrl.setMelName("real mel name")
   ctrl.__melName = "fake mel name"
   print ctrl.getMelName()

wypisze ona string "real mel name". Linia: ctrl.__melName = "fake mel name", nie jest błędem, gdyż po prostu stworzy nowy atrybut o podanej nazwie.

 

Pomiędzy znakami """ i """ znajdują się opisy działania metod. IDE interpretują je i wyświetlają jako podpowiedź przy pisaniu kodu.

 

Zgodnie ze schematem klasa Window dziedziczy po klasie Control:

class Window(Control):
   """Represents maya's basic window"""
   name =""
   controlsList = []
   def __init__(self, name, size):
       """Initializes Window"""
       self.setVisibility(False)
       self.name = name
       Control.__size = size
       self.setMelName( cmds.window(title=name, widthHeight=size))
       cmds.columnLayout(adjustableColumn = True)
   def show(self):
       """Shows window"""
       cmds.showWindow(self.getMelName())
       self.setVisibility(not self.isVisible())
   def hide(self):
       """Hides window"""
       if self.isVisible():
           cmds.toggleWindowVisibility(self.getMelName())
           self.setVisibility(not self.isVisible())   
   def addButton(self, btnLabel):
       """Adds button to window"""
       nuBtn = Button(self, btnLabel)
       self.controlsList.append(nuBtn)
       return nuBtn
   def addTextLabel(self, txtLabel):
       """Adds text label to window"""
       nuLabel = TextLabel(self,txtLabel)
       self.controlsList.append(nuLabel)
       return nuLabel
   def addTextEdit(self, txtLabel):
       """Adds text edit to window"""
       nuEdit = TextEdit(self,"")
       self.controlsList.append(nuEdit)
       return nuEdit

Jest to nieco bardziej skomplikowany twór, dlatego postaram się wyjaśnić jak działa. Klasa zawiera jeden atrybut - controlsList. Jest to lista kontrolek, który właścicielem jest okno (forma). Wprawdzie można by sobie poradzić bez niej, ale dobrze móc się odwoływać do kontrolek położonych na formie bez żonglowania dodatkowymi zmiennymi, np. w sytuacji, gdy chcemy wykonać jakąś akcję na wszystkich kontrolkach:

   for ctrl in wnd.controlsList:
       print ctrl.getMelName()

 

Klasa zawiera metodę __init__(). Jest to swego rodzaju odpowiednik konstruktora z języków C++ czy C#. Prawdę mówiąc, jest to funkcja wywoływana zaraz po konstruktorze (__new__()), ale doskonale nadaje się do inicjalizacji danych. Metoda przyjmuje dwa (poza standardowym - self) argumenty. Pierwszy to string określający tytuł okna. Drugi to tuple (=krotka, a fee), zawierający szerokość i wysokość okna. Najważniejszym fragmentem kodu tej metody jest:

       self.setMelName( cmds.window(title=name, widthHeight=size))
       cmds.columnLayout(adjustableColumn = True)

Pierwsza linia ustawia prywatny atrybut __melName, na wartość zwróconą przez mayową komendę window. Zapamiętanie melName’a jest niezbędne do późniejszego operowania na oknie.

 

Myślę, że warto teraz wspomnieć co nieco o konwencji wywoływania komend. W MEL-u jest to zazwyczaj:

nazwaKomendy –parametr1 wartosc1 –parametr2 wartosc2

w Pythonie:

maya.cmds.nazwaKomendy(parametr1=wartosc1, parametr2 = wartosc2)

.

Python pozwala na przekazywanie do funkcji nazwanych argumentów, dzięki czemu nie trzeba się trzymać ich kolejności i ilości. Konwencja nazewnictwa parametrów jest dokładniej wyjaśniona w helpie, pod Python\Python\Using Python (Maya2008/docs/Maya2008/en_US/wwhelp/wwhimpl/js/html/wwhelp.htm), ja wytłumaczę tylko to co niezbędne do zrozumienia przedstawionego kodu. Jednak najpierw wróćmy do opisu ciała metody __init__().

cmds.columnLayout(adjustableColumn = True)

ustawia typ layoutu okna, który umieszcza wszystkie kontroli w jednej kolumnie. Ustawienie parametru adjustableColumn na true, powoduje, że kontrolki należące do okna będą się wraz z nim rozszerzać i zwężać, trzymając się bocznych krawędzi.

 

Metody show() i hide() w prosty sposób wywołują komendy showWindow i toggleWindowVisibility, posługując się danymi klasy. Metody addButton(), addTextLabel(), addTextEdit(), tworzą egzemplarze odpowiednich obiektów i dodają kontrolki do okna. Sposób działania funkcji pokażę, na przykładzie pierwszej z nich.

   def addButton(self, btnLabel):
       """Adds button to window"""
       nuBtn = Button(self, btnLabel)
       self.controlsList.append(nuBtn)
       return nuBtn

Najpierw tworzony jest nowy egzemplarz obiektu klasy Button i referencja do niego jest dodawana do listy oraz zwracana przez funkcję. Po utworzeniu nowego obiektu wywoływana jest jego metoda __init__(), która dla przycisku wygląda następująco:

   def __init__(self, parentControl, btnLabel):
       self.setMelName(cmds.button(label = btnLabel,
           parent=parentControl.getMelName()))

Komenda button, która tworzy nowy przycisk, wymaga podania nazwy okna, do którego przycisk ma zostać dodany. Dlatego konieczne jest przekazanie do funkcji obiektu okna.

 

Querying

Pewnie zastanawiasz się jak pobrać dane z kontrolki np. z TextBoxa. W MEL-u wykonuje się to za pomocą wysłania zapytania o wartość, czyli po prostu dodania flagi –q (query) przed nazwą parametru, którego wartość chcemy pobrać. W Pythonie też jest prosto. Oto metoda zwracająca tekst etykiety przycisku:

   def getLabel(self):
       """Gets label"""
       return cmds.button(self.getMelName(), q=True, label = True)

Jak widać wystarczy przesłać do funkcji dwa nazwane argumenty, jeden q, drugi nazwa_parametru i obu przypisać wartość True.

 

Dodawanie procedur obsługi zdarzenia

Przycisk do niczego się nie przyda, jeśli nie będzie reagował na kliknięcia. Metoda setOnCommand() ustawia procedurę obsługi kliknięcia.

   def setOnCommand(self, procedure):
       """Sets onCommand procedure procedure_name(*args)"""
       cmds.button(self.getMelName(), e=True, command = procedure)

Wykorzystuje do tego komendę button, z flagą edit. Podobnie jak w wypadku query, należy przekazać imienny argument e (edit) ustawiony na True, z tym, że tym razem chcemy wyedytować, a nie pobrać wartość, więc trzeba również przekazać argument z nową wartością.

Przykład użycia metody:

def tested2(*args):
   print "tested2 executed!"
(...)
btn1.setOnCommand(tested2)

Procedury obsługi różnych zdarzeń, mogą pobierać różną ilość argumentów, dlatego najwygodniejszym rozwiązaniem jest dopuszczenie dowolnej ilości, przynajmniej w sytuacji, gdy nie będziemy z nich korzystać.

 

Pozostałe klasy pochodne

Pozostałe klasy dziedziczące po Control, są bardzo podobne do Button, dlatego nie będę ich oddzielnie omawiać. Mechanizmy, które zostały zaprezentowane powyżej, powinny wystarczyć do zrozumienia działania reszty kodu.

Cały kod, z przykładami użycia (funkcja main() ):

#!/usr/bin/env python
import maya.cmds as cmds

class Control(object):
   """Represents basic maya control. Shoud be used as abstract class."""
   __melName = ""
   __visible = True
   __size = (0,0)
   def getMelName(self):
       """Gets name representing control inside of Maya"""
       return self.__melName
   def setMelName(self, name):
       """Sets name representing control inside of Maya"""
       self.__melName = name
   def isVisible(self):
       """Checks whether control is visible"""
       return self.__visible
   def setVisibility(self, visibility):
       """Sets visibility of control"""
       self.__visible = visibility
   def getSize(self):
       """Gets size of control as (width,height)"""
       return self.__size
       """Sets size of control (width,height)"""
   def setSize(self, size):
       self.__size = size

class Window(Control):
   """Represents maya's basic window"""
   controlsList = []
   def __init__(self, name, size):
       """Initializes Window"""
       self.setVisibility(False)
       Control.__size = size
       self.setMelName( cmds.window(title=name, widthHeight=size))
       cmds.columnLayout(adjustableColumn = True)
   def show(self):
       """Shows window"""
       cmds.showWindow(self.getMelName())
       self.setVisibility(not self.isVisible())
   def hide(self):
       """Hides window"""
       if self.isVisible():
           cmds.toggleWindowVisibility(self.getMelName())
           self.setVisibility(not self.isVisible())   
   def addButton(self, btnLabel):
       """Adds button to window"""
       nuBtn = Button(self, btnLabel)
       self.controlsList.append(nuBtn)
       return nuBtn
   def addTextLabel(self, txtLabel):
       """Adds text label to window"""
       nuLabel = TextLabel(self,txtLabel)
       self.controlsList.append(nuLabel)
       return nuLabel
   def addTextEdit(self, txtLabel):
       """Adds text edit to window"""
       nuEdit = TextEdit(self,"")
       self.controlsList.append(nuEdit)
       return nuEdit

class Button(Control):
   def __init__(self, parentControl, btnLabel):
       self.setMelName(cmds.button(label = btnLabel,
           parent=parentControl.getMelName()))
   def setOnCommand(self, procedure):
       """Sets onCommand procedure procedure_name(*args)"""
       cmds.button(self.getMelName(), e=True, command = procedure)
   def getLabel(self):
       """Gets label"""
       return cmds.button(self.getMelName(), q=True, label = True)
   def setLabel(self, btnLabel):
       """Sets label"""
       cmds.button(self.getMelName(), e=True, label = btnLabel)

class TextLabel(Control):
   """Represents basic maya's text label"""
   def __init__(self, parentControl, txtLabel):
       """Initializes maya's text label"""
       self.setMelName(cmds.text( label= txtLabel,
           parent = parentControl.getMelName() ))
   def setLabel(self, btnLabel):
       """Sets label"""
       cmds.text(self.getMelName(), e=True, label = btnLabel)
   def getLabel(self):
       """Gets label"""
       return cmds.text(self.getMelName(), q=True, label = True)

class TextEdit(Control):
   """Represents maya's basic text edit"""
   def __init__(self, parentControl, initialText):
       """Initializes text edit"""
       self.setMelName(cmds.textField( text= initialText,
           parent = parentControl.getMelName() ))
   def setText(self, newText):
       """Sets text in text edit"""
       cmds.textField(self.getMelName(), e=True, text = newText)
   def getText(self):
       """Gets text in text edit"""
       return cmds.textField(self.getMelName(), q=True, text = True)

def tested2(*args):
   print "tested2 executed!"

def main():
   wnd = Window("test", (50,100))
   wnd.addTextLabel("click to execute:")
   btn1 = wnd.addButton("Click me!")
   btn1.setOnCommand(tested2)
   wnd.show()
   for ctrl in wnd.controlsList:
       print ctrl.getMelName()
   print len( wnd.controlsList)
   wnd.controlsList[0].setLabel("nu Label")

  • Like 2
Odnośnik do komentarza
Udostępnij na innych stronach

  • Odpowiedzi 9
  • Created
  • Ostatniej odpowiedzi

Top Posters In This Topic

Top Posters In This Topic

Dzięki za ogarek!

 

pozdr.,

skk.

 

PS

Swoją drogą obiektowość Pythona byłaby lepiej widoczna, gdyby samo API w Mai było obiektowe. Nie jest, i podstawowa rzecz, którą zajmuje się nasz obiektowy kod, to generowanie nieobiektowych komend Mai - to jakby bawić się XML'em bez żadnego DOM'mu.

 

Wprawdzie, jak piszesz, popularność Pythona nie tu żadnego znaczenia, bo nie da się używać go w Mai bez znajomości MEL'a, ale jeszcze jedną różnicą, którą wprowadza Python jest czytelność i elegancja kodu. Komendy te same, ale porządek w parametrach, zmiennych, pętlach itd jakby większy ;)

Odnośnik do komentarza
Udostępnij na innych stronach

Swoją drogą obiektowość Pythona byłaby lepiej widoczna, gdyby samo API w Mai było obiektowe. Nie jest, i podstawowa rzecz, którą zajmuje się nasz obiektowy kod, to generowanie nieobiektowych komend Mai - to jakby bawić się XML'em bez żadnego DOM'mu.
No faktycznie tak to wygląda. Z jednej strony rozumiem twórców Mayi - chcieli dać programistom MEL-a ten sam zestaw narzędzi w Pythonie co ułatwiłoby im przesiadkę na nowy język. Z drugiej strony, do zestawu paskudnych, strukturalnych funkcji mogliby dorzucić elegancki, profesjonalny, obiektowy wrapper. Doskonale udało się to ludziom z Microsoftu, kiedy osadzili DirectX-a w zarządzanym kodzie, w pełni wykorzystując specyfikę C# i zarazem nie pomniejszając sporego wachlarza możliwości jakie oferuje niezarządzana, nieobiektowa wersja API.

 

Faktycznie python jest zdecydowanie bardziej elegancki, niż MEL. W końcu to język programowania z prawdziwego zdarzenia i choć przyznam, że jako zwolennik całkowitego uporządkowania kodu, dostrzegam w nim wady, to jednak w porównaniu do natywnego języka Mayi, pozwala tworzyć zdecydowanie bardziej czytelne aplikacje.

Odnośnik do komentarza
Udostępnij na innych stronach

Tego żaden z Nas nie może wiedzieć na pewno, ale skoro oba programy są równolatkami, oba działąją na nodach (czy jak to się tam w Mai nazywa), to nie wydaje mi się, żeby były tu jakieś zasadnicze różnice. Spodziewałbym się raczej, że chodziło im o zgodność wstecz, jak już napisano, no i zwykłe korporacyjne lenistwo... po co zmieniać coś, co działa i się sprzedaje?

Odnośnik do komentarza
Udostępnij na innych stronach

Jeśli chcesz dodać odpowiedź, zaloguj się lub zarejestruj nowe konto

Jedynie zarejestrowani użytkownicy mogą komentować zawartość tej strony.

Zarejestruj nowe konto

Załóż nowe konto. To bardzo proste!

Zarejestruj się

Zaloguj się

Posiadasz już konto? Zaloguj się poniżej.

Zaloguj się



×
×
  • Dodaj nową pozycję...

Powiadomienie o plikach cookie

Wykorzystujemy cookies. Przeczytaj więcej Polityka prywatności