mercredi 7 décembre 2016

Python et 3ds Max... Et si c'etait pas si mauvais.




















 







Avec des logos aussi similaires on aurait pu croire à une rencontre fusionelle! Ah non, et en plus 3ds max à change de logo...


Hello à tous!


    Cela fait trop longtemps que je vais à reculons pour mettre mon nez dans le python de 3ds max... et bien voila! c'est fait ! et je vais vous en dire deux mots.
Le MaxPlus est apparu dans la version 2014, il est complete de version en version. 
MaxPlus c'est le module python de 3ds max, et c'est qu'un seul et unique gros fichier python! (déjà ça part mal vous aller me dire).
Je n'ai jamais voulu m'y plonge déjà parce que MaxPlus est trop eloigne du max script (on verra pourquoi et pourquoi c'est une bonne chose) et que de faire des choses simples me paraissait trop compliquer, surement par manque d'expérience dans la programmation. De plus je ne m'intéressais pas d'aussi pret à la programmation oriente objet. Et la pire des raisons pour que les gens ne se lancent pas dans le MaxPlus c'est le manque d'informations, la doc est aussi vide que Myspace et il y a très peu de tuto sur la toile, heureusement il y a quelques exemples, bref la communication et la documentation sont les gros points noirs du MaxPlus.

Donc voilà, ici, je pose ma brique à édifice pour y remédier.

    Le premier point que j'aimerais éclairer c'est qu'il ne faut pas chercher à se rapprocher du max script comme le fait le python et le mel, par exemple pour Maya. Vous retrouverez quelques noms de fonctions similaire, evidement, mais la logique n'est pas la même. LA raison : c'est que MaxPlus est base sur l'API C++ de 3ds Max. Et quand j'ai compris ça, les portes de mon petit cerveau se sont ouvertes.    En fait, MaxPlus est ce que l'on appelle un wrapper, il essaie tant bien que mal à "traduire" le C++ en Python. Pour faire ça les Monsieurs d'Autodesk ont choisi d'utiliser le programme swig. C'est surement pour cette raison que des choses maladroites sont faites comme l'utilisation d'un seul fichier, des classes servant de namespace, des méthodes converties en fonctions où tout un tas d'autres choses... mais qui au fond nous empeche pas d'utiliser MaxPlus.

    Ayant compris ça il est donc possible de lire la doc C++ et de s'y retrouver en MaxPlus. Nous retrouverons les "interfaces" C++, plus simplement dit les "objets, comme les INodes, Animatable, FPInterface... À la différence avec le max script où nous ne retrouvons pas de manière transparente ces objets. L'exemple flagrant est la création d'une teapot, en maxscript le mot "teapot" suffit à faire apparaitre une teapot dans le viewport, en MaxPlus il va falloir deux lignes de code mais derrière ces deux lignes il y a un concept simple de la 3d : les notions de node et d'objet referent. Alors que le maxscript jou la boîte noire sur ce genre de chose (ce que veut faire le nouveau pymx 2017) 
 
Un autre element fort intéressent est l'intégration de PySide, ce qui change pas mal la donne par rapport au Maxscript (sans .net). Avec PySide, le wrapper python de Qt, nous allons pouvoir faire des interfaces vraiment pousse. Par contre... il semble qu'avec des interceptions (de click, touches, move...) un peu pousse 3ds Max perd le contrôle et la navigation dans le viewport devient impossible.

    Malheureusement il manque pas mal de choses essentiels dans Max plus. Comme par exemple l'interface skinOps qui permet d'utiliser efficacement le modifier skin. Un petit éditeur integre à Max pour pouvoir taper quelques lignes a le vole. L'implementation des types python, les INodeTab, les tableaux de INode, sont partiellement implémentés en liste python; maliste[-1] nous retournerons un Index Error Index out of range... ce qui oblige à faire une petite liste compréhension pour convertir l'INodeTab en une liste python de INode. Ne possède pas de moyen simple de lister tous les objets de la scène. Ou encore l'utilisation de getter / setter en plus des properties classique, par exemple:



INode.name #retourne name

INode.name = mon_nouveau_name #set un nouveau nom

INode.GetName() #retourne name

INode.SetName(mon_nouveau_name) #set un nouveau nom

   De mon point de vue MaxPlus semble assez prometteur malgré tous ses défauts et j'ose espérer que cest sur la bonne voie. Ce qui me fait le plus peur c'est Autodesk... et que les dernières maj de python max de la version 2017 sont loin d'etre ce que j'attendais ....















lundi 14 novembre 2016

Le retour du node graph ! ... en PySide.

Vous vous souvenez que j'avais attaqué y a quelques années la conception d'un node graph view en pyqt parce que impossible de trouver une librairie pyqt/pyside pour faire "facilement"  ce genre de choses...
http://unblogdecolin.blogspot.fr/2013/07/pyqt-qgraphicsview-la-base-pour-un-node.html
  bref j'ai laissé tombé et le projet a était avorté.  
Jusqu'au jour où j'ai discuté avec mon ami Frangipane qui a rencontré le même genre de problématique,  sauf que lui a réussi à se lancer sur la conception d'une lib pyside pour faire des graph views qui claques.  Il a déjà fait une petite base bien rodé mais loin d'être implementable pour le moment et la Frangipane commençait à s'essouffler.  
Du coup je me suis incrusté sur son projet et nous avons décidé de relancer la machine, on a chauffé les github et me voilà  en train de forker de la Frangipane à coup de push request pour faire évoluer ce projet avec lui, parce que bazar nous avons tous besoin d'une bonne lib pyside pour faire des node graph !  

Une petite  preview en image:

mardi 25 octobre 2016

Marilla api update

Petites mises à jour du module gueguetags:
https://github.com/col-one/marilla



Désormais il est possible d'ajouter aux Guerilla tags par défaut les parents des meshs sélectionnés et de choisir la profondeur.
GuegueTags prend comme argument par défaut grp=0, sa valeur précise la profondeur.
-1 infinit
0 pas de parents
1 premier parent
2 premier et second parents
3 premier, second et troisième parent
....

vendredi 21 octobre 2016

Marilla : Alembic Tags Workflow pour Maya / Guerilla Render





Il existe déjà une passerelle maya / guérilla développée par les messieurs de Guerilla, elle fonctionne très bien mais elle est très peu flexible et permet pas d'avoir un workflow de production d'Anim. Heureusement l'alembic est là et le système de référence de guérilla fonctionne très bien.


En utilisant l'outil Marilla nous allons pouvoir mettre en place un workflow de référence entre Maya et Guérilla. Comme celui-là :


   

    L'utilisation des tags a beaucoup d'avantages, cela permet de ne plus être dependant d'objets, de meshs, de fichiers ... mais simplement d'un tag. Comme dans le graph ci-dessus la même série de tags va être utiliser pour shader les alembic du modeling pour le lookdev et les alembics de l'Anim pour le rendu final. Nous utilisons plus des objects mais des etiquettes. Là où dans un workflow classique il aurait fallu réassigner les matériaux aux alembics d'Anim. Un des autres avantages c'est l'utilisation de tags transverses, comme par exemple faire des tags "peaux", "métal", "mate" ou "shiny" ...



 Dans guérilla les préfixes des références peuvent être une manière de donner un espace à nos tags par exemple:


Objet1.abc:

    mesh:
        tags:
            -tete
            -corps
            -peau
            -pied

Objet2.abc:

    mesh:
        tags:
            -tete
            -corps
            -peau
            -pied



    Lorsque l'on importe une référence alembic dans guérilla il propose de le préfixer, par défaut par le nom de du fichier, ici nos nodes auront comme nom Objet1: mesh et Objet2: mesh.

Si on importe des Renders Graph différents mais qu'ils possèdent des tags de mêmes noms il va y avoir conflit, c'est là qu'interviennent les préfixes. 
Dans les proprietes du Render Graph il y a un parametre qui s'appel 'apply on': 'tags' et dans le champs text en dessous il faut preciser le prefix 'Object1:' pour le Render Graph de l'objet 1 et 'Object2:' pour le Render Graph de l'object 2. Ainsi les tags du même nom s'appliqueront bien dans leurs espaces respectifs.
préfixe, attention au :
On peut aussi préciser des tags à la place d'un préfixe, le Render Graph s'appliquera que sur les objets qui possèdent le tag en commun. ex:

Objet1.abc:
    mesh:
        tags:
            -toto
            -tete
            -corps
            -peau
            -pied

Objet2.abc:
    mesh:
        tags:
            -tata
            -tete
            -corps
            -peau
            -pied

Ici pour le Render Graph Objet1 il faudra préciser "Toto" et pour celui d'Objet2 il faudra préciser "Tata"




tag

Encore un autre avantage des tags c'est qu'on peut les combiner et faire des opérations boolean dessus. Un objet qui possède ce genre de combinaison :





    Nous remarquons les signes plus et moins. Dans ce cas l'objet sera trouvé par tous les tags "foot, Head, Tata, Toto" sauf "novisible".
Très utiles pour les retake et tweak de fin de RenderGraph.



Voilà je vous ai presente un workflow et survolle l'utilisation des tags.
Maintenant nous allons voir l'outil Marilla qui permettra d'optimiser l'utilisation des tags de Maya vers guérilla.





Install:

Telecharger le repo Marilla sur github:
https://github.com/col-one/marilla

Placer le dossier marilla dans un répertorie qui sera source par Maya, PYTHONPATH, MAYA_SCRIPTS_PATH...


Dans le dossier marilla nous pouvons trouver un fichier userSetup.py il faut ajouter les lignes de codes presentent dedans a votre propre fichier userSetup.py si nous en avons pas nous pouvons directement copier le userSetup.py dans le bon dossier, par default cela sera:
  • Windows: :\Documents and Settings\\My Documents\maya\\scripts
  • Mac OS X: ~/Library/Preferences/Autodesk/maya//scripts.
  • Linux: ~/maya//scripts.

Le userSetup.py permet d’exécuter du code lors de l'ouverture de maya, ici ca permettra d'ajouter un menu 'marilla' a la main window.

Si tout va bien nous devrions avoir ca:



    Du cote Guerilla, il va falloir copier un fichier lua dans un dossier UserPlugins. Dans le dossier marilla / guerilla nous copions le fichier openPort.lua et le collons dans un dossier UserPlugins. Si nous n'avons pas définit de UserPlugins dans le fichier guerilla.conf nous pouvons le coller dans le dossier plugins de l'install de Guerilla.

    Toutes les fonctionnalités se trouve dans maya. 

Auto Tags, nous sélectionnons l'ensemble de nos géométries (mesh) ou nos cameras et l'exécutons, cela va taguer les transform de nos mesh avec son nom et son top parent. 

Remove All Tags, permet de supprimer tout les tags d'une sélection de mesh.

Add / Remove Tags, permet de venir ajouter des tags custom, comme on a vu plus haut les fameux tags transverses. Il faut rentrer le nom du tag dans le champs text et faire Add ou Remove. Il y a la possibilité d'ajouter ou supprimer plusieurs tags a la fois en séparant les noms par une simple virgule, 'metal,#cccccc,blured...'

Export, permet d'exporter la sélection en alembic avec les tags Guerilla et un ensemble de presets :
    -ogawa
    -world
    -uvs
    -faces groups
Attention pour avoir un bon export il faut sélectionner que des transforms si ils sont dans un group il faudra sélectionner le group. 
Dans le popup nous préciserons nos paramètres d'export. Range Mode, PreRoll... 
Et si nous checkons 'Export to guerilla' cela enverra directement notre alembic dans un Guerilla préalablement ouvert. 


Et pour finir nous avons a disposition une petite API pour les gens qui souhaite ajouter ces fonctionnalités a leur propres pipe / outils ... l'help est dispo en depuis le menu marilla dans maya.





La partie Guerilla est très succincte et mériterai une amélioration comme pouvoir proposer l'export dans un nouveau fichier Guerilla, ou encore pouvoir remplacer la référence si elle existe déjà dans la scène Guerilla...

vendredi 16 octobre 2015

INDICATORZ : Demistification du knob("indicators")





    Hey me voila de retour ! Ca va faire plus d'un an que je n'ai pas ecrit de topic. De plus j'ai plein de suites et correction a ecrire (Blink, NodeGraph...)  mais je suis une grosse feignasse donc ce n'est toujours pas fait...

    Ce topic va traiter du knob indicator qui est present dans tous les nodes nuke et qui permet de gerer les petites vignettes de couleur sur le node comme le V ou le X...

Une vrai guirlande!

    Mais derriere tout ca nous allons surtout comprendre comment marche un systeme de flag comme celui-la et pas que dans Nuke (dans le monde du developpement, tres utilise dans Unity par exemple).


ICI les differentes siginification de chaque indicateur.

Les indicateurs sont generes par un knob de type float (Array_Knob). Si je demande la valeur du knob indicators de ce node :







 
nuke.toNode("NoOp").knob("indicators").value()
# Result:
33.0

Donc nous en concluons que les vignettes V et A correspondent a la valeur 33, si je prend n'importe quel node et que je fais
 
nuke.toNode("Nawak").knob("indicators").setValue(33)

Le node Nawak se verra attribuer ces deux vignettes, si avant il possedait d'autres vignettes elles disparaitront pour etre remplace par V et A.
Attention a chaque knob changed la valeur du knob indicator est reinitialise donc dans notre cas V et A disparaitront.


Nous comprenons que chaque vignette a une valeur definit, voici leur valeur :

A  :  1
E  :  2
M  :  4
C  :  8
X  :  16
V  :  32

Nous remarquons que chaque valeur est une puissances de deux et mieux encore ca nous fait penser a un codage binaire, en bit.

Eh bien ce n'est pas pour rien ! c'est la base du systeme de flags. Dans nuke une vignette/indicateur est un flag.
Grace a ce systeme nous pouvons genere des combinaisons de flag a partir d'une seul valeur. Et meme mieux, nous pouvons tester la presence d'un flag dans cette seul valeur, savoir si l'indicateur V est present sur mon node.

Avant de savoir faire tout ca, nous allons voir en peu de binaire.
Nous allons convertir nos flag en binaire :

Flag  :  Valeur  :  Binaire
A      :       1      :      1
E      :       2      :       10
M     :       4      :       100
C      :      8       :       1000
X      :     16      :       10000
V      :      32     :       100000


Maintenant imaginons que nous voulions un ensemble de flags de type E M X :
(petit rappel 1 + 0 = 1, 0 + 0 = 0, 1 + 1 = 1)

EMX  :   22    :               10
                                     100
                                 10000
                              = 10110

La valeur binaire de la somme de nos flags est 10110.
Grace a cette somme nous allons pouvoir tester la presence d'un flag. Est ce que dans ma somme de flag 22 (EMX mais nous ne sommes pas sense le savoir) il y a present le flag M qui a comme valeur 4 ?
Pour cela nous allons "comparer" notre somme binaire de maniere bit a bit avec la valeur binaire du flag que nous cherchons, a l'inverse d'une somme classique 1 + 0 = 0 des lors qu'il y a le presence d'un 0 la somme sera 0. Pour symboliser cette "comparaison" bit a bit nous utilisons &.

EMX & M
22 & 4
10110 & 100

    10110
& 00100
=  00100

Le resultat est 00100 soit 4 soit notre flag E, il est bien present dans nos flags. Si la sommes bit a bit n'est pas egale (ou = 0) au flag recherche alors il n'est pas present dans notre ensemble de flags.

Autres exemples :
EMX & M
22 & 4
10110 & 1
    10110
& 00001
 = 00000

Le flag A n'est pas present.

En programmation les operateurs bit a bit s'appel bitwise symbolise par & pour la "comparaison". (il existe bien d'autres operateur bitwise mais cela ne nous regarde pas ... ).

