cosmogonies.net Blog of cosmogonies.net

tools
13Apr/150

Unity’s Mono maniacs

Be sure of one thing: I love Unity and cannot let a day end without doing something with it^^.
But: there's a but.
I'm very disappointed about the transparency regarding its mono version, integration and... modification.
Everything in Unity is well documented, tutorialized, blogged and tweeted... everything but Mono !

What are we talking about exactly ?

Unity is using the C# language, but it stand on top of a modified version of Mono 2.6 .
Mono (http://www.mono-project.com/) is a framework : a utility toolkit that extends the language/compiler by high-abstraction primitives, like List, Dictionaries, garbage collector, Self-Introspection.
Mono was made as an open-source alternative to the .NET Framework, made by Microsoft (which also became open-source recently ! sources here )
You can see Mono as the foundation of Unity's kernel, and its secret recipe for its huge portability.

So, I have one request and one only:

As Unity provides its manual documentation : $INSTALLPATH/Unity/Editor/Data/Documentation/en/Manual/index.html
... and its API documentation : $INSTALLPATH/Unity/Editor/Data/Documentation/en/ScriptReference/index.html
Why do you not provide your framework documentation ?
I'm quite sure 90% of Unity users uses MSDN website when working on their games.
But think about it; That's like reading MS Word's help when using LibreOffice right ?

So you can find documentation of classes or methods that are not available in unity, especially when integrating other c# library, external to unity.
I found myself unable to use the namespace "System.Threading.Tasks", System.Drawing.Image or also all LINQ-relative syntax that are not available also.

What can we know ?

So, "Internet" says Unity runs a modified Mono 2.6, but from my own very very old post here :
http://answers.unity3d.com/questions/259448/how-to-determine-mono-version-of-unity-.html
I tried to get dynamically the Mono version, and that is a mess :

#$INSTALLPATH\Unity\Editor\Data\Mono\bin\mono.exe" -V
>Mono JIT compiler version 2.0 (Visual Studio built mono)
>Copyright (C) 2002-2010 Novell, Inc and Contributors. www.mono-project.com
>TLS: normal
>GC: Included Boehm (with typed GC)
>SIGSEGV: normal Notification: Thread + polling
>Architecture: x86
>Disabled: none

To be more precise :

#$INSTALLPATH\Unity\Editor\Data\Mono\bin>monop2 --runtime-version mono.exe
>runtime version: 2.0.50727.1433

In script you can also :

//you will need "using System.Reflection;" in your file header
System.Type type = System.Type.GetType("Mono.Runtime");
if (type != null) {
System.Reflection.MethodInfo displayName = type.GetMethod("GetDisplayName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
if (displayName != null)
    Debug.Log(displayName.Invoke(null, null)); // On Unity5.0 it printed "2.0 (Visual Studio built mono)"
}

Worse a dedicated "Mono Compatibility page" has disappeared since 4.1.2 :
http://docs.unity3d.com/412/Documentation/ScriptReference/MonoCompatibility.html
http://docs.unity3d.com/ScriptReference/MonoCompatibility.html => page not found !
Apparently, the modified version of Mono is open-source, but the github link seems quite frozen to me...
So please, help us at least find the real and correct documentation of our everyday framework !

By the way, taken how things move fast :

I know the biggest feature in Unity is its huge portability, and it was named from that, but maybe we can improve our workflow by following the two main directions of .NET and Mono ?
When you looked at the history of Mono releases  (Yes Mono 2.6 is dated from 2009) and the Microsoft's ones (.NET 4.0 and .NET 4.5 ) there is great features and Unity could benefits from them.
(To follow the equivalent rule between these twos, Let's say Mono 2.6 is equivalent to C# 3.0 (in terms of language features). Mono 2.6.1 started adding .NET 4.0 features).
But what about the "modified version" Unity uses ?
Did it evolves along side of the Editor ?
Maybe I'm wrong, and if this kind of documentation exists, I will be  very thankful to anyone who can link it to me !
But if not, please please provides us something to work with ^^.

suggest
5May/141

Funfact, some lines of code for “Ghost of a tale” are writen from Alcatraz !

Ghost of a tale, coded from Alcatraz

Let me set the scene here :

The GDC14 was just finished, I only had the saturday to visit San Francisco a little more.
Last year I've done a great tour, but couldn't reach the Rock !
What a great opportunity to capture all sort of textures, of rotten and twisted metals to the famous decaying prison^^.

But, on my way to it, I just crossed the visitable WorldWarII submarine, the mighty USS PAMPANITO .
I was like a kid in a toy-store ; you can't imagine how many pictures of pipes and "steampunk devices" I took !
Enough to drain a lot my camera's battery...
So the boat was boarding, and I had no juice left to make my camera work... but I have always my work with me ^^.
... I just sat in Alcatraz library, refilling my camera on my computer, while killing time... on Unity's game development of course !
Wanna hear more about the game ? Have a look at : http://www.ghostofatale.com/

Ghost of a tale, coded from Alcatraz

Ghost of a tale, coded from Alcatraz

 

service
5Mar/133

How to batch Motionbuilder: FBX SDK to the rescue.

When I suddently discovered that MotionBuilder does not have a batch mode, I fainted, as I consider doing my job is half the time coding batch tools.

As a reminder, the Batch version of a software is the access of this one in command-line; means without interfaces, but more importantly it is also scriptable so you can process infinite auto-tasks management with it.
Maya comes with a mayabatch.exe, and xsi has its xsibatch.exe, but you can just start MotionBuilder.exe with a -console flag, which allow you having an external output, nothing really fancy.
This is a real issue here: How can building a python framework, multi-environment, and keep tools doing full-auto pipeline processes.
Thankful to Autodesk policy, they gave us a solution : FBX SDK.

  • Explanations:

FBX SDK is written in c++, but hopefully Autodesk managed to wrapped it with SWIG to propose a binding python (if the link is dead, try a search with fbx sdk python binding in Area)
The full doc is actually here, but it is the c++ one. Do not worry about it, python equivalent are found easily (remember dir statement).

So, Is this another environement to my framework: FBX ? Not quite.
We are still in standalone python, but we are using a library to PARSE a file format.
As Maya uses .MA as a 3d structure of succession of MEL Commands, and .MB as a binay, FBX provides both ascii and binary, but ascii fbx are very hard to parse.
FBX SDK help you to read, modify and save any file of FBX, but require only python, ANY python installed on your computer.
For example, I use FBX SDK to open, read, and manage a fbx file, running into a mayabatch.exe (and its mayapy python).
You can also used FBX SDK into MotionBuilder UI, but that is REALLY the snake eating its own tail !

  • Several snippets:

Here I share with you several usefull snippets:


def getHandle(_FilePath):
  import PyFBX
  import PyFBX.fbx

  lSdkManager = PyFBX.fbx.KFbxSdkManager.Create()
  ios = PyFBX.fbx.KFbxIOSettings.Create(lSdkManager, PyFBX.fbx.IOSROOT)
  lImporter = PyFBX.fbx.KFbxImporter.Create(lSdkManager, "")
  lImporter.Initialize(_FilePath, -1, lSdkManager.GetIOSettings())
  MobuFileHandle = PyFBX.fbx.KFbxScene.Create(lSdkManager, "myScene")
  lImporter.Import(MobuFileHandle)
  return MobuFileHandle

If you use that to parse a lot of files, be really sure to free the RAM from
.Import here will literally copy the file into the RAM of your computer, and as the reference is still alive, the garbage collector will not free the RAM.
My advice here is to explicitly calling lSdkManager.Destroy() and recreate a fileHandle from scratch every time you switch from a file to another.


def saveAs(_FBXHandle, _FilePath):
  import PyFBX
  import PyFBX.fbx
  import os.path
  lSdkManager = PyFBX.fbx.KFbxSdkManager.Create()
  ios = PyFBX.fbx.KFbxIOSettings.Create(lSdkManager,PyFBX.fbx.IOSROOT)
  lExporter = PyFBX.fbx.KFbxExporter.Create(lSdkManager,"")
  lExporter.Initialize(_FilePath,-1,lSdkManager.GetIOSettings())
  res = lExporter.Export(_FBXHandle)

  if res and os.path.exists(_FilePath):
    print("File was exported succesfully at ="+_FilePath)

Remember how Motionbuilder manage their types of objects in Navigator Models aside, and all objects ordered by types?
Same thing here in FBX, everything is stored by types first, and you have a Method of Scene for everyone:
Finally, notice how most of them are only accessible with an index, GetCharacter, GetControlSetPlug, GetCharacterPose, GetPose , GetMaterial, GetTexture, GetVideo
Here are some snippets for parsing:


def getVideoFiles(_Scene):
  VideoList=[]
  for currentIdx in range(_Scene.GetVideoCount()):
    VideoList.append( _Scene.GetVideo(currentIdx).GetFileName() )
  return VideoList

def getMaterials(_Scene):
  MaterialList=[]
  for currentIdx in range(_Scene.GetMaterialCount()):
    MaterialList.append( _Scene.GetMaterial(currentIdx) )
  return MaterialList

def getTextures(_Scene):
  TextureList=[]
  for currentIdx in range(_Scene.GetTextureCount()):
    TextureList.append( _Scene.GetTexture(currentIdx) )
  return TextureList

def getChildren(_Node):
  ChildrenList=[]
  for currentIdx in range(_Node.GetChildCount()):
    ChildrenList.append( _Node.GetChild(currentIdx) )
  return ChildrenList

So you can easily explore the graph of a top node:


def getAllNodes(_TopNode):
  import PyFBX
  import PyFBX.fbx
  theNodeList=[]

  def recurseExplore(currentNode):
    print currentNode.GetTypeName(), currentNode.GetName()

    for i in range(currentNode.GetChildCount()):
      cur = currentNode.GetChild(i)
      theNodeList.append(cur)
    recurseExplore(cur)

  recurseExplore(_TopNode)
  return theNodeList
fd = getHandle(r"\\...\Your\Path\FileName.fbx")
test = getAllNodes(fd.GetRootNode())

  • Avoiding traps and shortcuts:

Finally, you can of course look for a specific property.
You have in the doc all the methods for getting rotation or translation in Nodes.
Here is some snippets to get a value of a property by name, for example your own custom ones.


#currentNode= fd.GetRootNode()
value = currentNode.FindProperty(_PropertyName).Get()

But be very careful about not mixing both SDK in your mind!
FBX is NOT Motionbuilder, and experienced python coders in Mobu will find some similarities, but it differs a lot.
For example, forget about .Data member of a node to get its value, but use .Get() method.

Precisely, python in Mobu is called "Open Reality SDK" and is not FBX SDK, but the python package name in mobu, pyfbsdk, can be really confusing (FB prefix comes from FilmBox).
Here are some others examples:


if type(currentNode) == PyFBX.fbx.KFbxNode:
  value = currentNode.Translation.Get()
  print type(value), type(value[0]),value[0]
#<class 'fbx.fbxDouble3'> <type 'float'> 0.0
  value = currentNode.GetPreRotation(0)
  print type(value), value, value[0]
#<class 'fbx.KFbxVector4'> fbx.KFbxVector4(0.000000, 0.000000, 0.000000, 1.000000) 0.0

  • The bright and dark sides

You can open the scenes quickly, automatically.
You can safely open/close a lot of files like this.

I encountered some issues while saving cleaned file, so be sure to increment and not overwrite (as usual of course!)
You cannot blast, cannot plot, etc. ; well basically, you are not in mobu, nor using pyfbsdk module, but you can go very far (edit keys for example)

In my present experience with MotionBuilder, I used FBX SDK to:

- Scan all characters assets to parse a specific PreRotation joint value.
- Scan all scenes to list their content, sync it with asset management system.
- Done some stats about the artists worked, draw dependencies graph and find obsolete assets.

blog
14Feb/130

Invisible chars could be a nightmare

... so avoid them at all costs. Show these shy bastards !

The main trick about whitespaces is about not being able to see them, or count them.

Because you could fail an == operator due to these discreet ghosts,
or also badly concatenate paths or batch commands,
please always configure your IDE to show whitespaces and co.

  • Tabs too !

Especially dealing with python, I found very usefull to display an arrow (Eclipse, NotePad++) or a line (Sublime) to easily count the level of indentation.
But it happens sometimes you have to merge foreign code, from others snippets, from internet, from samples in doc of your Maya/MotionBuilder/whatever, and you will have to conform everything.
So let's make a rule of showing every tabs as whitespaces (4) and display whitespaces.

  • my IDE Configuration reminders.
  1. Eclipse: Window > Preferences > General > Editors > Text Editors  "whitespace characters"
  2. Sublime: on file Preferences.sublime-settings
    "draw_white_space": "all",
    "tab_size": 4,
    "translate_tabs_to_spaces": true,
  3. MonoDevelop: Tools>Options: Text Editor>Markers and Rulers "Whitespace Markers"
  • Log it right!

    When displaying a variable, for a quick debug of a value, I prefer to always finish by an equal without spaces like that:

    print("MyVar ="+str(MyVar))

    It's a not nice to read, but displaying like that you will have a real certitude about the start of the value. Just after the marker "=". You cannot be more sure.

  • End of the line...

At last, I do not show EndOfTheLine carret. Because they are blurrying the view, especially from braces opening.
And I never encounter a case where it is helpful to display. Please sure if you have !

 

notice
Tagged as: , No Comments
tools
16Dec/120

Failing @publishing on Android PlayStore: Signing package with correct encryption !

I record here my painful experience as releasing my second game on Google Android Market: the "Play Store".

I find that I signed my apk with a key with the latest JAVA 1.7 keytool, that used as crypto algorithm "SHA256withRSA"
I found this algorithm is not supported by Android Publishing, and provoke an error : "Package file was not signed correctly" when installed (so AFTER being purchased!)
The very epic fail is that the apk was authorized and google let me release on the market a deficient application !

In the rush, I uninstalled JDK 1.7, rolling back to 1.6, delete my key from my current keystore, and re-create one (same name) with "SHA1withDSA" encryption...
When I uploaded the new release, well encrypted this time, I get this FANTASTIC error just after the upload process in my dev web-page: "The apk must be signed with the same certificates as the previous version."
Ok then... so I can NOT fix my release because of my first wrong encryption (caused by a better default algorithm remember). Wonderfull.

After several mails unanswered and some forum topics , I decided to unpublished my whole game. And create a new one, same name, same description but with a good SHA1-encrypted first apk upload.
I hope my bad experience will be useful for someone...

Lesson learned ? Not use latest jdk Well understand all releasing/publishing processes and do not let any commands or steps as default value.
If you do not understand an argument, an option in Unity3d (which create your key user-frendly but with default values, do NOT ignore it, and give you TIME to read about it. Painful BUT Safe.

Little step by step processes:
1°) Install JDK 1.6, and to not forget to add this environment variables:
JAVA_HOME C:\Program Files\Java\jdk1.6.0_26
Path C:\Program Files\Java\jdk1.6.0_26\bin
2°) In CommandLine interpreter, type:
keytool -genkey -alias TheNameYouWant -keystore TheNameYouWantPrivateKey.store
3°) Verify your key like that:
keytool -v -list -keystore "TheNameYouWantPrivateKey.keystore"
4°) Check apk signature:
jarsigner -verify -certs -verbose D:\The\Path\To\Your\Application\MyAwesomeGame.apk

