PDA

Voir la version complète : Créer un filtre d'exportation



César Vonc
11/01/2012, 13h29
Bonjour,


Je souhaiterais créer un filtre d'exportation vers le format DOF pour le jeu Racer dont la structure est détaillée ici : http://www.racer.nl/dof.htm

Cependant, vu que je ne suis pas encore à l'aise en binaire, j'aimerais commencer par exporter un petit fichier texte regroupant les paramètres des matériaux (fichier .shd pour Racer).


Peut-on créer ce filtre en Python ou forcément en C++ ?

valkaari
11/01/2012, 14h35
En python, il y a la fonction c4d.plugins.SceneSaverData qui correspond à la fonction pour sauvegarder ses propres formats de fichier. Donc j'imagines qu'il est possible de le faire en python, si tout a été implémenté.

Il n'y a pas d'exemple en python à ma connaissance, mais il y en a dans le répertoire plugins cinema4DSDK pour le C++

oli_d
11/01/2012, 16h03
Si jamais va voir du côté de la bibliothèque struct (http://docs.python.org/library/struct.html) pour la lecture et l'écriture en python des fichiers binaires.

Pour ce qui est du plugin dans les règles de l'art, c'est probablement avec la classe c4d.plugins.SceneSaverData comme déjà dit par Val

César Vonc
11/01/2012, 22h05
D'accord, peut-on créer ce filtre d'exportation avec un script Python ou faut il faire un module à part comme en C++ ? Mes connaissances se limitent à du script COFFEE.

Ou bien est-ce que script et module Python signifient la même chose ? J'ai l'impression qu'ils sont confondus, lorsque je recherche sur la toile comment créer un module Python pour C4d.


Si créer un module Python est aussi complexe qu'en C++, ne vaut-il pas mieux utiliser le C++ d'office, qui a forcément toutes les fonctionnalités ?

oli_d
12/01/2012, 04h50
D'accord, peut-on créer ce filtre d'exportation avec un script Python ou faut il faire un module à part comme en C++ ?Ben justement en python tu as le choix, si tu veux un module d'exportation comme ceux qui existent déjà (3ds,dwg,obj etc...) avec réglages divers dans les préférences générales et tout et tout, il faut passer par un plugin, c 'est à dire en gros que tu vas créer une classe qui hérite de c4d.plugins.SceneSaverData.

Sinon tu peux très bien passer par un script, donc taper ton code directement dans le gestionnaire de script qui générera ton fichier binaire.

Ou bien est-ce que script et module Python signifient la même chose ? J'ai l'impression qu'ils sont confondus, lorsque je recherche sur la toile comment créer un module Python pour C4d.Regarde les exemples fournis avec la doc python (y en a pas pour un module d'export, mais comme l'a dit Val il y en a un pour le c++, tu peux largement t'en inspirer en python, le principe sera le même


Si créer un module Python est aussi complexe qu'en C++, ne vaut-il pas mieux utiliser le C++ d'office, qui a forcément toutes les fonctionnalités ?vaste débat ... L'avantage du python c'est justement de pouvoir faire à la fois rapidement un petit script pour te dépanner, et de pouvoir également faire des plugins plus complexes. En gros on a les avantages du COFFEE et du c++ dans un seul langage.

Pour moi le python est également plus "intuitif" et plus compact, tu économises pas mal de ligne de code. Par contre cela a des défauts on peut plus facilement faire des conneries, surtout au niveau des variables non déclarées et non typées.

Après cela va dépendre aussi des autre softs que tu utilises, le python je le retrouve dans Vue, ArcMap, Open office ...

Pour ce qui est des fonctions/classes qui manquent, il n'y en a plus beaucoup, et cela va s'améliorer à chaque version.

L'inconvénient du python, c'est également qu'il sera un peu plus lent.

Après les bibliothèques externes à disposition peuvent également jouer, et là je ne pourrai pas te dire cela dépend de tes besoins, mais on en trouve beaucoup dans les deux camps

En résumé, je dirais que si ton but c'est de devenir un programmeur quasiment pro qui vit de ses plugins ->C++
Si c'est comme moi pour dépanner vite créer une petite fonction que tu n'as pas sous la main, un petit plug de temps en temps, bref programmeur du dimanche -> Python

César Vonc
12/01/2012, 19h35
Oui, je vais juste faire un petit outil qui me facilitera la tâche pour exporter vers ce jeu comme je fais quelques scripts pour me facilter la modélisation en Coffee, de la prog de dimanche comme tu dis.

Je vais tenter le coup avec un module Python !


Je n'ai encore jamais vraiment touché à ce langage, faut dire qu'il est apparu dans C4d juste au moment où je commençais à bien comprendre le COFFEE, mais bon, faut bien commencer un jour.


J'ai regardé à droite à gauche comment créer un module, donc je créé mon fichier .pyp, je le place dans le dossier plugins de C4D :



import c4d
from c4d import plugins, gui

MODULE_ID = 1000002

class ExporteurDof(c4d.plugins.SceneSaverData):
gui.MessageDialog('Essai 2.')

if __name__=='__main__':
c4d.plugins.RegisterSceneSaverPlugin(MODULE_ID, "Racer DOF (*.dof)", ExporteurDof(), 0, "Essai", "dof", __res__)Et là, trois choses :

- En lançant C4D ou en cliquant sur « Recharger les modules Python », ma fonction ExporteurDof s'exécute d'emblée car mon message d'essai s'affiche à l'écran.
- Le message d'erreur « TypeError: You can just reload already registered plugins » apparaît dans la console.
- Et mon filtre n'apparaît pas dans la liste Fichier > Exporter.


J'ai l'impression que le numéro d'identifiant du module est la cause de ces problèmes... j'ai beau le changer, mais les erreurs persistent.
Les identifiants d'essais sont pourtant bien 100000x ?

oli_d
12/01/2012, 22h20
En fait je viens de voir dans la doc qu'il y a un exemple dans la classe c4d.plugins.SceneSaverData : Py IES-Meta à cliquer en haut sous "see also"". (il est même installé chez moi mais je me souviens pas si c'est moi ou si c'est d'origine)