Un petit exemple concret d'utilisation dans nuke:
Nous allons chercher tous les nodes qui possedes des knobs de stereos, c'est a dire qui possede l'indicateur V soit la valeur 32.
Attention les & bitwise fonctionnent avec des Integer.

 
stereo_nodes = []
view_flag = 32
for node in nuke.allNodes():
    flags_value = 0
    try:
        flags_value = node.knob("indicators").value()
    except AttributeError:
        print "this node doesnt have indicators"
    if int(flags_value) & view_flag != 0 :
        stereo_nodes.append(node)


Pour etre safe j'ai mis un try except mais sachez que tout les nodes ont un knob indicator, il est meme possible de mettre des indicateurs sur des dot ou des backdrops =))).

STYLAYYYY


















lundi 10 novembre 2014

Nuke Tuto Blink Script, la base.


    Passons a notre premier BlinkScript!

Intro du tuto : http://unblogdecolin.blogspot.fr/2014/11/tuto-blinkscript-intro.html

A travers c'est différents articles nous allons pas innover dans le processing d'image au contraire nous allons voir des choses qui existent déjà mais qui vont nous permettre d'apprendre les bases du BlinKScript pour pouvoir plus tard se prendre la tête sur des nouveaux effets ...

    Pour ce premier BlinkScript nous allons simplement additionner les pixels a une valeurs quelconque. Nous verrons la structure de base, les inputs / outputs et la fonction process().