suggest
21Nov/120

Bloody Typing (my second smartphone game) is realeased !

Bloody Typing

My second game is realeased on both Apple and Android Market !

It is a typing game: you kill ennemies when you type their "names".
It is fun, it is arcade, and it WILL improve your typing skills !

Apple iOS App Store link    &     Android Play Store link
It is done in Unity3d, in c#.


It contains:

MODE STORY: unlock one by one all words categories to unlock next levels.
4 environments are currently infested by evil vampires !
Words categories are:
Graveyard: "Color","Sport","Common"
Misty Forest: "Animal","Stars","Plants"
Ruined Castle: "Body", "Bitter biters", "Phobia"
Mad Lab: "Science","Dino","Extinct species", "Medical"

Each story levels end by a BOSS, that can be only killed by a famous quote!
MODE ARCADE: Scoring challenge to type faster, making big scores on longer words.
TIMED CHALLENGES: Beat your score on a fixed duration, perfect mode for public transports!

To have tested my game myself, I can assure you it will really help you to type faster.
It helps memorize the keyboard layout, and on top of that you also learn some vocabulary !

Three difficulty modes assures that casuals could access every corner of the game,
by the way hard difficulty and scoring let the game "Easy to learn, Hard to master".

3 Virtual keyboard layouts are available right now: QWERTY, DVORAK, AZERTY
You also had the option to hide the keyboard, for a very challenging blind game !
Learning or improving how to type with fun ! Write enemies names to kill them.