Pour ce qui est du numéro tu as meilleur temps d'en demander un sur plugin cafe.

César Vonc
12/01/2012, 22h38
Ah ça y est j'ai compris !

Il y a bel et bien un exemple d'utilisation de c4d.plugins.SceneSaverDatadans la doc de Python : Py-IESMeta.
Édition : On semble l'avoir vu en même temps, Oli_d !

Donc il y avait plus d'une erreur :


Le dernier argument de ResdisterSceneSaverPlugin semble être facultatif, j'ai tenté de l'utiliser sans savoir mais je n'ai pas compris son utilité.

L'argument reste légèrement mystérieux, est-ce le nom du dossier res sans « res » ?

Si ma fonction s'exécutait d'emblée c'est parce qu'elle était nue dans ma classe, il fallait la mettre dans une fonction def chose():.


Visiblement il vaut mieux créer un nouveau dossier dans le dossier plugins, y mettre son fichier .pyp, créer un dossier res contenant un fichier c4d_symbols.h ainsi qu'un dossier strings_fr contenant c4d_strings.str.

Le fichier c4d_symbols.h semble initier des choses, on dirait des identifiants pour les éventuels menus du module, sous la forme enum { variable = numéro }.

Quant au fichier c4d_strings.str, il semble contenir la description des éventuels menus du module en fonction de la langue définie par le suffixe du dossier strings_fr, sous la forme STRINGTABLE { variable "Description" }


Voilà ce que j'en ai déduit, je peux me tromper bien sûr, qu'un spécialiste me corrige.


Le code semble mieux tenir la route à présent :


import os
import c4d
from c4d import plugins, gui

MODULE_ID = 1000002

class ExporteurDof(plugins.SceneSaverData):
def __init__(self):
gui.MessageDialog('Essai 3.')

if __name__=='__main__':
plugins.RegisterSceneSaverPlugin(id=MODULE_ID, str="Racer DOF (*.dof)", g=ExporteurDof, info=0, description="", suffix="dof")Le filtre est désormais visible dans le menu Fichier > Exporter !


Je continue ma prospection sous vos yeux ébahis.

César Vonc
12/01/2012, 23h20
Hmm avant de continuer, quelqu'un peut-il me dire si le module Py-IESMeta de la doc de Python fonctionne chez lui ?

J'ai le message d'erreur : « Impossible d'enregistrer le fichier [...] (le fichier est peut être protégé en écriture) » lorsque je tente d'exporter.