Nous commençons par créer un nouveau node BlinkScript et nous supprimons l’intégralité du code déjà présent dans l’éditeur de texte.

    Première chose a faire c'est de créer un objet kernel dans le quel nous allons mettre toutes nos instructions. Cet objet dérive de ImageComputationKernel qui possède deux manières de traiter les pixels : eComponentWise et ePixelWise. Pour ce premier BlinkScript nous allons utiliser eComponentWise.
Pour le moment nous avons ça :


    eComponentWise traite tout les channels d'un seul coup. C'est certainement plus performant mais nous ne pourrons pas faire d’opérations sur un channel en particulier. eComponentWise est parfaitement adapte pour notre premier BlinkScript qui est une simple addition.

    Maintenant que nous avons notre kernel, il faut que nous définissions une entrée et une sortie a l’intérieur. Image et ses différents argument sont la pour ça. Le premier argument définit si l'Image est en lecture : eRead ou en écriture : eWrite. Le deuxième définit le type d’accès au pixel, dans notre cas nous avons besoin d'eAccessPoint qui est la manière la plus simple d’accéder aux pixels nous comprendrons pourquoi dans la suite. Il existe trois autres manières d’accéder aux pixels que nous verrons dans des futurs articles. Il reste un dernier argument qui permet la gestion des bords de nos images : eEdgeClamped permet de répéter a l’extérieure de l'image la valeur du pixel le plus proche du bord, eEdgeConstant va répéter des valeurs de pixel noir 0.0 alors que  eEdgeNone  répète rien et est donc l'option la plus performante. Dans notre BlinkScript Additionator nous avons pas besoin de répéter les bords donc on va utiliser eEdgeNone.