service
23Aug/120

APyTA_17 : OBJECT Part III = Static tactics

APyTA_17 : OBJECT Part III = Static tactics

Cosmogonies_APyTA_17

Illustation of Lesson APYTA#17:
http://cosmogonies.net/Blog/category/apyta/

Well, this snippet is just a more elaborate version of the latest post, about creating an object-oriented skeleton:
Because my credo here is to illustrate how objects is all about meaning, giving sense (and some reality) to our lines of code, I propose here to add some arguments to give some consistence to our skeleton, a little how we create a character in role playing game^^. So, this is my prototype of my Avatar creation:

#
def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):
#

See how we are dealing with real stuff, and also giving more flexibility than previous snippet.

By the way, you have now to discover another concept, static members and methods.
I talked previously how a class is instantiated into a variable: myInstance = myClass().
The instance will benefits to all members and methods (functions) bound to the self keyword in class definition.

Well sometimes you don't want to bind members to instances. You want a variable to be linked to the scope of the class itself !
These are called static members and static methods, and examples are explained bellow.
Again, rather than cutting the long snippet, I placed my comments through the lines inside the code itself:

import math
import maya.OpenMaya
import pymel.core
import maya.cmds

maya.cmds.currentUnit(linear='m')  # Unless you're a gridsnap-lover, I prefer working with meter units that make more sense for me.
maya.cmds.jointDisplayScale(5)