Pour ceux qui voudraient faire l'essai mais qui n'ont pas de lumières IES sous la main, des gratuites sont dispos ici (http://www.mrcad.com/download-free-ies-lights/).


Édition : le problème semble être lié au fait qu'un des dossiers du répertoire d'enregistrement du fichier à exporter comporte un caractère spécial (une lettre accentuée). Quelqu'un peut-il confirmer ?
Ça marche chez moi si j'exporte dans D:\Cesar\ et plante dans D:\César\

oli_d
13/01/2012, 05h55
sur mac OSX ça passe, même avec les accents (ce que j'évite comme la peste en général)

César Vonc
13/01/2012, 23h43
Étrange, j'espère que ça ne vient pas de chez moi.

En attendant je poursuis mon module, j'arrive à créer un fichier texte en me basant sur l'exemple se la doc :



import os
import c4d
from c4d import plugins, documents, utils, gui

MODULE_ID = 1000002

class ExporteurDof(plugins.SceneSaverData):

def Save(self, node, nom, doc, filterflags):

def ecriture(doc, nom):
try:
f = open(nom, "w")
except IOError, e:
return c4d.FILEERROR_OPEN

f.write("DOF1")

try:
f.close()
except IOError, e:
return c4d.FILEERROR_CLOSE

return c4d.FILEERROR_NONE

statut = ecriture(doc, nom)
return statut

if __name__=='__main__':
plugins.RegisterSceneSaverPlugin(id=MODULE_ID, str="Racer DOF (*.dof)", g=ExporteurDof, info=0, description="", suffix="dof")


Par contre, je sèche complètement pour écrire en binaire.
Comment, par exemple, ajouter un entier 32 bits non signé après DOF1 ?

J'ai essayé f.WriteLong(monentier) mais je n'ai eu que des erreurs.

oli_d
14/01/2012, 06h54
Il faut que tu transformes ton nombre en chaine avec la fonction str(nbre)

ça c'est pour un fichier texte, pour la lecture/écriture d'un fichier binaire à l'aide de la bibliothèque struct (http://docs.python.org/library/struct.html) un petit exemple :


import c4d
import struct

def ecrire():
f = open('test.bin','w')
f.write(struct.pack('<d',1234.56789))
f.close()

def lire():
f = open('test.bin','r')
print struct.unpack('<d',f.read(8))
f.close()

if __name__=='__main__':
ecrire()
lire()
ici j'écris un double ('d') en byte order Little endian ('<').
pour lire on utilise read avec le nombre de byte correspondant (8 pour un double, voir tableau dans la doc)

Donc si tu veux un entier non-signé tu remplacera 'd' par 'I' et à la lecture tu liras 4bytes et si tu veux un order byte spécifique tu rajoute le <ou> (voir les différents tableaux dans la doc struct (http://docs.python.org/library/struct.html)):

import c4d
import struct

def ecrire():
f = open('test.bin','w')
f.write(struct.pack('I',123456))
f.close()

def lire():
f = open('test.bin','r')
print struct.unpack('I',f.read(4))
f.close()

if __name__=='__main__':
ecrire()
lire()Après tu peux même "packer" plusieurs variables même de différents types à la suite (voir aussi exemples dans doc) : struct.pack('Id',123456,98.4567)

César Vonc
14/01/2012, 21h55
Parfait, toujours aussi clair, Oli_d !

D'après la structure du format en question, l'ordre des octet est de type petit-boutiste, donc <I pour les entiers non signés.


J'ai en revanche un petit soucis pour écrire un entier 16 bits non signé (10), je fais : whrite(struct.pack('<H', 10)), mais j'obtiens 0D0A 00 en hexa, au lieu de 0A00.

Pourtant, lorsque j'écris 11 : whrite(struct.pack('<H', 11)), j'obtiens bien 0B00. Comment cela se fait-il ?


Cet entier représente la longueur d'une chaîne de caractère, donc peut-être y a-t-il un autre moyen de l'écrire ?


Autre chose, peut-on mélanger write("texte") et whrite(struct.pack()) ? Les chaînes de caractère sont écrites en clair dans ce format, donc le fichier reste bon en hexa, mais est-ce propre pour autant ?

César Vonc
17/01/2012, 11h49
J'ai remarqué que cette anomalie concerne les valeurs précédant 0A, en hexa :

Décimal : Entier 16 bits en héxadécimal
01 : 0100
02 : 0200
03 : 0300
04 : 0400
05 : 0500
06 : 0600
07 : 0700
08 : 0800
09 : 0900
10 : 0D0A00
11 : 0B00
12 : 0C00
13 : 0D00
14 : 0E00
15 : 0F00
16 : 1000
17 : 1100
18 : 1200
19 : 1300
20 : 1400

De même pour :

266 : 0D0A01
522 : 0D0A02
778 : 0D0A03
etc.

Je ne comprends vraiment pas ce que vient foutre ce 0D (13). Et pourtant, à la lecture, C4d lit bien 10 pour 0D0A00.

Mais du coup, si mon fichier contient l'entier 2573, soit 0D0A, C4d plante à la lecture :
struct.unpack('H', f.read(2))
« unpack requires a string argument of lengh 2 », comme s'il ne voyait pas 0D



Je vois aussi que 0D correspond au retour chariot (CR) d'un texte, donc peut-être que C4d interprête le fichier binaire comme un fichier texte et ignore ce caractère...


Si quelqu'un a une explication ou une solution, je suis preneur, car ce soucis bloque complètement mon code.

oli_d
17/01/2012, 16h16
Tu es sûr que c'est pas ton lecteur hexadecimal qui déconne, car sur mon mac avec HexEdit je n'ai pas ce problème ...

César Vonc
17/01/2012, 17h15
On va vérifier. Ci-joint se trouve le fichier dix.bin généré avec ce script :


import c4d
import struct

def ecrire():
f = open('dix.bin','w')
f.write(struct.pack('H', 10))
f.close()

if __name__=='__main__':
ecrire()J'utilise Hex Workshop qui m'affiche toujours 0D0A00.


Peut-être est-ce dû au fait que tu sois sur Mac, oui.


Édition : J'apprends que 0D et 0A caractérisent respectivement un retour chariot et un saut de ligne (CR et LF), qui sont utilisés conjointement, sauf sur les mac qui n'utilisent que CR, ce qui expliquerait pourquoi tu n'as pas ce problème.
http://fr.wikipedia.org/wiki/Fin_de_ligne

Si tel est le cas, cela reste un défaut de C4d, peut-on le signaler ?

oli_d
17/01/2012, 19h22
Je confirme, sur ton fichier j'ai bien 0D0A00 alors qu'avec ton code ci-dessus sur mac j'obtiens bien 0A00 (voir fichier en pj). Cela m'étonne que cela soit dû à c4d mais plutôt à python, essaie peut-être de poser la question sur PluginCafe ...

Ou teste peut-être sur le shell python sur ton PC pour voir si le résultat est le même

César Vonc
17/01/2012, 21h37
Merde, je viens d'essayer avec le Python 2.7.2 que j'avais d'installé, sans passer par C4d, et le problème est toujours là...


Pour ceux qui ont Python et veulent vérifier, il suffit de copier le code de mon précédent message, de virer import c4d, d'enregistrer au format .py et de l'executer avec python.exe. Le fichier dix.bin devrait apparaître au même endroit que le fichier .py.


Je vais faire un tour sur le forum de Python, ça m'étonnerait que ce problème soit passé inaperçu (en espérant que la solution ne sera pas de passer au C++...).



Édition : Ah, putain ! J'ai compris...

Il faut lire et écrire en précisant le mode binaire, le mode texte étant par défaut, soit rajouter « b » à côté du mode d'écriture :


import c4d
import struct

def ecrire():
f = open('dix.bin','wb')
f.write(struct.pack('H', 10))
f.close()

if __name__=='__main__':
ecrire()Et là, tout fonctionne à merveille !


J'avoue avoir eu une sueur froide pendant un bref instant.

valkaari
17/01/2012, 23h14
ha ben je crois qu'on s'est tous fait avoir ^^
merci pour le retour.

(oui je suivais la conversation sans rien comprendre)

oli_d
18/01/2012, 04h17
"Une seule lettre vous manque et tout est dépeuplé" Lamartine

Autant pour moi, je n'avais jamais vu ce petit "b"

Bravo pour la trouvaille, c'était tordu. C'est vraiment les joies de la programmation, on peut de temps en temps perdre un temps fou pour un petit détail comme ça.

César Vonc
21/01/2012, 11h48
Je suis content, mon filtre marche plutôt bien, je cherche maintenant à le peaufiner.


Dans une propriété Texture, lorsque le type de projection n'est pas UVW, j'utilise la fonction :
CallCommand(12235) # Générer des coordonnées UVW
après avoir précédemment sélectionné la propriété Texture en question avec SetActiveTag(), mais l'opération s'exécute sur le document actif, et non sur la copie invisible sur laquelle j'applique toutes les modifications pour la conversion (triangulation des polygones, conversion en objets éditables, etc.)


Donc ma question est : comment appliquer un CallCommand() sur un document précis ?
J'ai essayé avec SendModelingCommand(), comme j'avais fait pour les autres, mais je n'arrive pas à m'en servir correctement vu que la commande s'applique sur une propriété et non un objet.
Une idée ?

Tengaal
21/01/2012, 11h56
Plutôt que de générer des coordonnées UVW, pourquoi ne pas tout simplement ajouter une propriété "texture adaptative" sur ton objet, cette propriété "colle" la texture au maillage (comme une projection UVW) avec l'avantage de conserver le tag de texture conservant une projection autre que UVW (ex: planaire, cubique, cylindrique.)

et pour la programmation ça simplifie les choses, une fois que l'objet et sa texture sont inséré dans la projet, on a juste à ajouter un tag "texture adaptative" et ça y est.

César Vonc
21/01/2012, 12h20
Hmm, je ne dois pas bien m'en servir, car lorsque j'ajoute une propriété Texture adaptative à un objet qui a : une propriété UVW, et une propriété Texture dont la projection est de type sphérique, par exemple ; lorsque je récupère les coordonnées de la propriété UVW, elle restent les mêmes qu'avant l'ajout de la texture adaptative.

Donc je récupère toujours le dépliage UVW de base et non le sphérique.

Tengaal
21/01/2012, 17h45
c'est normal que tu récupère les mêmes UVW qu'à l'origine, le tag "texture adaptative" ne génère pas des coordonnées UVW et ne les modifie pas si elles existent.

Ce tag est totalement dissocié des coordonnées UVW de l'objet, même si il produit par contre le même effet, à savoir coller la texture au maillage quand celui-ci se déforme.

cette propriété est une alternative à l'UVWn elle a comme avantage de pouvoir garder une projection différente qu'UVW, ce qui permet donc, quand la texture est collée au maillage, de pouvoir, à tout moment, déplacer/tourner/mettre à l'échelle la texture qui est restée en projection "sphérique/cubique/planaire, etc...

donc ici, même si tu supprimes le tag UVW de ton objet, le tag "texture adaptative" collera quand même la texture sur le maillage.
ce tag est aussi utilisable sur des objet non-polygonaux (primitives, objets Nurbs, etc...), c'est en cela qu'il est interessant par rapport à la projection UVW qui oblige de convertir les objets en polygonal...

A toi de voir si tu veux absolument rester sur un projection UVW + tag UVW, ou bien passer par une projection autre + tag "texture adaptative"...

César Vonc
21/01/2012, 19h59
D'accord, je comprends mieux.

Le but ici est de récupérer les coordonnées UV pour les écrire ensuite dans mon fichier exporté, donc je suis un peu obligé d'avoir une propriété UVW.
À moins qu'on ne puisse récupérer celles de la propriété Texture adaptative ?
En tout cas textureadaptative.GetSlow(0) ne marche pas.

oli_d
22/01/2012, 03h55
Donc ma question est : comment appliquer un CallCommand() sur un document précis ?


Essaie de rendre le document actif par c4d.documents.SetActiveDocument(doc) avant de lancer la commande.

Tengaal
22/01/2012, 10h53
D'accord, je comprends mieux.

Le but ici est de récupérer les coordonnées UV pour les écrire ensuite dans mon fichier exporté, donc je suis un peu obligé d'avoir une propriété UVW.
À moins qu'on ne puisse récupérer celles de la propriété Texture adaptative ?
En tout cas textureadaptative.GetSlow(0) ne marche pas.


Ah ok j'avais pas capté que c'etait pour récupérer les données de projection et les exporter, comme un imbécile je m'etais mis en tête que c'etait pour un import de fichier et non un export, alors que c'est pourtant bien mentionné dans le titre de ton sujet.

mea culpa ! :icon_wip:

César Vonc
23/01/2012, 23h55
Pas de soucis, Tengaal, j'aurais au moins appris l'utilité de la texture adaptative. ^^


Ah oui, bien vu, Oli_d.

Je précise qu'il faut pas oublier de redéfinir l'ancien document une fois l'export terminé et de fermer le clone avec KillDocument(), sans quoi on se retrouve avec une sorte de document impossible à éditer, avec tous les menus grisés, qui rend c4d très instable.




Pour ceux qui chercheraient à faire une boîte de dialogue qui s'ouvre avant l'exportation, je propose ceci :



class Dialogue(gui.GeDialog):
def CreateLayout(self):
self.SetTitle("Paramètres d'exportation")
self.AddStaticText(0, c4d.BFH_CENTER, 0, 0, "Essai", c4d.BORDER_NONE)
return True

class ExporteurDof(plugins.SceneSaverData):
def Save(self, node, name, doc, filterflags):
self.dial = Dialogue()
self.dial.Open(c4d.DLG_TYPE_MODAL, MODULE_ID, -1, -1)

if __name__=='__main__':
plugins.RegisterSceneSaverPlugin(id=MODULE_ID, str="Racer DOF (*.dof)", g=ExporteurDof, info=0, description="", suffix="dof")Ça marche, n'empêche que la variable info de la classe RegisterSceneSaverPlugin m'intrigue.
D'après la doc (http://chicagoc4d.com/C4DPythonSDK/modules/c4d.plugins/index.html?highlight=registerscenesaverplugin#c4d. plugins.RegisterSceneSaverPlugin), celle-ci peut prendre la valeur :

c4d.PLUGINFLAG_SCENEFILTER_DIALOGCONTROL

Avec comme description, en anglais : « By default the scene loader/saver displays its own description dialog during importing/exporting of scene files. By setting this flag it will surpress this behaviour allowing to display another extended dialog for example. »

Si je comprends bien, cela signifie qu'elle permet d'afficher une boîte de dialogue d'exportation ? Pourtant je ne vois pas de changement en appliquant cette valeur plutôt que 0. J'ai pu créer ma boîte de dialogue sans, donc je ne comprends pas bien son intérêt.

oli_d
24/01/2012, 13h50
Si je comprends bien, cela signifie qu'elle permet d'afficher une boîte de dialogue d'exportation ? Pourtant je ne vois pas de changement en appliquant cette valeur plutôt que 0. J'ai pu créer ma boîte de dialogue sans, donc je ne comprends pas bien son intérêt.

Sur ce coup là je ne peux pas t'aider, mais je comprend à peu près la même chose que toi, c'est à dire si on ne veut pas utiliser la boîte de dialogue d'importation par défaut il faut utiliser ce c4d.PLUGINFLAG_SCENEFILTER_DIALOGCONTROL. Donc si tu as testé et que cela ne change rien, je ne vois pas.
A tout hasard, est-ce que cela n'agirait pas dans les paramètres d'importation dans les préférencesgénérales de c4d ??? (mais bon même avec mon faible niveau d'anglais, cela n'a pas l'air d'être ça ...)

valkaari
25/01/2012, 00h42
excellent en tout cas merci pour le partage, j'apprends pleins de trucs ^^

César Vonc
27/01/2012, 19h56
A tout hasard, est-ce que cela n'agirait pas dans les paramètres d'importation dans les préférencesgénérales de c4d ??? (mais bon même avec mon faible niveau d'anglais, cela n'a pas l'air d'être ça ...)
Ah c'est possible, mais peut-être faut-il créer notre boîte de dialogue d'une manière bien spécifique, car je n'ai toujours rien dans l'onglet de mon filtre dans les préférences générales.



Je cherche à récupérer les normales d'une propriété normale (tnormal) d'un objet.
J'ai beau faire :

normales = propNormale.GetData()
print normales[0]

J'obtiens toujours None, alors que propNormale.GetDataCount() m'indique bien le nombre de polygones...

Je n'arrive pas à récupérer les données de cette propriété, quelqu'un a une idée ?
(Pour créer une propriété Normale, exporter en FBX puis importer)


Dans le cas d'une propriété lissage (ou ombrage Phong), il y a la fonction bien pratique PolygonObject.CreatePhongNormals() qui renvoie les normales de chaque point des polygones. Mais elle fonctionne uniquement lorsque l'objet a une propriété Lissage.

César Vonc
27/01/2012, 23h29
Je crois avoir trouvé.

J'ai cherché du côté de VariableTag qui propose la fonction GetAllHighlevelData().

normales = propNormale.GetAllHighlevelData()

Elle ne me retourne non pas un tableau la taille de GetDataCount(), mais de : nombre de polygones * 4 (points abcd) * 3 (nombres XYZ).
Soit un gros tableau d'entiers (pas même de vecteurs) :

p0 a x, p0 a y, p0 a z,
p0 b x, p0 b y, p0 b z,
p0 c x, p0 c y, p0 c z,
p0 d x, p0 d y, p0 d z,
p1 a x, etc.

Pas très pratique, mais au moins les données sont là !


PS : Par contre j'ignore dans quel format sont ces valeurs ; pour un vecteur qui vaut normalement (0, 0, -1), j'ai (0, 0, 33536). Tous ces entiers sont positifs, donc je me demande s'ils correspondent vraiment aux normales...


Édition :

En fait je me casse la tête pour rien, il suffit d'ajouter une propriété Lissage pour récupérer les normales de la propriété Normale avec CreatePhongNormals(), et ce quelque soit la valeur du lissage phong, car les paramètres de la propriété Normale écrasent celles du lissage.

Pas très logique, je trouve, d'avoir nécessairement une propriété Lissage avec une propriété Normale. Enfin bon, problème résolu.

César Vonc
30/01/2012, 23h25
Après avoir longuement bossé sur le sujet, je vous propose une classe qui converti et regroupe tous les éléments d'un objet à exporter, elle peut donc servir de base pour d'autres modules d'export.


Le but est d'avoir aucune contrainte pour exporter, toutes les propriétés nécessaires à l'exportation (normales, uvw, ...) se créeront et se trieront automatiquement, notamment dans les cas complexes où plusieurs matériaux et dépliages UVW sont appliqués à un même objet. Même si les sélections de polygones se superposent, le ménage se fait tout seul et des tableaux tout propres définissent quel matériau et quel dépliage uv est affecté à tel polygone.


Cette classe, que j'ai nommé Convertisseur a 3 fonctions :

execute(objs, triangle) : Fonction principale, converti le document et récupère ses données.
objs : (booléen) si vrai, exporte les objets sélectionnés, sinon, exporte tous les objets du document.
triangle : (booléen) converti ou non les quadrangles en triangles.

normales_obj(index_obj) : Renvoie un tableau contenant les normales de chaque point de l'objet (le nombre total de points vaut le nombre de polygones * 4).
- index_obj : (entier) numéro de l'objet dans la liste objs_pol[index_obj]

uvw_obj_pol(index_obj, index_pol, correction) : Renvoie les coordonnées UVW du polygone, avec ou sans les corrections définies dans la propriété Texture (comme l'échelle et le décalage).
- index_obj : (entier) numéro de l'objet dans la liste objs_pol[index_obj]
- index_pol : (entier) numéro du polygone
- correction : (booléen) si vrai, renvoie les coordonnées UVW avec l'échelle et le décalage de la propriété Texture pris en compte.


Après la fonction execute(), la classe contient les variables nécessaires à l'exportation, et peuvent donc être utilisées pour l'écriture du fichier.

Voici le script en question qui créé un fichier contenant les données exportées dans une structure quelconque :


import c4d
import struct
from c4d import plugins, documents, utils, gui

MODULE_ID = 100001

class Convertisseur:
document = None
objs_pol = [] # [] Objets polygonaux
obj_mat = [] # [[]] Propriétés texture par objet
obj_mat_pol = [] # [[]] Propriété texture (son index dans obj_mat[[]] ) par polygone par objet
obj_mat_uvw = [] # [[]] Propriété UVW par propriété texture par objet

objs_cam = [] # Caméras

mats = [] # Matériaux du document

# --- Initialise les variables ---
def __init__(self):
self.document = None
self.objs_pol = []
self.obj_mat = []
self.obj_mat_pol = []
self.obj_mat_uvw = []
self.objs_cam = []
self.mats = []

# --- Récupère les normales d'un objet dans la liste objs_pol ---
def normales_obj(self, index_obj):
obj = self.objs_pol[index_obj]
if (obj is None): return False
normale = obj.CreatePhongNormals()
return normale

# --- Récupère les coordonnées UVW d'un polygone d'un objet de objs_pol, avec ou non la correction de l'échelle de la propriété Texture ---
def uvw_obj_pol(self, index_obj, index_pol, correction):
index_mat = self.obj_mat_pol[index_obj][index_pol]
prop_uvw = self.obj_mat_uvw[index_obj][index_mat]
prop_mat = self.obj_mat[index_obj][index_mat]
uvw = prop_uvw.GetSlow(index_pol)
if (correction is True):
facX = prop_mat[c4d.TEXTURETAG_TILESX]
facY = prop_mat[c4d.TEXTURETAG_TILESY]
decX = prop_mat[c4d.TEXTURETAG_OFFSETX]
decY = prop_mat[c4d.TEXTURETAG_OFFSETY]
uvw["a"].x *= facX
uvw["a"].x += decX
uvw["a"].y *= facY
uvw["a"].y += decY
uvw["b"].x *= facX
uvw["b"].x += decX
uvw["b"].y *= facY
uvw["b"].y += decY
uvw["c"].x *= facX
uvw["c"].x += decX
uvw["c"].y *= facY
uvw["c"].y += decY
uvw["d"].x *= facX
uvw["d"].x += decX
uvw["d"].y *= facY
uvw["d"].y += decY
return uvw

# --- Fonction principale (document, objets à convertir(liste, ou nulle pour tout convertir)) ---
def execute(self, objs, triangle):
def objets_editables(docu, objs):
commandeid = c4d.MCOMMAND_MAKEEDITABLE
marque = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO
mode = c4d.MODELINGCOMMANDMODE_ALL
converti = utils.SendModelingCommand(command=commandeid, list=objs, doc=docu, flags=marque, mode=mode)
c4d.EventAdd()
return converti

doc = c4d.documents.GetActiveDocument()

if (objs is True):
docu = doc.GetClone()
objets_editables(docu, docu.GetActiveObjects(True))
else: docu = doc.Polygonize(False)

self.document = docu
c4d.documents.SetActiveDocument(docu)

retour = self.convertir(docu, objs, triangle)

c4d.documents.SetActiveDocument(doc)
return retour

# --- Vide la mémoire ---
def __del__(self):
c4d.documents.KillDocument(self.document)
c4d.StatusClear()

def convertir(self, docu, objs, triangle):
# -- Répertorie tous les objets si aucun n'est sélectionné --
def repertorie_objs(docu):
liste = []
premObj = docu.GetFirstObject()
if not premObj: return False
def objetIteration(op):
if not op: return liste
liste.append(op)
objetIteration(op.GetDown())
objetIteration(op.GetNext())
objetIteration(premObj)
return liste

objs_sel = objs # Objets sélectionnés
if objs_sel is False: objs_sel = repertorie_objs(docu)
elif objs_sel is True: objs_sel = docu.GetActiveObjects(True)
if objs_sel is False: return False
c4d.StatusSetSpin()

# -- Trie les objets --
def trie_objets(objs):
polygonaux = []
cameras = []
for obj in objs:
if (obj.GetType() == 5100): # Objet polygonal
polygonaux.append(obj)
elif (obj.GetType() == 5103): # Caméra
cameras.append(obj)
return polygonaux, cameras

self.objs_pol, self.objs_cam = trie_objets(objs_sel)
if (self.objs_pol is None): return False
c4d.StatusSetSpin()

# -- Triangule les objets --
def triangule(docu, objs):
commandeid = c4d.MCOMMAND_TRIANGULATE
marque = c4d.MODELINGCOMMANDFLAGS_CREATEUNDO
mode = c4d.MODELINGCOMMANDMODE_ALL
converti = utils.SendModelingCommand(command=commandeid, list=objs, doc=docu, flags=marque, mode=mode)
c4d.EventAdd()
return converti

if triangle is True:
if triangule(docu, self.objs_pol) is False: return False
c4d.StatusSetSpin()

# -- Ajoute le matériau par défaut au doc --
def materiau_def_doc(docu):
matDef = c4d.BaseMaterial(c4d.Mmaterial)
matDef.SetName("matpardef")
docu.InsertMaterial(matDef)
c4d.EventAdd()

materiau_def_doc(docu)
c4d.StatusSetSpin()

# -- Répertorie les matériaux --
self.mats = docu.GetMaterials()
c4d.StatusSetSpin()

# -- Traitement des propriétés des objets --
def traite_props(objs, mats, docu):
# - Ajoute la sélection de polygones par défaut -
def selection_def(obj):
selDef = c4d.BaseTag(c4d.Tpolygonselection)
selDef.SetName(" ")
obj.InsertTag(selDef)
nbp = obj.GetPolygonCount()
nb = [1]*nbp
selDef.GetBaseSelect().SetAll(nb)
c4d.EventAdd()

# - Ajoute le matériau par défaut -
def materiau_def(obj, mats):
matDef = c4d.BaseTag(c4d.Ttexture)
matDef[c4d.TEXTURETAG_MATERIAL] = mats[0]
matDef[c4d.TEXTURETAG_RESTRICTION] = " "
matDef[c4d.TEXTURETAG_PROJECTION] = 6
obj.InsertTag(matDef)
c4d.EventAdd()

# - Ajoute une propriété UVW -
def uvw_def(obj):
uvwDef = c4d.BaseTag(c4d.Tuvw)
obj.InsertTag(uvwDef)

# - Converti en proj UVW -
def proj_uvw(obj, docu):
prop = obj.GetFirstTag()
while prop:
propt = prop.GetType()
if ((propt != 5616) or (prop[c4d.TEXTURETAG_PROJECTION] == 6)):
prop = prop.GetNext()
continue
docu.SetActiveTag(prop)
c4d.CallCommand(12235) # Générer des coordonnées UVW
prop = prop.GetNext()

# - Trouve la propriété Sélection de polygones par son nom -
def trouve_sel(nomsel, obj):
if (nomsel is None or nomsel == ""): nomsel = " "
prop = obj.GetFirstTag()
while prop:
if (prop.GetType() == 5673): # Propriété Sélection de polygones
if (prop[c4d.ID_BASELIST_NAME] == nomsel):
return prop
prop = prop.GetNext()
return None

# - Assigne quel matériau (son index dans obj_mats[[]] ) est associé à quel polygone
def assigne_mat_par_pol(obj, li_sel):
nbpol = obj.GetPolygonCount()
li_matparpol = li_sel[0].GetBaseSelect().GetAll(nbpol)
li_matparpol = [x * (len(li_sel)-1) for x in li_matparpol]
for s in range(len(li_sel)):
sel = li_sel[s].GetBaseSelect().GetAll(nbpol)
for p in range(nbpol):
if (li_matparpol[p] == 0): li_matparpol[p] = sel[p] * (len(li_sel) - s-1)
return li_matparpol

# - Ajoute unr propriété Lissage -
def phong_def(obj):
phongDef = c4d.BaseTag(c4d.Tphong)
phongDef[c4d.PHONGTAG_PHONG_ANGLELIMIT] = True
phongDef[c4d.PHONGTAG_PHONG_ANGLE] = 0
obj.InsertTag(phongDef)

# - Trie les propriétés -
def trie_props(obj):
li_mat = []
li_sel = []
li_uvw = []
li_mat_uvw = []
phong = False
props = obj.GetTags()
props.reverse()
for prop in props:
propt = prop.GetType()
if (propt == 5612): # Propriété Lissage
phong = True
elif (propt == 5671): # Propriété UVW
li_uvw.append(prop)
elif (propt == 5616): # Propriété Texture
li_mat.append(prop)
nomsel = prop[c4d.TEXTURETAG_RESTRICTION]
sel = trouve_sel(nomsel, obj)
li_sel.append(sel)
if (len(li_uvw) == 0): li_mat_uvw.append(None)
else: li_mat_uvw.append(li_uvw[len(li_uvw)-1])
li_mat.reverse()
self.obj_mat.append(li_mat)
li_matparpol = assigne_mat_par_pol(obj, li_sel)
self.obj_mat_pol.append(li_matparpol)
if (len(li_uvw) != 0):
for x in range(len(li_mat_uvw)):
if li_mat_uvw[x] is None: li_mat_uvw[x] = li_uvw[0]
li_mat_uvw.reverse()
self.obj_mat_uvw.append(li_mat_uvw)
if (phong is False): phong_def(obj)

for obj in objs:
selection_def(obj)
materiau_def(obj, mats)
proj_uvw(obj, docu)
trie_props(obj)
c4d.StatusSetSpin()

traite_props(self.objs_pol, self.mats, docu)


return True

class Dialogue(gui.GeDialog):
annuler = False
triangle = False
selection = False
utf8 = False

def CreateLayout(self):
self.SetTitle("Paramètres")
self.AddCheckbox(10, c4d.BFH_SCALEFIT, 0, 0, 'Trianguler les polygones')
self.AddCheckbox(11, c4d.BFH_SCALEFIT, 0, 0, 'Exporter la sélection')
self.AddSeparatorV(20)
self.AddCheckbox(12, c4d.BFH_SCALEFIT, 0, 0, 'Encoder le chemin et le nom du fichier en UTF-8')
self.AddSeparatorV(21)
self.AddDlgGroup(c4d.DLG_OK|c4d.DLG_CANCEL)

if (len(doc.GetActiveObjects(True)) > 0): self.SetBool(11, True)

return True

def Command(self, id, msg):
if (id == 1):
self.annuler = False
self.triangle = self.GetBool(10)
self.selection = self.GetBool(11)
self.utf8 = self.GetBool(12)
self.Close()
if (id == 2):
self.annuler = True
self.Close()
return True

def exporte():
dial = Dialogue()
dial.Open(c4d.DLG_TYPE_MODAL, MODULE_ID, -1, -1)
if (dial.annuler is True): return False

converti = Convertisseur()
etat_conv = converti.execute(dial.selection, dial.triangle)
if (etat_conv is not True): return "Erreur de conversion"

def ecriture():
chemin = c4d.storage.SaveDialog()
if chemin is None: return False

if dial.utf8 is True:
chemin = chemin.decode('UTF-8', 'strict')

try:
f = open(chemin, "wb") # "w" pour texte, "wb" pour binaire
except IOError:
return "Erreur d'ouverture : le fichier est peut-être protégé en écriture, ou le dossier de destination est introuvable ; essayez en cochant l'encodage UTF-8."

f.write(struct.pack('BBB', 0xEF, 0xBB, 0xBF)) # Signature UTF-8
for o in range(len(converti.objs_pol)):
obj = converti.objs_pol[o]
nbp = obj.GetPolygonCount()
f.write(obj.GetName())
f.write("\n\t" + str(nbp) + " polygones")
norm = converti.normales_obj(o)
mat = converti.obj_mat_pol[o]
tex = converti.obj_mat[o]
c4d.StatusSetSpin()
for p in range(nbp):
pa = obj.GetPolygon(p).a
pb = obj.GetPolygon(p).b
pc = obj.GetPolygon(p).c
pd = obj.GetPolygon(p).d
puvw = converti.uvw_obj_pol(o, p, True)
pmatt = mat[p]
pmat = tex[pmatt]
pmatn = pmat[c4d.TEXTURETAG_MATERIAL].GetName()
f.write("\n\t\t" + str(p) + ", " + pmatn)
f.write("\n\t\t\t" + "a-" + str(pa) + ", XYZ" + str(obj.GetPoint(pa))[6:] + ", UVW" + str(puvw["a"])[6:] + ", Normale" + str(norm[p*4])[6:] )
f.write("\n\t\t\t" + "b-" + str(pb) + ", XYZ" + str(obj.GetPoint(pb))[6:] + ", UVW" + str(puvw["b"])[6:] + ", Normale" + str(norm[p*4+1])[6:] )
f.write("\n\t\t\t" + "c-" + str(pc) + ", XYZ" + str(obj.GetPoint(pc))[6:] + ", UVW" + str(puvw["c"])[6:] + ", Normale" + str(norm[p*4+2])[6:] )
if (dial.triangle is False and pc != pd):
f.write("\n\t\t\t" + "d-" + str(pd) + ", XYZ" + str(obj.GetPoint(pd))[6:] + ", UVW" + str(puvw["d"])[6:] + ", Normale" + str(norm[p*4+3])[6:] )
f.write("\n\n")

try:
f.close()
except IOError, e:
return "Erreur de fermeture : le fichier est peut-être protégé en écriture."

return True

etat = ecriture()
if (etat is not True and etat is not False): gui.MessageDialog(etat)

def main():
exporte()

if __name__=='__main__':
main()
Exemple de sortie avec un cube :



Cube
6 polygones
0, matpardef
a-0, XYZ(-100, -100, -100), UVW(0, 1, 0), Normale(0, 0, -1)
b-1, XYZ(-100, 100, -100), UVW(0, 0, 0), Normale(0, 0, -1)
c-3, XYZ(100, 100, -100), UVW(1, 0, 0), Normale(0, 0, -1)
d-2, XYZ(100, -100, -100), UVW(1, 1, 0), Normale(0, 0, -1)
1, matpardef
a-2, XYZ(100, -100, -100), UVW(0, 1, 0), Normale(1, 0, 0)
b-3, XYZ(100, 100, -100), UVW(0, 0, 0), Normale(1, 0, 0)
c-5, XYZ(100, 100, 100), UVW(1, 0, 0), Normale(1, 0, 0)
d-4, XYZ(100, -100, 100), UVW(1, 1, 0), Normale(1, 0, 0)
2, matpardef
a-4, XYZ(100, -100, 100), UVW(0, 1, 0), Normale(0, 0, 1)
b-5, XYZ(100, 100, 100), UVW(0, 0, 0), Normale(0, 0, 1)
c-7, XYZ(-100, 100, 100), UVW(1, 0, 0), Normale(0, 0, 1)
d-6, XYZ(-100, -100, 100), UVW(1, 1, 0), Normale(0, 0, 1)
3, matpardef
a-6, XYZ(-100, -100, 100), UVW(0, 1, 0), Normale(-1, 0, 0)
b-7, XYZ(-100, 100, 100), UVW(0, 0, 0), Normale(-1, 0, 0)
c-1, XYZ(-100, 100, -100), UVW(1, 0, 0), Normale(-1, 0, 0)
d-0, XYZ(-100, -100, -100), UVW(1, 1, 0), Normale(-1, 0, 0)
4, matpardef
a-1, XYZ(-100, 100, -100), UVW(0, 1, 0), Normale(0, 1, 0)
b-7, XYZ(-100, 100, 100), UVW(0, 0, 0), Normale(0, 1, 0)
c-5, XYZ(100, 100, 100), UVW(1, 0, 0), Normale(0, 1, 0)
d-3, XYZ(100, 100, -100), UVW(1, 1, 0), Normale(0, 1, 0)
5, matpardef
a-6, XYZ(-100, -100, 100), UVW(0, 1, 0), Normale(0, -1, 0)
b-0, XYZ(-100, -100, -100), UVW(0, 0, 0), Normale(0, -1, 0)
c-2, XYZ(100, -100, -100), UVW(1, 0, 0), Normale(0, -1, 0)
d-4, XYZ(100, -100, 100), UVW(1, 1, 0), Normale(0, -1, 0)J'espère que cela aidera certains, ne serait-ce que pour avoir un exemple d'export de fichier, les filtres par défaut de C4d étant souvent incomplets, je suis assez content de pouvoir enfin exporter toutes les données dans n'importe quel format.

user4D
30/01/2012, 23h41
Je suis pas sur d'avoir tout compris mais je tiens à te féliciter pour ta ténacité :icon_mrgreen:

Merci pour ce script :icon_prie:

valkaari
31/01/2012, 00h51
Cool.

Faudrait que tu regardes du coté des fonctions __Init__ et __del__ que tu peux "surcharger" (simplement en les créant) (overload en english)
Ces fonctions sont appelés à la création d'une instance de classe et à la suppression de l'instance.

En gros, quand tu fais un monObject = maclasse() la fonction __Init__ est automatiquement exécuté. (attention quand tu utilises la fonction del par contre ^^)

Je suis pas très fan non plus des déclarations de fonctions dans une fonction. Mais si ça fonctionne finalement, on s'en fout un peu non ?

oli_d
31/01/2012, 05h46
Super, bravo et merci pour le partage.

C'est vrai que n'ai pas souvent vu des fonctions déclarées à l'intérieur d'une fonction, mais pourquoi pas si ça aide à la lisibilité.

Chez moi quand je coche la case exporter la sélection cela ne marche pas, le fichier est vide. Je pense que c'est parce que tu récupères la sélection dans ton clone de doc polygonisé (tu me comprends?) et je pense que la sélection ne suit pas dans docu (ligne 99).

Sinon ça marche nickel.

Je pense qu'une petite boîte de dialogue pour définir le chemin du fichier ne serait pas du luxe (mais c'est vrai que si au final tu utilises le code dans un plugin d'export, c'est déjà inclu):

c4d.storage.LoadDialog(flags=c4d.FILESELECT_SAVE)E n tous cas encore bravo !

César Vonc
31/01/2012, 13h31
Ah oui tiens, pratiques, __init__ et __del__, j'ai mis à jour le script.

En effet, Oli_d, en fait la sélection se perdait avec les objets non polygonaux, la fonction doc.Polygonize() clone le document et converti les objets paramétriques en objets éditables mais sans conserver leur sélection.

Donc dans le cas d'objets sélectionnés, vaut mieux se passer de doc.Polygonize() pour utiliser doc.GetClone() avec une commande.


Bien vu pour la fenêtre de sauvegarde.

J'en ai profité pour corriger le problème que j'avais avec les fichiers et dossiers accentués.
Lorsque j'enregistrais dans un dossier contenant un caractère spécial (sous Win 7 x64) le script plantait car il recherchait le dossier :
C:\Users\César\Desktop
au lieu deu :
C:\Users\César\Desktop

J'ai ajouté une case à cocher dans la boîte de dialogue pour encoder en UTF-8... enfin décoder, car je crois qu'en réalité la fonction f.open(chemin, "w") voit le chemin comme étant en ASCII donc le fait de décoder permet de l'encoder ensuite correctement en utf-8, (ou en ANSI vu que ça semble être lié à Windows) ou une connerie du genre, je tirerai ça au clair un peu plus tard.


chemin = c4d.storage.SaveDialog()
chemin = chemin.decode('UTF-8', 'strict')En tout cas, là, tout marche !