Si nous précisons pas d'argument a notre Image les valeurs par default sont eRead, eAccesPoint et eEdgeNone.
Donc nous allons creer une entree src et une sortie dst, c'est les noms choisis par foundry mais nous pouvons les nommer comme bon nous semble.


    Il nous reste plus qu'a dériver une fonction pour que notre BlinkScript soit opérationnel c'est la fonction process() cette fonction va répéter les instructions, présentes dans son bloc, pixel après pixel et ligne après ligne. Le sens de processing des BlinkScript est toujours d'en bas a droite a en haut a gauche.
Nous créons la fonction void process(){ } a la suite de nos Image :


Dans le bloc de la fonction process() nous allons rentrer toutes nos instructions. Dans notre cas tout tiens en une ligne, j'assigne la valeur du pixel de src + une 0.5 au pixel de dst. En eComponentWise pour avoir le pixel de notre src il faut appeler la fonction src() simplement et de même pour avoir le pixel de notre dst nous appelons dst().
Notre instructions sera :


Nous compilons notre BlinkScript en cliquant sur Recompile, nous connectons une image a notre input src, un viewer sur notre BlinkScript  et nous pouvons voir toute notre image être additionne a 0.5, whouaa!.

    Nous le répétons ce BlinkScript ne fait absolument rien de fou mais c'est la meilleur façon de voir comment fonctionne un BlinkScript. En resume, pour que notre BlinkScript fonctionne il lui faut une entre et une sortie et une instruction dans process(). Il faut voir la fonction process() en peu comme une double boucle for :

for(y=0; y‹h; y++){
  for(x=0; x‹w; x++){
    ...
  }
}

Elle va parcourir tout les pixels de l'image et appliquer ses instructions sur chacun d'eux.

    Concrètement pour que notre BlinkScript soit facile utiliser il lui manque un knob pour que l'utilisateur puisse changer la valeur a additionner de manière interactive et non en changent le code. Nous verrons comment procéder dans un prochain article.

a++


C.


Nuke Tuto Blink Script, intro.







English version, here.


     Depuis la version 8 de nuke the foundry nous propose une nouveauté qui est le node de BlinkScript.
 Le node de BlinkScript a comme fonction de "processer" les pixels d'une image, c'est a dire permettre d'appliquer des calculs et des algorithmes sur chaque pixel d'une image a l'aide d'un langage de programmation. Avant l'existence du BlinkScript ce genre de calcul au niveau du pixel était possible, sans passer par le NDK, avec l'utilisation de la fonction sample() en python mais c'était extrêmement couteux en performance. Parcourir une image pixel par pixel en pyhon peut prendre plusieurs dizaines de seconds voir minutes selon l'utilisation des valeurs du pixel et de la taille de l'image. Grâce au BlinkScript les performances sont bien meilleurs.

    Nous allons rapidement voir comment le BlinkScript fonctionne, il possède un éditeur de texte qui lui permet de recevoir le code qui lui fournit les instructions a exécuter. Une fois ce code rentré nous allons demande au BlinkScript de le convertir et de le compiler en cliquant sur le bouton Recompile ainsi il devient utilisable par nuke. Le langage utilisé pour "coder" des BlinkScript est un langage dit "kernel" avec une syntaxe proche de celle du C++ il sera converti dans un langage adapté a l’exécution de ces instructions soit sur le CPU soit sur le GPU (SMID ou OpenCL). Une fois converti et compile le BlinkScript devient aussi performant, voir plus qu'un node classique code en C++ avec le NDK. Le système de Blink a été aussi intégré aux NDK pour pouvoir développer des nodes C++ et profiter de cette nouvelle technologie CPU / GPU.

    Le réel avantage du BlinkScript est donc sa performance mais il possède quelques mauvais points. Il est bon de les savoir pour comprendre les limites de ce node et ne pas perde de temps a essayer de programmer des choses que le BlinkScript ne pourra pas faire.
    Le BlinkScript travail que sur le pixel, il est impossible de travailler sur un autre contexte. Par exemple ce n'est pas possible de paramétrer la taille d'une image, de changer de frame, de changer les connections du BlinkScript ... Il n'agit que sur le pixel.
    Le BlinkScript peut posséder des Knobs mais leurs valeurs peuvent être attribué qu'a la première exécution du BlinkScript. Au final nous pouvons qu'initialiser les valeurs des Knobs. Il est impossible de changer une valeur d'un knob en cour de processing (calcule de l'image) par contre elles peuvent être lues. Si la valeur d'un knob est changé "manuellement" le processing sera relancé automatiquement avec cette nouvelle valeur.
    Le BlinkScript étant assez nouveau la gestion des erreurs de syntaxe ou de compilation sont souvent flous et nous aide pas beaucoup pour trouver d’où vient notre erreur. Il faut se préparer a passer du temps a debugger notre BlinkScript. Une bonne connaissance en C++ peut être un avantage pour corriger et éviter ce genre d'erreurs.
    Le BlinkScript ne travail que sur les channels R, G, B, et A ce qui nous oblige très souvent a faire précéder le BlinkScript d'un Shuffle pour rediriger les Channels souhaité vers RGBA.
    Nous pouvons en déduire que le BlinkScript est a utiliser principalement dans un gizmo entouré d'une panoplie de nodes pour le préparer et agir sur d'autre contexte que celui du pixel.

   Une fois satisfait du code de votre BlinkScript il est possible de le publier, c'est a dire que nuke va préparer le node a une utilisation en production. Nuke va grouper notre BlinkScript et linker ses knobs sur l'interface du group. Nous avons le choix de crypter le code source pour éviter tout plagia si notre souhait est de garder secret nos supers algorithmes, l’éditeur de texte disparaitra. Si nous décidons de ne pas crypter, le code source sera accessible dans l’éditeur de texte du BlinkScript.

    Voila nous avons fait un rapide tour d'horizon de ce nouveaux node. Son fonctionnement, ses avantages et ses désavantages nous paraissent plus clair. Nous sommes prés a nous lancer dans le développement d'un BlinkScript. Dans les prochains articles nous allons voir comment utiliser les différentes fonctions du BlinkScript a travers plusieurs exemples.