class eSexe():
    """ An enumeration of all sex status """
    (kMale, kFemale) = ("Male",  "Female")  # This snippet is useful when you want to manage an very simple-object with several enumeration (as struct in c)

class Avatar(object):  # Also when desribing your mother-of-all class, always inherit from the python class-object (you will benefits of some built-ins)
    """ This object is a rigged-skeleton representation of a character """
    NbSpine = 8  # Arbitrary, we decide that all spinal chain will have 8 joints. So it is in a static Member of Avatar class.
    HipsRatio = 0.4  # Percentage of hips height. Arbitrary also !

    def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):
        self.Age = _Age  # With age,  bones are stacked and incurved,  int ,  in years
        self.Size = _Size  # How tall it is. Float,  in meters
        self.Sex = _Sex  # Sexe influence Hips Width and Shoulder width

        self.LimbList = []   # Limb Lists
        self.ArticulationList = []   # All Joints Lists
        self.Hips = None  # Quick Access shortcut
        self.NeckBase = None  # Quick Access shortcut

        self._createTrunk()  # We can call an method into the inialisation process of our Avatar instance.

        ArmLength = self.NeckBase.getTranslation(space='world').y - self.Hips.getTranslation(space='world').y
        for i in range(_NbArm):
            angle = (float(i) / float(_NbArm)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle), 0, math.sin(angle))
            newArm = Arm(chr(65 + i), self.NeckBase, RelativePosition * Limb.getGenderOffset(Arm, self.Sex), ArmLength)  # Here we create a new instance of class Arm, and Arm-Object.
            newArm.createIK()  # Notice here we call the method from the newly created instance. For Leg it is done differently.
            self.LimbList.append(newArm)

        for i in range(_NbLeg):
            angle = (float(i) / float(_NbLeg)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle),  0,  math.sin(angle))
            newLeg = Leg(chr(65 + i), self.Hips,  RelativePosition * Limb.getGenderOffset(Leg, self.Sex))  # Notice also the call of a static-method of class Limb.
            self.LimbList.append(newLeg)

        if _hasTail:
            self.LimbList.append(Tail(self.Hips, 10, 2))

        RootLocator = pymel.core.circle(normal=(0, 1, 0), center=(0, 0, 0), radius=0.5)[0]
        pymel.core.annotate(RootLocator, text=str(_Name), point=(0, 0, 0.5))
        pymel.core.parent(self.Hips, RootLocator)

    def _createTrunk(self):
        """ Will create the trunk of the body """
        HipsPos = maya.OpenMaya.MVector(0, self.Size * Avatar.HipsRatio, 0)  # Notice here the use of static member of Avatar, treat that like a constant value.
        SpineOffset = (self.Size - HipsPos.y) / float(Avatar.NbSpine)
        SenescenceOffset = float(self.Age) / 100.0  # Factor of spine curve due to age (Cheating because Height is conserved)
        SenescenceOffset *= 0.01

        for increment in range(Avatar.NbSpine):
            pymel.core.general.select(clear=True)   # We do not want Maya to create fuzzy stuff with active selection. So before each creation we clear the selection.
            currrentArticulation = pymel.core.nodetypes.Joint(name="Spine_" + str(increment + 1).zfill(2))
            currrentArticulation.setTranslation(maya.OpenMaya.MVector(0,  HipsPos.y + increment * SpineOffset, (increment ** 2) * SenescenceOffset))

            if self.ArticulationList:  # Not the first joint, we have to connect it to the previous one...
                pymel.core.connectJoint(currrentArticulation,  self.ArticulationList[-1],  parentMode=True)  # Remember,  -1 is the index of the last member of a list.
            else:
                self.Hips = currrentArticulation

            if increment == Avatar.NbSpine - 2:  # The last two bones are Neck and Head, so Arms takes their root from bellow.
                self.NeckBase = currrentArticulation

            self.ArticulationList.append(currrentArticulation)

