Salut tout le monde voila je souhaiterais passer mon petit script de python générator en plugin
http://frenchcinema4d.fr/showthread....=1#post1058314
Pour le moment voila comment fonctionne mon python generator :
Je génère une spline en récupérant la positions des deux nuls que j'ai mis en enfant de mon générateur
Je génère une spline qui va me servir de profil
Je met les deux dans un sweep
Et je fait ça pour X cordes puis je les met dans un nuls et je retourne le nuls.
En le transformant en plugin voila ce que j'aimerai "rajouter".
Création des 2 nuls afin que lorsqu'on sélectionne mon objet dans le manager on puisse sélectionner l'un des deux null dans le viewport
Ajout d'un mode réaliste avec les dynamiques de c4d (en mettant un tag dynamique spline)
Le problème dans la première feature c'est que le plugin dois créer 2 null et les cacher de la liste des objets et avoir une interaction avec eux, l'utilisateur dans le viewport en les bougeant, le plugin en récupérant ces positions.
Es-ce possible vu que logiquement un objet ne peux être qu'un objet, comment utilisé un objet qui n'est pas encore créer?
Pour la deuxième feature je ne sais pas si cela est possible. Vu que ma spline est update (créer) a chaque frame, il est dont re-initialisé à chaque frame et il n'y a pas d'animation.Afin de set les point qui ne bouge pas il faut appuyer sur un bouton (bouton qui n'existera pas car pour que le boutton exisite, il faut que mon objet,ainsi que mon tag contrainte y sois déjà appliqué ^^)
Une autre chose dont je ne suis pas sur, pour store une variable dans mon objet afin de la récupérer d'une frame à une autre je doit créer un BaseContainer puis faire un monObj = monBaseContainer. Es-ce correct? J'ai un peu du mal a comprendre le fonctionnement du basecontainer, ce que c'est réellement.
Donc voila je ne sais pas trop quel type faire pour mon plugin, un OBJECT_GENERATOR, OBJECT_INPUT,OBJECT_POLYGONOBJECT? Quel sont justement la différence entre ces 3 type d'objet car j’avoue ne pas comprendre.
Pour la spline dynamique voici en gros l'idée http://pastebin.com/qv3AY4SZ, sauf que j'aimerais via mon plugin pouvoir mettre a jour les différentes données
Pour gérer les variables dans mon python generator j'ai utilisé les DU mais la comment faire pour faire des options / plusieurs onglet pour mon plugin? Je n'ai rien vu dans le SDK relatant ceci ^^'.
Merci d'avance et j'espère avoir été clair ^^'
Dernière modification par gr4ph0s ; 05/02/2016 à 21h53.
SDK Specialist
MAXON Computer GmbH
C'est très clair !.. mais j'ai rien compris
En tout cas, comme je te l'ai dit, persévère dans cette voie Maxime. C'est bien de voir les petits jeunes du forum se lancer dans le grand bain
kenavo !! // Pinterest KAMIGAZ®
C'est pas très clair ce que tu veux faire en fait avec ton plugin. On dirait que tu veux faire un plugin command.
Tu pourrais peut être le faire comme la commande optimize. Tu peux appeler la commande, mais tu as la possibilité d'ouvrir une fenêtre de paramètres pour la commande. (nombre de points dans la spline, profil ?)
Tu sélectionnes deux objets neutres (obligatoire) que tu as crées, tu lances la commande zoup ça crée une spline, tu bouges tes neutres, tu lances la commande etc etc etc
Ton "workflow" est pas très clair.
Pour essayer de répondre à tes questions sinon :
Pour un générateur tout se passe (ou presque) dans GetVirtualObject(op, hh). Mais il y a les histoires de cache dans c4d. Donc s'il n'y a pas de changement qui influence le générateur, il renvoie le cache. Qui lui a peut être été modifié par le tag dynamique.
Dans la doc il y a un exemple avec le générateur array les objets sont crée avec leur tag qui sont actif. En théorie ça doit fonctionner.
Pour le basecontainer, tu peux voir ça comme un tableau avec des cases qui contiennent des données ou d'autre basecontainer. tous les BaseList2D ont des baseContainer. Ces cases sont identifiées par un ID. Les BaseList2D ne stockent pas leur données obligatoirement dans des basecontainer comme tu peux le voir dans la doc c++ (et si tu remontes plus haut tu verra le schéma qui montre toutes les class qui héritent de BaseList2D)
Un BaseObject hérite des propriétés/méthodes du BaseList2D.
Pour un générateur, l'interface est relié au baseContainer automatiquement. Le baseContainer d'un objet est également automatiquement enregistré quand tu utilises la commande sauvegarde de c4d. (et donc automatiquement chargé)
C'est pour ça que tu peux utiliser le basecontainer de l'objet pour stocker des données. Sauf qu'il faut le faire dans une case qui t'es réservé et donc prendre un ID auprès de plugincafé.
Tu peux également stocker des données dans la class de ton objet. La par contre il faut utiliser les fonctions Read/Write pour sauvegarder/charger les données quand tu utilises la commande sauvegarder de cinema4D.
Sinon tu peux aussi ajouter un champ dans l'interface de ton plugins/outils, le rendre invisible et l'utiliser pour stocker des données. Du coup t'as pas à te soucier du baseContainer. Tout dépends des données que tu veux stocker.
Dans mon plugin py-Timer j'utilise le baseContainer du document pour stocker le temps passé sur un document. CONTAINER_ID est un ID unique récupéré chez plugincafé comme ça j'suis certain que personne n'y touchera.
Mais je pourrais aussi utiliser le CONTAINER_ID pour y stocker non pas un Real mais un BaseContainer. Du coup dans ce sous container je pourrais gérer mes Ids comme je veux.Code:doc = c4d.documents.GetActiveDocument() bc = doc.GetDataInstance() bc.SetReal(CONTAINER_ID, bc.GetReal(CONTAINER_ID)+ self.update_time/1000)
C'est pas facile à expliquer mais en fait tu verra que c'est super simple à utiliser.
Quand tu enregistres ton plugin (RegisterObjectPlugin) tu peux lui spécifier des paramètres, un ou plusieurs.
OBJECT_GENERATOR --> génère un objet comme une primitive cube ou donut.
OBJECT_GENERATOR|OBJECT_INPUT --> génère un objet et a besoin d'un objet en enfant pour fonctionner. Comme l'HN, spline nurb, l'array, le cloner mograph tout ça quoi.
Puis il peut générer un OBJECT_POLYGONOBJECT ou un OBJECT_POINTOBJECT
le pointobject peut être une spline ou un polygon object. C'est le parent des deux autres quoi.
Tu peux donc avoir OBJECT_GENERATOR|OBJECT_INPUT|OBJECT_POLYGONOBJECT
Pour les interfaces dans un plugin tu peux regarder un exemple de la doc ou il utilise le répertoire res avec tous les fichiers qu'il y a dedans.
Pour ce qui est de ton code pour les boucles, quand tu connais le nombre de boucle que tu dois faire, utiliser un For
de plus tu test ton index, s'il est 0 tu fais un vector(0) sinon tu fais le calcul.
Sauf que le calcul avec un index = 0 ça donne 0 donc un vecteur(0)
toute ta boucle peut donc être écrite
ton bout de code segment_length = spline_length / (number_points - 1) vas renvoyer un entier. (chose que tu ne veux peut être pas si ta spline est divisé en nombre de points impair) Donc mets le dénominateur en float(number_points-1) comme ça le résultat de la division sera un float()Code:for i in xrange(number_points): spline.SetPoint(i,c4d.Vector(0,0,segment_length * i))
sinon au lieu de mettre
while index <= (int(number_points) - 1)
tu peux mettre
while index < int(number_points)
C'est comme les sms si tu peux éviter de taper des lettres fait le xD
Le reste du code j'ai pas spécialement cherché plus loin puisque ton "workflow" n'est pas très clair.
ps :
ton code a fait planter mon c4d :'(
Dernière modification par valkaari ; 06/02/2016 à 01h51.
Merci pour toutes ces réponses !
Merci effectivement pour l'exemple du Py-RoundedTube (enfin merci maxon :p) quand je parlais des paramètres c'est exactement ceci que je voyais et j'ai capté comment ça fonctionne !
Pour le GetDeformCache je vais faire des tests mais je pense avoir compris
Pour le baseContainer de ce que j'en ai compris :
- C'est une array, pouvant contenir lui même une array de type BaseContainer ou d'autre variable.
- Chaque objet/générateur à un BaseContainer, contenant les propriétés de cet objet.
- Par défaut C4d save/load les BaseContainer du document et des objets, donc si on a store nos données dans les basecontainer du doc/obj il sera save/load si c'est un fait manuellement alors il ne sera pas save/load? (la je ne suis pas sur ^^)
- Chaque entrée de cette "array" à un ID unique et vu que certaines sont utilisés pour les objets il faut s'assurer d'avoir un ID unique pour le sien.
Pour le 3 je ne suis pas sur d'avoir compris exactement ce qui est sauvegardé quelle est la différence entre la classe de mon objet (BaseObject?) et le "basecontainer de l'objet".
En gros j'ai mon objet (baseObject) je récupère son container via c4d.BaseList2D.GetDataInstance (mais je peux faire container = monBaseObject.GetDataInstance() vu qu'il hérite de BaseList2D ?) et donc sa sera dans ce container qu'il me faudra un ID unique et la où je save mes données (qui elle seront automatiquement save/load à la sauvegarde/chargement de ma scene et donc de mon objet)
Ou sinon je peux faire monBaseObject.Write() vu qu'il hérite de C4DAtom mais la je devrais hook les fonctions de save/load de c4d et je n'aurais pas besoin d'un Unique_ID vu que la je crée un autre baseContainer dans mon objet et je ne suis donc plus embêtée par les valeur qui y sont déjà pré-définis?Es ce bien cela? Comment faire pour faire un Hook sur le save/load ? Et quel est la différence entre Write/Read et WriteObject/ReadObject ?
Pour le petit 4 il y a un truc qui me chiffonne tu dis Donc dans tout les cas il me faudra ou moins 1 Container_ID, dans le premier cas si j'ai besoin de 10 containers, je pourrais les mettre en enfant de mon container qui à un ID unique alors que si j'avais fait ça directement il m'aurais fallu 10 Container_ID c'est bien cela?
Pour les Container_Id sa se recup au même endroit que les plugin_id? http://www.plugincafe.com/forum/developer.asp
Encore une fois merci, tout seul j'aurais galéré a comprendre tout ça !
Ps : pour le code tu as l'erreur?Tu es sur quel version,moi la R16. Car c'est un truc que j'ai arrangé / copier collé vite fait en cours donc c'est fort probable que ça ne sois pas propre. J'ai vu qu'il n'utilisais pas les constantes mais bon moi ça marchais et c'étais plus pour voir si c'étais possible que de faire un truc propre et optimisé (c'est mieux quand ça marche partout et c'est toujours cool d'avoir des retour pour s'améliorer ! )
Dernière modification par gr4ph0s ; 06/02/2016 à 14h44.
SDK Specialist
MAXON Computer GmbH
Salut je vois que tu progresses fort !
Bon déjà pour récupérer ton container tu peux simplement faire objet[ton_no_unique] et pour le renseigner objet[ton_no_unique] =xx
Si tu as besoin de stocker plusieurs valeurs, tu peux tout mettre dans un seul sous-container dans lequel tu es sûr que tous les no sont libres et sur lequel tu n'auras aucun conflit :
J'utilise des constantes juste pour que cela plus lisible, mais c'est pas obligatoireCode PHP:
import c4d
ID_UNIQUE = 12345678
ID_TOTO = 0
ID_TUTU = 1
ID_XX = 2
def main():
bc = c4d.BaseContainer()
bc[ID_TOTO] = 123
bc[ID_TUTU] = "n'importe quoi"
bc[ID_XX] = 2.345
op[ID_UNIQUE] = bc
if __name__=='__main__':
main()
Pour le no unique, oui c'est le même que pour les plugins, j'utilise souvent le même d'ailleurs, l'essentiel est d'être sûr de ne pas rentrer en conflit avec un no existant.
[EDIT] Dans l'exemple j'ai attribué le BaseContainer à un objet, mais tu peux faire la même chose sur un document, par exemple pour stocker les derniers réglages d'un outil sur un document. Cela fonctionne également sur toutes les classes héritées de C4DAtom les objets, les tags, les matériaux, ....
Dernière modification par oli_d ; 06/02/2016 à 17h02.
Ok dac bon bha y'a plus qu'a maintenant
Encore une fois merci et effectivement une fois qu'on a capté c'est tout simple
Dernière modification par gr4ph0s ; 06/02/2016 à 19h11.
SDK Specialist
MAXON Computer GmbH
Je crois que tu as tout compris. L'avantage du python c'est que tu peux accéder à pratiquement tout directement. (c'est d'ailleurs souvent une critique de ce language) Mais t'as tout compris c'est cool
Alors un exemple pour les read write copyTo (les trois doivent être surchargées)
Parce que pour les sauvegardes/ chargement le BaseContainer est pris en compte automatiquement, mais pour la copy d'objet également. Ces fonctions viennent de C4DAtom c'est dire ^^
Dans delta mush j'ai utilisé dans ma class un tableau pour stocker le déplacement vectoriel de chaque point d'un objet sur lequel est placé le tag. Le tableau c'est maxon::BaseArray<Vector> m_vDisplacement;
C'est du c++ mais tu vas te sentir en terrain familier
Code:class TDeltaMush : public TagData { INSTANCEOF(TDeltaMush, TagData); friend class ODeltaMush; public: virtual Bool Init(GeListNode* node); virtual Bool Message(GeListNode *node, Int32 type, void *data); virtual EXECUTIONRESULT Execute(BaseTag* tag, BaseDocument* doc, BaseObject* op, BaseThread* bt, Int32 priority, EXECUTIONFLAGS flags); virtual Bool Draw(BaseTag *tag, BaseObject *op, BaseDraw *bd, BaseDrawHelp *bh); static NodeData* Alloc(void) { return NewObjClear(TDeltaMush); } virtual Bool CopyTo(NodeData *dest, GeListNode *snode, GeListNode *dnode, COPYFLAGS flags, AliasTrans *trn); virtual Bool Read (GeListNode *node, HyperFile *hf, Int32 level); virtual Bool Write(GeListNode *node, HyperFile *hf); virtual Bool GetDEnabling (GeListNode *node, const DescID &id, const GeData &t_data, DESCFLAGS_ENABLE flags, const BaseContainer *itemdesc); //---------------------------------------------------------------------------------------- ///Calculate Vector displacement ///@param[in] op base object to calculate from ///@param[in] tag that got the parameter for delta mush ///@return true or false if caltulation is done. //---------------------------------------------------------------------------------------- Bool _CalculateVDisp(BaseObject *op, BaseTag *tag); maxon::BaseArray<Vector> m_vDisplacement; Bool m_initdone; };
Ce tableau n'est pas sauvegardé pour le coup. encore moins quand tu copie l'objet (et les tags qui sont attaché).
La chose assez drôle également c'est que le tag est également copié si tu met un déformateur à l'objet. Le déformateur vas dupliquer l'objet pour lui appliquer ses transformations. Donc ton tag aussi est dupliqué. C'est pour ça que c'est très important la fonction CopyTo() si tu stock et utilise des données dans la class. (qui ne sont pas calculé à chaque frame)
Alors du coup tu surcharges les fonctions et puis ça roule. Il faut aussi, quand tu enregistres ton plugin, utiliser le paramètre disklevel.
Ca c'est pour la version de ton plugin. Par exemple, dans la version 1.0 tu stock des couleurs. (ces données seront stocké dans le fichier c4d). Dans la version 2.0 tu stock en plus des positions. Il est important que ta version 2.0 utilise un disklevel supérieur. Sinon tu risque de sauvegarder des données que ta version 1.0 sera incapable de lire et t'aura un beau "fichier corrompu"
Si tu veux des exemples de fonctionrs read ou write ou copyto on peut en trouver sur le forum café c'est superSimple aussi une fois que t'as compris.
Mais ça reste des fonctions un peu "avancé" dans le développement de cinema4D, normalement tu devrais t'en sortir sans avoir à utiliser ces fonctions.
HyperFile = fichier C4d ?
Donc si je comprend bien c'est dans cette hyperfile(mon fichier c4d) que mon container (héritant de c4d.atome) va être récupéré via CopyTo (en justement copiant les donnée de l'hyperfile vers un nouvelle C4d.atome/basecontainer ou ce que je veux) Que je vais ensuite lire avec la fonction read et que je vais save avec Write directement dans l'hyperfile (c'est la que sa hic car si je peux écrire direct dans l'hyperfile pourqoi je ne pourrais pas lire?^^')
Le C++ je comprend mais c'est ce qui m'avais freiné a l'utilisé "quotidiennement" quand j'étais un peu plus jeune c'étais les histoires de pointeur / référence tousa même si je captais le principe je reste un éternel étourdis et je me retrouvais plus souvent à debugger des erreur de gestion d'adressage mémoire que la logique du programme :p
Et puis faut compiler a chaque fois. Pour le moment je reste sur le python vu qu'on peu aussi interagir avec C4DAtom aussi et donc plus ou moins faire la même chose par exemple pour ton tableau de deplacement on peu la recup via MSG_TRANSLATE_POINT et MSG_PRETRANSLATE_POINT puis pour chaque point récupérer le vecteur.
En tout cas merci de prendre le temps de m'expliquer
SDK Specialist
MAXON Computer GmbH
j'insiste sur le fait que tu peux faire une chié de chose dans cinema4D sans avoir à utiliser la sauvegarde de données par un plugin. Le système de cache de cinema4D optimise déjà pas mal de chose. D'ailleurs si tu regardes cinema4D il y a peut de tag ou d'objet que tu peux "freeze" ou "initialiser"
Dans la doc :
Note It is recommended to store as much as possible in the BaseContainer as Cinema 4D will handle the copying of those values automatically. Only use member variables when necessary.
[MODE PAS CERTAIN]
Je crois pas qu'on puisse dire qu'hyperfile = c4d non. J'ai jamais trop réfléchi à ce que c'était réellement à vrai dire. Je sais que c'est utilisé dans les fonctions write/read pour lire/écrire des données c'est tout.
Quand tu regardes la fonction hyperfile.GetDocument() : Gets the active document for the hyper file operation. Can be None, for example when saving layouts
Donc visiblement les fichiers layouts sont des hyperfiles aussi. Le format c4d doit avoir une partie également basé dessus.
C'est une sérialisation des données on dirait.
[/MODE PAS CERTAIN]
Je crois que tu n'as pas compris les fonctions read/write/copyTo.
Cinema4D utilise déjà ces fonctions pour un baseContainer parce qu'il sait comment traiter ces informations et il sait comment elles sont composées. C'est aussi pour ça qu'un baseContainer est automatiquement sauvegardé quand tu sauvegardes ton fichiers et que tu n'as donc pas à t'en soucier.
Si par contre tu as des données que tu veux stocker et qui ne sont pas calculés (ou qui peuvent être stocké sur un cache) là tu dois le faire toi même. Un HyperNurbs par exemple ne stock aucune données. Il prends un objet en enfant, il fait le calcul et recrache le résultat (le cache est construit par la même occasion). Tant que l'objet n'est pas "dirty" on utilise le cache. Si tu sauvegarde soit il stock le cache soit il recalcule le tout quand tu charges le fichier. (aucune idée)
la fonction read --> à l'ouverture d'un fichier
la fonction write --> à la sauvegarde d'un fichier
la fonction copyTo --> utilisé quand tu dupliques un objet ou par certaines fonctions de c4D.
C'est c4d qui gère les appels à ces fonctions. A aucun moment tu ne doit spécifier le fichier que tu veux lire.
C'est juste c4D qui dit à ton plugin 'bon si t'as un truc à lire dans le fichier c'est le moment et tient voilà le fichier à lire'
Si tu veux gérer des vecteurs à 5 dimensions, c4d ne sais pas comment sauvegarder ça. Imagine que tu as un tableau de vecteurs à 5 dimensions. Ben dans ta fonctions write tu mettrais un truc du genre
Ne pas oublier que c'est du FINO (first in first out)
Attention à la fonction copie et au référence et non copie de données.
Erreur de ma part ce n'est pas de c4dAtom que ses fonctions viennent mais de NodeData
Code:class monPlugin: def __init(self): self.myVectorList =[vecteur5(), vecteur5(),vecteur5(),vecteur5(),vecteur5()] Write(self,node, hf): hf.WriteInt32 (len(self.myVectorList)) #on enregistre la taille du tableau for v in self.myVectorList: hf.WriteFloat32(v.a) hf.WriteFloat32(v.b) hf.WriteFloat32(v.c) hf.WriteFloat32(v.d) hf.WriteFloat32(v.e) Read(self,node,hf,level): self.myVectorList = [] #reset du tableau count = ht.ReadInt32() # on récupère la taille du tableau for v in xrange(count): self.myVectorList.append(Vecteur5( hf.ReadFloat32(), hf.ReadFloat32(), hf.ReadFloat32(), hf.ReadFloat32(), hf.ReadFloat32(), ) ) CopyTo(self, dest, snode, dnode, flags, trn): #ce code doit créer une référence et non une copie donc attention mais c'est l'idée dst.myVectorList = self.myVevtorList
Ok un énorme merci j'avais pas du tout capté que les methodes :
Write / Load / CopyTo étais appelé par défaut et donc logique si on veux rajouter des fonctions il faut les surcharger !
SDK Specialist
MAXON Computer GmbH
j'ai oublié un truc aussi, il faut éventuellement appeler les fonctions de la class à la fin de tes fonctions surchargés
en c++ un truc du style return TagData::CopyTo(dest, snode, dnode, flags, trn);
ça renvoie les paramètres de la fonction à la fonction de la class. (je me souviens plus du terme employé)