Suite du tuto :  http://unblogdecolin.blogspot.fr/2014/11/nuke-tuto-blink-script-suite.html

A++

C.



    Since version 8 of nuke the foundry proposes a novelty that is the node of BlinkScript.
    The node of BlinkScript as function to process the pixels of an image, that is possible to apply calculations and algorithms on each pixel of an image using a programming language. Before its existence such of calculation at the pixel level was possible, without going through the NDK, with the use of the sample() function in python but it was extremely costly in performance. Browsing image pixel a pixel with pyhon may take severals tens of seconds depending using of pixel values and the image size. Thanks to BlinkScript performance is much better.

    Quickly we'll see how the BlinkScript works, it has a text editor that allows it to receive the code which provides instructions to execute. Once the code has been entered we will ask the BlinkScript to convert and compile it by clicking the button Recompile, so it becomes usable by nuke. The language used to "encode" the BlinkScript is a sort of language called "kernel" with a syntax similar to the C++ then it will be converted into a suitable language to the execution of these instructions either on the CPU or on the GPU (MISA or OpenCL ). Once converted and compiled BlinkScript also becomes effective, like a classic node code in C ++ with the NDK. Blink system has also been integrated into the NDK to develop nodes in C ++ and enjoy this new CPU / GPU technology.

    The real advantage is BlinkScript's performance but it has some bad points. It is good to know to understand the limits of this node and not wast our time trying to manage things that BlinkScript can not do.
   The BlinkScript work on the pixel, it is impossible to work on a different context. For example it is not possible to set the size of an image, change frame, change the connections BlinkScript ... It works only on the pixel.
    The BlinkScript may possess Knobs but their values can only be assigned has the first run of the BlinkScript. Finally, we can just initializes the values of the Knobs. It is impossible to change a value of a knob during processing, in contrast they can be read. If the value of a knob is changed "manually" the processing will be restarted automatically with this new value.
    The BlinkScript being fairly new that's why error handling syntax or compilation are often vague and not much help us to find where our error is. We must prepare to spend our time to debug BlinkScript. A good knowledge of C ++ can be a benefit to correct and prevent such errors.  
    The BlinkScript work on the channels R, G, B, A and so we have very often precede the BlinkScript a Shuffle to redirect to the desired RGBA Channels.
     We can deduce that the BlinkScript is to use primarily in a gizmo surrounded by an array of nodes to prepare and act on context other than the pixel.


    Once satisfied with the code of your BlinkScript it is possible to publish it, that nuke will prepare the node has a production use. Nuke will group our  BlinkScript and link knobs to its interface group. We have the choice to encrypt the source code to avoid plagiarism if our desire is to keep our secret algorithms, the text editor will disappear. If we decide not to encrypt the source code will be available in the text editor BlinkScript.

    Here we did a quick tour of this new node. Its operation, its advantages and disadvantages we seem clearer. We are ready to get into the development of a BlinkScript. In future articles we will see how to use the various functions of BlinkScript through several examples.

The next tuto :    http://unblogdecolin.blogspot.fr/2014/11/nuke-tuto-blink-script-suite.html

A++

C.









jeudi 9 janvier 2014

Ajouter des Widget dans des QListWidget et les appeler


    Voila une petite maniere de creer des widgets dans les lignes dune QListWidget et de pouvoir les appeler pour les utiliser.



from PySide.QtGui import *
from PySide.QtCore import *

class Win(QListWidget) : 
    def __init__(self) : 
        super(Win, self).__init__()

        s = QSize(25, 25)

#On met des widget type QCheckBox dans nos ligne
#de notre QListWidget

    #on rajoute 10 ligne
        for i in range(10) : 

        #On instancie un QListWidgetItem qui contiendra 
        #nos QCheckBox et QSpinBox
            item = QListWidgetItem()
            item.setSizeHint(s)

        #on cree nos widgets
            if i % 2 == 0 :
                cb = QCheckBox("Check Box " + str(i))
                cb.setFixedHeight ( 25) 
            else : 
                cb = QSpinBox()
                cb.setSuffix(" : Spinn Box " + str(i))
                cb.setFixedHeight ( 25)

        #On ajoute notre QListWidgetItem dans une ligne
        #grace a addItem() de QListWidget
            self.addItem(item)

        #Et on ajoute nos CheckBox et SpinBox a nos QListWidgetItem
        #Grace a la methode setItemWidget() de QListWidget
        #Le premier arg est le QListWidgetItem, le second arg est notre
        #Widget, pour nous QCheckBox et QSpinBox
            self.setItemWidget(item, cb)


            self.show()

#On recupere nos widget spinbox et checkbox : 

    #liste tous les items present dans le QListWidget
    #dans notre cas se sont des QListWidgetItem 
        widgetItems = []
        for i in range(self.count()) : 
            item = self.item(i)
            widgetItems.append(item)

    #on liste tous nos widget present dans nos QListWidgetItem
    #en passant par la method itemWidget() de QListWidget()
        checkItems = []
        for it in widgetItems : 
            ch = self.itemWidget(it)
            checkItems.append(ch.text())

            
s = Win()