class Limb(object):
    def __init__(self, _Name):
        self.ArticulationList = []  # Same name as in Avatar member, but 'self.' indicates the ownership of this class instance. We're deadling with two very different variables so.
        self.Name = _Name
        self.Effector = None  # Every Limb MUST have an effector. But right now it is purely virtual. The member will be set when Ik will be constructed.

    @staticmethod
    def getGenderOffset(_Limb, _Sex):  # Notive there is no 'self' first argument.
        """ Static Method that return offset of Base position for given Limb """
        if _Limb is Arm:  # Notice also the first expected argument here is the class itself, not one of their instances: That's where is operator fit better than ==
            if _Sex == eSexe.kFemale:
                return 0.25
            elif _Sex == eSexe.kMale:  # Male have larger shoulder...
                return 0.3
        elif _Limb is Leg:
            if _Sex == eSexe.kFemale:  # Male have larger hips...
                return 0.3
            elif _Sex == eSexe.kMale:
                return 0.25
        else:
            raise  # Unknown Limb ! (Not the purpose here but why not raise a custom Exception... as a new class of course)

class Arm(Limb):
    NbArticulation = 3  # We iterate through a max joint define in the class member, not the instance member. It is call a static member. Useful for storing scoped-constants and convert factor.

    def __init__(self, _Name, _NeckBase,  _Offset, _Length):
        super(Arm, self).__init__("Arm_" + _Name)  # Inheritance in python is not automatic. Super (Mother) class of Arm is Limb, and its methods are callable form Arm instance, but it needs the keywords super to do that.
        LastPosition = _NeckBase.getTranslation(space='world')
        _Offset.normalize()

        for i in range(Arm.NbArticulation):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))
            NewPosition = LastPosition + _Offset * (_Length / float(Arm.NbArticulation))
            currentArticulation.setTranslation(NewPosition)
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation,  _NeckBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation,  self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)
            LastPosition = NewPosition

    def createIK(self):
        pymel.core.general.select(clear=True)
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)
        self.Effector = result[0]  # Filling Limb inherited member with the newly created effector.

class Leg(Limb):
    NbArticulation = 3

    def __init__(self, _Name, _HipsBase,  _Offset):
        super(Leg, self).__init__("Leg_" + _Name)
        BasePosition = _HipsBase.getTranslation(space='world')
        previouslyCreated = _HipsBase
        for i in range(0, Leg.NbArticulation):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))

            if(previouslyCreated == _HipsBase):  # First joint
                currentArticulation.setTranslation(BasePosition + _Offset, space='world')
            else:
                InverseLerp = (1 - (i / float(Arm.NbArticulation - 1)))   # Lerp is the transpose of a range FROM [min = >max] TO [0 = >1]   # Arm.NbArticulation-1 because we do not care about the first Articulation
                currentArticulation.setTranslation(maya.OpenMaya.MVector(previouslyCreated.getTranslation(space='world').x, previouslyCreated.getTranslation(space='world').y * InverseLerp, previouslyCreated.getTranslation(space='world').z), space='world')

            pymel.core.connectJoint(currentArticulation,  previouslyCreated,  parentMode=True)
            previouslyCreated = currentArticulation
            self.ArticulationList.append(currentArticulation)
        self.createIK()  # Instead of Arm, here we decided that it is into the __init__ process to create IK, so we call the methods from here.

    def createIK(self):
        pymel.core.general.select(clear=True)
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)  # IkRPsolver TOASK
        self.Effector = result[0]
        return result[0]