mercredi 8 janvier 2014

Gizmo Nuke Time Code

    Et voila j'ai enfin fais un node de timecode pour nuke.
Vous pouvez le copier/coller dans votre graph nuke ou en faire un fichier .gizmo :

Gizmo {
 name TimeCode tile_color 0xff addUserKnob {20 "" l Color}
 addUserKnob {41 ramp T Text1.ramp}
 addUserKnob {41 color T Text1.color}
 addUserKnob {41 p1 l "point 1" T Text1.p1}
 addUserKnob {41 color0 l "color 0" T Text1.color0}
 addUserKnob {41 p0 l "point 0" T Text1.p0}
 addUserKnob {20 "" l Node}
 addUserKnob {41 output T Text1.output}
 addUserKnob {41 premult T Text1.premult}
 addUserKnob {41 cliptype l "clip to" T Text1.cliptype}
 addUserKnob {41 replace T Text1.replace}
 addUserKnob {41 invert T Text1.invert}
 addUserKnob {41 opacity T Text1.opacity}
 addUserKnob {26 "" l mask}
 addUserKnob {41 maskChannelMask l "" -STARTLINE T Text1.maskChannelMask}
 addUserKnob {41 maskChannelInput l "" -STARTLINE T Text1.maskChannelInput}
 addUserKnob {41 inject T Text1.inject}
 addUserKnob {41 invert_mask l invert T Text1.invert_mask}
 addUserKnob {26 ""}
 addUserKnob {20 Font n 1}
 Font 0
 addUserKnob {41 font T Text1.font}
 addUserKnob {41 index l "" -STARTLINE T Text1.index}
 addUserKnob {41 size T Text1.size}
 addUserKnob {41 kerning T Text1.kerning}
 addUserKnob {41 leading T Text1.leading}
 addUserKnob {41 xjustify l justify T Text1.xjustify}
 addUserKnob {41 yjustify l "" -STARTLINE T Text1.yjustify}
 addUserKnob {20 "" n -1}
 addUserKnob {20 Transform n 2}
 addUserKnob {41 box T Text1.box}
 addUserKnob {41 matrix T Text1.matrix}
 addUserKnob {41 translate T Text1.translate}
 addUserKnob {41 rotate T Text1.rotate}
 addUserKnob {41 scale T Text1.scale}
 addUserKnob {41 skew T Text1.skew}
 addUserKnob {41 center T Text1.center}
 addUserKnob {20 "" n -1}
}
 Input {
  inputs 0
  name Input1
  xpos 177
  ypos -144
 }
 Text {
  message "\n\[python str(int(nuke.thisNode().knob('hour').value())).zfill(2)]:\[python str(int(nuke.thisNode().knob('min').value())).zfill(2)]:\[python str(int(nuke.thisNode().knob('sec').value())).zfill(2)]:\[python str(int(nuke.thisNode().knob('frame').value())).zfill(2)]\n\n\n"
  font C:/Windows/Fonts/arial.ttf
  size 80
  kerning 0.1
  yjustify center
  box {512 389 1536 1167}
  translate {154 84}
  center {1024 778}
  name Text1
  selected true
  xpos 185
  ypos -71
  addUserKnob {20 User}
  addUserKnob {7 frame}
  frame {{"\[python -execlocal \\nret\\ =\\ \\ nuke.frame()\\ %\\ nuke.Root().fps()]"}}
  addUserKnob {7 sec}
  sec {{"\[python -execlocal \\ns\\ =\\ nuke.frame()\\ //\\ nuke.Root().fps()\\ %\\ 60\\ \\nret\\ =\\ s\\n\\n]"}}
  addUserKnob {7 min}
  min {{"\[python -execlocal s\\ =\\ nuke.frame()\\ //\\ nuke.Root().fps()\\nret\\ =\\ s\\ //\\ 60\\ %\\ 60]"}}
  addUserKnob {7 hour}
  hour {{"\[python -execlocal s\\ =\\ nuke.frame()\\ //\\ nuke.Root().fps()\\nret\\ =\\ s\\ //\\ 3600\\n]"}}
  addUserKnob {41 message_1 l message T Text1.message}
 }
 Output {
  name Output1
  xpos 177
  ypos -4
 }
end_group



mardi 29 octobre 2013

NUKE list all callbacks

   Hey,
It's been a long time i searched how to list all callbacks present in a nuke script...
And now i get it !

We can list them by type like knobChanged or onCreate and so on.

We acces by the callbacks module in .nuke :

 

nukeCallbacks = nuke.callbacks


And then use its variables by the type :

 

nukeCallbacks = nuke.callbacks
allKnobChanged = nukeCallbacks.knobChangeds



Nuke list callbacks in a big dictionary with the node Class as key and its value as a list who contains the function to call and its arguments.
 

nukeCallbacks = nuke.callbacks
allKnobChanged = nukeCallbacks.knobChangeds

# Result: 
{'Read': [(<function readFn at 0x00000000047A3EB8>, (101,), {}, None)], 'Write': [(<function writeFn at 0x00000000047A3D68>, (), {}, None)], '*': [(<function readFn at 0x00000000047A3EB8>, (101,), {}, 'Read1')], 'Roto': [(<function rotoFn at 0x00000000047A3DD8>, (), {}, None)]}

Here some types examples :


 
autolabels = {}
beforeFrameRenders = {}
beforeRenders = {}
filenameFilters = {}
knobChangeds = {}
onCreates = {}
onDestroys = {}
onScriptCloses = {}
onScriptLoads = {}
onScriptSaves = {}
onUserCreates = {}
updateUIs = {}

lundi 22 juillet 2013

PyQt QGraphicsView la base pour un node graph

   
Evolution de la chose : http://unblogdecolin.blogspot.fr/2016/11/le-retour-du-node-graph-en-pyside.html

Ici je vais vous présenter une base pour faire un "node graph" ou viewport 2D... mon exemple est très simple il y a seulement deux éllipses en guise de "nodes",pour l'instant on regarde juste le paramétrage de base de notre QGraphicsView qui est notre support de base, peut être un jour je vous représenterai un ticket pour les nodes et leurs connections.


Un QGraphicsView avec une grille, deux items sélectionnable et bougeable, un lasso de sélection et un alt-drag pour bouger dans la vue.


 
from PyQt4 import QtGui, QtCore


class View(QtGui.QGraphicsView):
    def __init__(self):
        QtGui.QGraphicsView.__init__(self)
        self.resize(500, 500)

#on place le point de pivot des transformation au niveau du pointeur
       self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
       self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
#on cache les scrollBar
       self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
       self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)  
        
#active l’antialiasing
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        
#creation du scene qui contient les items et sera dans la QView
        self.scene = QtGui.QGraphicsScene()
        self.scene.setSceneRect(-50000, -50000, 100000, 100000)

        
#Ajout d'une grille en fond
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(60,60,60), QtCore.Qt.CrossPattern))
  
#creation de deux elipse simple a la scene, selectable et movable
        ep = self.scene.addEllipse (20, 40, 50, 50)
        ep.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
        ep.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
 
        epS = self.scene.addEllipse (20, 120, 50, 50)
        epS.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
        epS.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
        
#parametrage du drag mode  rubber …. permet davoir un lasso rectangulaire de selection
        self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
        self.setRubberBandSelectionMode(QtCore.Qt.IntersectsItemShape)
        self.setScene(self.scene)
        
        
#alt drag scroll bar, permet de se deplacer dans le graph grace a la touche alt.       
    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Alt:
            self.setDragMode(QtGui.QGraphicsView.ScrollHandDrag)
    def keyReleaseEvent(self, event):
        if event.key() == QtCore.Qt.Key_Alt:
            self.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
            
        
d = View()
d.show()




vendredi 7 juin 2013

Displacement 32bit

   Un simple post sur la découverte d'un lien qui m'a appris, enfin!, a régler correctement une displace avec zbrush et vray. Finit le tatonage du shift ... place a la précision:

http://www.cggallery.com/tutorials/displacement/

mardi 21 mai 2013

joli print avec .center =)

    Petit poste rapide pour vous présenter une façon de faire des prints ou commentaires de manière cool et visible; grâce  a la fonction .center du module string:


 

>>> print "HELLO".center(50, "-")
----------------------HELLO-----------------------

jeudi 9 mai 2013

reseau to local script nuke

   

    Rien de bien étonnant, nuke demande énormément en appels disques et donc sur notre réseau supinfocom, et bien ça rame pas mal !
La solution la plus simple que j'ai trouvé a un mois de la fin et bien c'est de rapatrier toutes nos séquences de rendu en local, rien de bien fou... sauf que je veux garder exactement les mêmes chemins dans mes reads pour pouvoir passer de la séquence réseau a la séquence local juste en changeant la lettre du root (pour notre cas de D: a W:).
De plus j’avais pas envie de perdre de temps a faire ça manuellement donc j'ai écris un script pour automatiser tout ceci.
Voici le script :


 
import os, nuke
import shutil, glob, threading
from PySide import QtGui, QtCore

#creation de la class progress bar en pyside
class progBar(QtGui.QWidget):
    def __init__(self, parent = None):
        QtGui.QWidget.__init__(self, parent)

        self.pBar = QtGui.QProgressBar(self)
        #je set la taille de la fenetre ainsi que sa position dans l'espace
        self.setGeometry(500,500,10,20)
        #je set un flag tophint pour que la progress bar soit toujours en top
        self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) 
        #j'affiche le widget progress bar
        self.show()

    #creation d'une simple fonction pour seter la valeur de la progress bar
    def prog(self, p):
        self.pBar.setValue(p)

        

#creation de la class principal derive du module thread    
class readOnLocalClass(threading.Thread):
    def __init__(self, init = True, parent = None):
        threading.Thread.__init__(self, parent)

        self.init = init

        if self.init:
            self.ini()

        else:
            self.notIni()

    #fonction a executer si l'argument init est True
    def ini(self):

            #ouverture d'une fenetre de dialogue pour selectionner une sequences d'image
            files = nuke.getClipname("files sequences : ", multiple = True)
            #remplacement du root W: en D:
            self.newPathLocal = files[0].replace("W:", "D:")
            #split du path pour avoir que le path
            self.newPathLocalSplit = os.path.split(self.newPathLocal)
            #si le dossier local n'existe pas alors on le cree avec makedirs,
            #makedirs cree de maniere recursive tout l'abre du dossier
            if os.path.isdir(self.newPathLocalSplit[0]):
                pass
            else:
                os.makedirs(self.newPathLocalSplit[0])

            #je liste tout les fichiers present dans le dossier reseau
            self.allFiles = glob.glob(os.path.split(files[0])[0] + "\*.*")
      

            #j'appel la class progressbar et je l'affiche
            self.ProgBar = progBar()
            self.ProgBar.show()

    #fonction a executer si l'argument init est False
    #recupere le path du read selectionner et remplace le D: par W:
    #liste les fichiers reseau a partir du path local
    def notIni(self):
        files = nuke.selectedNode().knob("file").value()
        self.newPathLocal = files.replace("D:", "W:")

        self.allFiles = glob.glob(os.path.split(self.newPathLocal)[0] + "\*.*")
        self.newPathLocal = files.replace("W:", "D:")
        self.newPathLocalSplit = os.path.split(self.newPathLocal)

        #j'appel la class progressbar et je l'affiche
        self.ProgBar = progBar()
        self.ProgBar.show()        

    #derive la fonction run du module thread
    def run(self): 
    
        i = 0
        #tant que i est pas egal au nombre de fichier present dans le dossier reseau
        while i != len(self.allFiles):
            #copie le fichier dans le nouveau dossier local
            shutil.copy(self.allFiles[i], self.newPathLocalSplit[0])
            i+=1
            #calcule du pourcentage pour la progression de la bar
            progr = (float(i)/float(len(self.allFiles)))*100
            #progression de la bar
            self.ProgBar.prog(progr)
        
        print "le thread est finit"
       
        self.ProgBar.close()

        self.creaRead()
        

    #fonction pour creer le node read ainsi que le bouton reload   
    def creaRead(self):
        if self.init:
            #creation du node read
            self.read = nuke.createNode('Read', 'file {' + self.newPathLocal + '}', inpanel = False)
            #creation du knob button reload
            bk = nuke.PyScript_Knob("Reload_Copy")
            #set la command du bouton, ici la fonction reload()
            bk.setCommand("reaload()")
            #ajoute le kno au node read
            self.read.addKnob(bk)

        else:
            print "reload"
            nuke.selectedNode().knob("reload").execute()


       