class Tail(Limb):
    def __init__(self,  _HipsBase,  _NbSubdiv, _Size=1):
        super(Tail, self).__init__("Tail")
        BasePosition = _HipsBase.getTranslation(space='world')
        for i in range(1, _NbSubdiv):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name="Tail_" + str(i + 1).zfill(2))
            offSet = i / float(_NbSubdiv)
            currentArticulation.setTranslation(maya.OpenMaya.MVector(BasePosition.x, BasePosition.y, BasePosition.z - (offSet * _Size)),  space='world')
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation, _HipsBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation, self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)
        self.createSplineIK()

    def createSplineIK(self):
        pymel.core.ikHandle(name="TailIKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSplineSolver)

JohnDoe = Avatar("John Doe", _Sex=eSexe.kMale)
TentacleMonster = Avatar("Weirdo", _Size=2.5, _NbArm=5, _NbLeg=3)
GrandMaMouse = Avatar("Grand'Ma Mouse", _Size=1.2, _Age=86, _hasTail=True)

Tagged as: , , , , No Comments
blog
21Aug/121

APyTA_16 : OBJECT Part II, How to inherit a skeleton

APyTA_16 : OBJECT Part II, How to inherit a skeleton

I promise you that object-oriented coding will be meaningful.
For a more accurate example, I have a biggest snippet to show you , dedicaced to rig team.

First we have to introduce a new concept between objects : the inheritance.
You can also create a hierarchy between objects, indicating explicitely in a class a super-class.
Not like parenting, telling a class is issued from another object (could be your classes or another built-in type) is about describing an object which is a more complex version of this class.
The super-class is a generalisation of your class.
Here is a quick example of hierarchy into classes:

class Animal:
  pass
class Mamal(Animal):
  pass
class Cat(Mamal):
  pass

Well, so you can create objects with inheritance of other classes
and you can create members (self-owned variables, which are objects too).
Sometimes it is very hard to DESIGN when contain, and when inherit.
This will help you in most of the case:
"inherits" means " is a sort of "
"contains (members)" means "have some "

Not as before, I will comment the code directly inside the snippet, as there is a lot to say!
So, as I want to create a skeleton, how object-orientation can change my way of coding ?
This is my design:
I propose to create an Avatar object. It contains Limbs.
There are two sort of limbs, arms and legs.
Each of those are basically a list of joints.
Here's the snippet (do not care for lame-algorithms, a more completed version will follow in next post)

import math
import maya.OpenMaya
import pymel.core


class Avatar(object):  # Also when desribing your mother-of-all class, always inherit from the python class-object (you will benefits of some built-ins)
    """ This object is a rigged-skeleton representation of a character """

    def __init__(self, _Size=1.70, _NbArm=2, _NbLeg=2, _hasTail=False):
        self.Size = _Size  # How tall it is. Float,  in meters

        self.LimbList = []   # Limb Lists
        self.ArticulationList = []   # All Joints Lists
        self.Hips = None  # Quick Access shortcut
        self.NeckBase = None  # Quick Access shortcut

        self._createTrunk()  # We can call an method into the inialisation process of our Avatar instance.

        RelativePosition = maya.OpenMaya.MVector(math.cos(0), 0, math.sin(0))
        currentArm = Arm("Left", self.NeckBase,  RelativePosition * 0.2)  # Here we create a new instance of class Arm, and Arm-Object.
        self.LimbList.append(currentArm)

        RelativePosition = maya.OpenMaya.MVector(math.cos(math.pi), 0, math.sin(math.pi))
        currentArm = Arm("Right", self.NeckBase,  RelativePosition * 0.2)
        self.LimbList.append(currentArm)

        RelativePosition = maya.OpenMaya.MVector(math.cos(0), 0, math.sin(0))
        currentLeg = Leg("Left", self.Hips,  RelativePosition * 0.2)
        self.LimbList.append(currentLeg)

        RelativePosition = maya.OpenMaya.MVector(math.cos(math.pi), 0, math.sin(math.pi))
        currentLeg = Leg("Right", self.Hips,  RelativePosition * 0.2)
        self.LimbList.append(currentLeg)

    def _createTrunk(self):
        """ Will create the trunk of the body """
        HipsPos = maya.OpenMaya.MVector(0, self.Size * 0.4, 0)
        SpineOffset = (self.Size - HipsPos.y) / float(8)

        for increment in range(8):
            pymel.core.general.select(clear=True)   # We do not want Maya to create fuzzy stuff with active selection. So before each creation we clear the selection.
            currrentArticulation = pymel.core.nodetypes.Joint(name="Spine_" + str(increment + 1).zfill(2))
            currrentArticulation.setTranslation(maya.OpenMaya.MVector(0,  HipsPos.y + increment * SpineOffset, 0))

            if self.ArticulationList:  # Not the first joint, we have to connect it to the previous on...
                pymel.core.connectJoint(currrentArticulation,  self.ArticulationList[-1],  parentMode=True)  # Remember,  -1 is the index of the last member of a list.
            else:
                self.Hips = currrentArticulation

            if increment == 6:  # The last two bones are Neck and Head, so Arms takes their root from bellow.
                self.NeckBase = currrentArticulation

            self.ArticulationList.append(currrentArticulation)


class Limb(object):
    def __init__(self, _Name):
        self.ArticulationList = []  # Same name as in Avatar member, but 'self.' indicates the ownership of this class instance. We're deadling with two very different variables so.
        self.Name = _Name


class Arm(Limb):
    def __init__(self, _Name, _NeckBase,  _Offset):
        super(Arm, self).__init__("Arm_" + _Name)  # Inheritance in python is not automatic. Super (Mother) class of Arm is Limb, and its methods are callable form Arm instance, but it needs the keywords super to do that.
        BasePosition = _NeckBase.getTranslation(space='world')

        for i in range(3):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))
            currentArticulation.setTranslation(BasePosition + _Offset * (i + 1))
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation,  _NeckBase,  parentMode=True)
            else:
                pymel.core.connectJoint(currentArticulation,  self.ArticulationList[-1],  parentMode=True)
            self.ArticulationList.append(currentArticulation)