def readOnLocal():
    b = readOnLocalClass(init = True)
    #lance le thread, la fonction run()
    b.start()

def reaload():
    b = readOnLocalClass(init = False)
    #lance le thread, la fonction run()
    b.start() 


Et en bonus :p le script pour changer de root:

import os
import nuke

#remplace la lettre de disque, demande une nouvelle lettre en str et une list de node read
def changeRoot():
    p = nuke.getInput("Change Root Letter")
    allReads = nuke.allNodes("Read")
    for r in allReads:
        n = r.knob("file").value()
        rr = n.split(":/")
        nn = rr[0].replace(rr[0], p )
        nnP = os.path.join(nn + ":", rr[1])
        r.knob("file").setValue(nnP)


lundi 22 avril 2013

python nuke unpremult for all read and shuffle node

    Voici un petit script qui fait gagner du temps, il crée un node unpremult sous tout les nodes read, shuffle et le script recrée les connections des dependences:


 
#permet de recupere un dictionnaire contenant le node connecte ainsi que l'id de son input
def getOutput(node):
    dep = node.dependent()
    id = []
    for d in dep:
        ip = d.inputs()
        for i in range(ip):
            if d.input(i) == node:
                id.append(i)
    dictNodeInput = dict(zip(dep, id))
    return dictNodeInput


#permet de connecter tout les inputs d'un dict cree par la fonction getOutput() a un node
def setOutput(node, dictInput):
    for e, i in dictInput.items():
        e.setInput(i, node)
    return


read = nuke.allNodes("Read")
s = nuke.allNodes("Shuffle")

for p in s:
    read.append(p)

for r in read:
    d = getOutput(r)
    unp = nuke.nodes.Unpremult()
    unp.setInput(0,r)
    setOutput(unp, d)




mardi 16 avril 2013

Petite astuce dict zip python.


    Voici une petite astuce pour simplifier les attributions "keys", "values" d'un dictionnaire.
Lorsque l'on a deux listes et qu'on veut les combiner dans un dictionnaire on peut le faire sans boucle avec la fonction zip de la class dict. Comme ceci:


 

keys = ['A', 'B', 'C']

values = [1, 2, 3]

dic = dict(zip(keys, values))

print(dic)

>>> {'A': 1, 'B': 2, 'C': 3}




mardi 9 avril 2013

PySide/PyQt QDesktopWidget

Petit pense-bête sur le module Qt QDesktopWidget:


 

#module pour les info ecrans
ff = QtGui.QDesktopWidget()

#recupere les dimension des ecrans.
ff = QtGui.QDesktopWidget().screenGeometry(widget)
#recupere l'index de l ecran ou est place le widget
ff = QtGui.QDesktopWidget().screenNumber(widget)
#recupere le nombre d'ecran du system
ff = QtGui.QDesktopWidget().screenCount()


jeudi 28 mars 2013

Slate Material Editor et Py3dsMax (sme)

    Voici quelques fonctions bien utiles pour scripter avec le slate material editor (sme), je vous les présentes a travers un petit script qui permet de stocker tout les matériaux des objets sélectionnés dans une nouvelles vue du sme.


 

from Py3dsMax import mxs

#je recupere la liste des objets selectionnes:
sel = mxs.selection

#je cree une nouvelle vu et recupere son node:
nodeView = mxs.sme.GetView(mxs.sme.CreateView("stock_view"))

#je cree la boucle qui traitera chaque objets:
for node in sel:
 
    #je recupere le node material:
    mat = node.material
 
    #je creer le node material dans la nouvelle vue, je lui donne une position grace a la fonction point2 vector.
    nodeView.CreateNode(mat, mxs.point2(0,0))
 
    #j'execute une commande maxscript qui permet de mettre en ordre les materiaux dans la nouvelles vue.
    mxs.execute('actionMan.executeAction 369891408 "40060"')



mardi 12 mars 2013

Py3dsMax et les modifiers

Voici les principales fonctions py3dsmax pour travailler avec les modifiers:


 
#import de la library py3dsmax
from Py3dsMax import mxs

#cree une liste des objets selectionne
sel = mxs.selection

#cree un modifier avant l'index 6 (l'index 1 est le modifier le plus haut de la pile) 
#pour avoir un modifier toujour en bas de la pile mettre un grand before (ex: 10000)
mxs.addModifier(sel[0], mxs.Skin_Wrap(), before = 6)

#la liste des modifiers de l'objet
ld = sel[0].modifiers

#recupere un modifier par son nom
sk = sel[0].modifiers["Skin_Wrap"]

#recupere le node d'un objet par le nom
corp = mxs.getNodeByName("name_obj")


jeudi 28 février 2013

Zbrush to Max pourquoi c'est mou ?

    J'ai souvent entendu et eu ce problème, "j'ai sorti une décimation (ou high poly) de zbrush avec mes normales... et quand je rends avec max ma modé est toute mole et je perds beaucoup de détails."

Le problème vient des smooth group, faut savoir que par défaut max import des .obj avec un smooth group commun pour tout les poly. Vous pouvez voir ça dans l'editpoly, face mode, smoothing groups.

Tout les poly on un smooth group a 1.










Donc avant de rendre votre modé sortie de ZBrush pensez bien a "Clear All" les smooth groups.










Voila, c'est bête mais on y pense pas toujours.
En image, un petit comparatif:

 Avec smoothing group a 1.



Sans smoothing group.

La modé reste en peu moche (pas de lighting, shader, texture ..)  mais on peu voir qu'on gagne réellement en dureté et en détails.