class Leg(Limb):
    NbArticulation = 3

    def __init__(self, _Name, _HipsBase,  _Offset):
        super(Leg, self).__init__("Leg_" + _Name)
        BasePosition = _HipsBase.getTranslation(space='world')
        previouslyCreated = _HipsBase
        for i in range(3):
            pymel.core.general.select(clear=True)
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))

            if(previouslyCreated == _HipsBase):  # First joint
                currentArticulation.setTranslation(BasePosition + _Offset, space='world')
            else:
                InverseLerp = (1 - (i / float(2)))   # Lerp is the transpose of a range FROM [min = >max] TO [0 = >1]   # Arm.NbArticulation-1 because we do not care about the first Articulation
                currentArticulation.setTranslation(maya.OpenMaya.MVector(previouslyCreated.getTranslation(space='world').x, previouslyCreated.getTranslation(space='world').y * InverseLerp, previouslyCreated.getTranslation(space='world').z), space='world')

            pymel.core.connectJoint(currentArticulation,  previouslyCreated,  parentMode=True)
            previouslyCreated = currentArticulation
            self.ArticulationList.append(currentArticulation)

I hope you had catch it. Now let's manipulate that:


JohnDoe = Avatar()  # This variable aim to a newly created object, typed from class Avatar.

#As in pymel, you no longer need to find Hips by name and fit a naming-convention.

JohnDoe.Hips.setTranslation(maya.OpenMaya.MVector(1,2,3)) #Move hips

#Now, let's select both hands:
pymel.core.general.select(clear=True)  
for currentLimb in JohnDoe.LimbList:
    if type(currentLimb) is Arm:
        pymel.core.general.select( currentLimb.ArticulationList[-1],add=True )

See how Object-Orientation is more about giving sense to a bunch of integers and strings ?
But this backside of the medal is this design thing. Sometimes you don't need to deeply create a very precise hierarchy of objects.
Worse, you can have a big change in your framework (like a new Software in the pipeline), which needs you to re-think your all classes...
As this concise example lacks of real interest, the next post will go (a little) further into Avatar creation.

notice
Tagged as: , , , 1 Comment
tools
20Jul/120

APyTA_15 : OBJECT Part I, Everything is an object, and Object is everything, and…

APyTA_15 : OBJECT Part I, Everything is an OBJECT is Everything...

The golden rule in python is this. Everything is an object, so without fully understand it, you already use it a lot.
You may also already known that python is an object-oriented language.
It is time to define this notion right now.

A class is a definition of a template, describing a structure. A type.
An object is an instance of this class, its application into a 'real' case

MyVar = "lalo"
type(MyVar) # Result: <type 'str'> #
MyVar.__len__() # Result: 4 #
id(MyVar)# Result: 875904576L #
MyVar.replace("la","ri") # Result: 'rilo' #

Looking at this little piece of code, you have to understand that the type statement will return the name of the class, given the object.
Every time you create a variable, using the = operator, python will store somewhere in RAM (indexed by id() ) a copy of the class definition, with your given values.
This is the key concept here, the instantiation. And forget about Maya Instances, the concept here is different.
To take a more accurate example, Instantiate a class into a variable is like copy/paste a template folder, where you already put some subFolder/subFiles to avoid to do that every time.
Storing a named variable with a typed-value is precisely instantiate a class, given its template as a type, to create a new object.

The next notion, and sorry again, some vocabulary to learn, is to know what classes are made of.
Basically, they are a list of Members and Methods.
Members are local variable, created in the scope of the instance of the class, and refers to a value (could be a built-in, as int or string, but could be any other classes too).
Methods are like members, created locally in the object-scope, but they are functions defined with a def statement, they have a scope,  and can return value.

Python does not comes with built-in variables type to manage Vectors. Maya does, but forget that and focus on how to do some calculus with vectors.
First, you can create a variable, setting it with a tuple of three floats, like MyVector = (1.2,3.4,5.6). But you will very soon have readability issues to access the Y axis by MyVector[1] ...
Secondly, you will have to create a lot of calculus functions, and every time you will need to assert your arguments, because they will work only for your specific tuple.

import math
class APYTA_Vector():

  def __init__(self,_x=0.0,_y=0.0,_z=0.0):
    self.x=_x
    self.y=_y
    self.z=_z

  def getLength(self):
    return math.sqrt( self.x*self.x + self.y*self.y + self.z*self.z)

  def Normalize(self):
    InvLength = 1 / self.getLength()
    self.x *= InvLength
    self.y *= InvLength
    self.z *= InvLength

print APYTA_Vector() #<__main__.APYTA_Vector instance at 0x000000000CF23B88>
MyVector = APYTA_Vector(1,2,3)
print MyVector.y #2
print MyVector.getLength()#3.74165738677
MyVector.Normalize()
print MyVector.x, MyVector.y, MyVector.z #0.267261241912 0.534522483825 0.801783725737
print MyVector.getLength()#1.0

First, we can see here the class is defined by the keyword class. I usually had a prefix to my classes, because clashes can occurs, and that signed them explicitly.
The members of the class are defined into a very specific function (a Method) __init__(self). Here I setup three members, initialized at 0.0
Remember well the self , which is mandatory to be the first argument, and it explicitly refers to the Instance of the class (the object) .
This __init__ function is not really a constructor like in other languages (if you omit it, your object still exist), but it is used for initialization, declaration of members.

Like __init__, you can define any functions, with def statement, into the scope of the class. It is exactly like a function, living in the scope class, and having a first mandatory argument self.
Here, the method getLength can return an evaluation of all the object members, and see how referring to it is so neat : MyVector.getLength() .
This is the "object revolution" in coding. You will start to forget about processes that take "that and that" and return its calculus.
You will begin to manage object of meaning. Define functions (Methods) that just make sense.

Finally, see how I can modify the members of my object into a method, like in Normalize() ?
Nothing is returned, but the values (members) of my current instance are updated according to the processes of Normalization.
It is the very same thing of a list.sort() for example.

suggest
27May/120

APyTA_14: Most used modules, Exp Files management.

APyTA_14: Most used modules, Exp Files management.

The os module, for Operating System, is a module that helps you deal with your filesystem.
A sub-module, called os.path, will be dedicated to the path resolving and manipulation.
http://docs.python.org/library/os.html and http://docs.python.org/library/os.path.html

You will see in the os doc a lot of low-level functions, because python handle very well the kernel of the file system.
But we do not need that depth, often so let's have a look first about how dealing with files easily:

FILEPATH_ASSTRING=r"F:\PythonCourses\PYTHON_TEST01.txt" #For this test please enter here a path where you have the right to create files.
myFile = open(FILEPATH_ASSTRING,'w')
print type(myFile)#<type 'file'>  Another python object, called a File Descriptor
myFile.write("line1\n")
myFile.writelines(["line2\n","line3\n"])
myFile.close()
print open(FILEPATH_ASSTRING,'r').readlines() #returned as a list of string (lines)
#['line1\n', 'line2\n', 'line3\n'] (to check use REAL notepad, like notepad++, forget the crappy one in windows)

Manage a file with the open built-in function, giving a path (remember the string format for path).
Your variable will not be the string stream, it is an handle, a file - descriptor. Take that object like a cursor into your text editor.
Notice also the second argument. You can open a file in ONE MODE ONLY. 'w' is for write, and if anything in the path already exists, it will purely vanish into the warp !
So be very careful in which mode you have to deal with your file ('w' is write, 'r' for read, 'a' for append).

I hardly show you a full example here, because it will require involving your filesystem, so let's enumerate the most useful functions:
os.mkdir(path) #Create the directory at path (but the sub-path must exist)
os.makedirs(path)#Create the directory at path, and create all parent folder if the sub-path does not exist !
os.rename(OldNamePath, NewNamePath) #Rename the file (but can not change its path).
os.listdir(path) #Get all direct children of current Folder. Could be files or sub-folders !
os.remove(path) #Delete a file, do not work if path is a directory!
os.rmdir(path) #This remove a directory, but only if it is empty
os.chmod(path,mode) #Change the attribute of the file at path (use to put a file in read-only)
os.chown(path, uid, gid) #Change the owner of the file at path (give it to root for example).

import os,os.path
print os.environ #Will display all you need to know about your current machine (USERNAME,COMPUTERNAME,USERDOMAIN, etc.)
myTempFolder = os.environ['TEMP'] #For me it is : 'C:\\Users\\Username\\AppData\\Local\\Temp'
if not os.path.exists(myTempFolder):
  os.makedirs(myTempFolder)
for currentItem in os.listdir(myTempFolder):
  if os.path.isfile(myTempFolder+"\\"+currentItem): #Warning currentItem is JUST the file name.
    print currentItem

The os.path module is a tool to analyze the string as a path one, and the exists function is really mandatory to safely consider the file or dir.
FILEPATH_ASSTRING=r"F:\PythonCourses\PYTHON_TEST01.txt" #remember the r to express the string as RAW ?
os.path.exists(FILEPATH_ASSTRING) # Result: True #
os.path.basename(FILEPATH_ASSTRING) # Result: PYTHON_TEST01.txt #
os.path.dirname(FILEPATH_ASSTRING) # Result: F:\PythonCourses #Returned value is full path of the directory!
os.path.getsize(FILEPATH_ASSTRING) # Result: 18L #  In Bytes
os.path.isdir(FILEPATH_ASSTRING) # Result: False #
os.path.splitdrive(FILEPATH_ASSTRING) # Result: ('F:', '\\PythonCourses\\PYTHON_TEST01.txt') #
os.path.splitext(FILEPATH_ASSTRING) # Result: ('F:\\PythonCourses\\PYTHON_TEST01', '.txt') #
os.path.splitunc(FILEPATH_ASSTRING) # Result: ('', 'F:\\PythonCourses\\PYTHON_TEST01.txt') #   The first tuple will be the start of a UNC path like \\ServerName\...

If you want to copy a file very easily, use the shutil module which can do that for you:
http://docs.python.org/library/shutil.html

import shutil
shutil.copy(SourcePath, DestinationPath) #Copy the file from source to destination
shutil.copy2(SourcePath, DestinationPath) #Idem, but also copy the stat ! (owner, creation date, etc.) A perfect clone!
shutil.copytree(SourcePath, DestinationPath) #Can duplicate recursively a whole filesystem hierarchy.

At last, but not least, os.walk is a very powerful function to explore recursively your filesystem.
Here is the snippet:

fileList=[]
rootdir = os.environ['TEMP']
for root, subFolders, files in os.walk(rootdir):
  for currentFile in files:
    fileList.append(os.path.join(root,currentFile)) #You can use os.path.join instead of the operator +"/"+
import pprint
pprint.pprint(fileList) #Lots of crap right ?
service
information