PDA

Voir la version complète : Générateur non bloquant



César Vonc
08/09/2013, 11h53
Bonjour,

Je cherche à créer un générateur qui ne bloque pas tout Cinema 4D pendant que le calcul de l'objet s'effectue, un peu comme le Booléen que l'on peut annuler avant qu'il termine son opération.


class Essai(plugins.ObjectData):

def __init__(self):
self.SetOptimizeCache(True)

def GetVirtualObjects(self, op, hierarchyhelp):

for i in xrange(5000000) :
c4d.StatusSetBar(i * 0.00002)
a = math.sqrt(2*i)

print "Fini"
c4d.StatusClear()

return c4d.BaseObject(c4d.Ocube)

if __name__ == "__main__":
plugins.RegisterObjectPlugin(id=MODULE_ID, str="Essai",
g=Essai,
description="roundedtube", icon=None,
info=c4d.OBJECT_GENERATOR)Actuellement, on est obligé d'attendre que la boucle se termine, C4D ne veut rien entendre pendant.


Alors j'ai lu dans la doc que la fonction GetVirtualObjects() était utilisée dans un fil d'exécution :

This function is called in a thread context. Please see the important information (http://code.vonc.fr/c4d/python/help/modules/c4d.threading/index.html#threading-information) about threading.Donc je commence à jouer avec la classe BaseThread :


def GetVirtualObjects(self, op, hierarchyhelp):

#c4d.StopAllThreads()
fil = c4d.threading.GeGetCurrentThread()

for i in xrange(5000000) :
c4d.StatusSetBar(i * 0.00002)
a = math.sqrt(2*i)
if fil.TestBreak() :
print "Coupe"
break

print "Fini"
c4d.StatusClear()

return c4d.BaseObject(c4d.Ocube)Sauf que maintenant, au moindre fait et geste, la boucle est interrompue. : P



Je me demande s'il n'y a pas une solution plus sage, car les objets de type Déformateurs (c4d.OBJECT_MODIFIER) peuvent s'annuler d'office, sans qu'on ait rien à bidouiller dans le code. D'autant plus que la classe BaseThread est assez maigre en fonctions.

valkaari
08/09/2013, 18h09
si tu regardes l'exemple du roundtube en c++ il utilise le testBreak.

Par contre il utiliser le HierarchyHelp fourni dans la fonction GetVirtualObject pour obtenir le thread avec un hh->GetThread()

la fonction GeGetCurrentThread est à mon avis trop générique et ne renvoie pas le bon thread à tester.


Le problème c'est que je sais pas si la fonction GetThread est dispo dans la classe HierarchyHelp en python.

J'ai essayé une fois d'utiliser ça en c++ et ça coupé effectivement tout le temps.

edit :
essayes quand même avec un
hierarchyhelp.GetThread()


la fonction est peut être présente mais non documenté

et en passant il faut aussi vérifier que le thread existe bien et qu'il n'a pas été "cassé"

César Vonc
08/09/2013, 19h15
Il ne semble y avoir hélas aucune fonction en Python dans le hierarchyhelper :


AttributeError: 'PyCObject' object has no attribute 'GetThread'De même pour les autres fonctions (GetMg, GetDocument...), snif. J'ai essayé dans la démo de la R15, pour info.

Je vais continuer de creuser la question.

paspas
09/09/2013, 10h00
salut

une idée en passant :

pourquoi ne pas utiliser une autre fonction pour ta boucle : la fonction while

cette fonction est directement conditionnel

une boucle du genre


while ( i < 50000) :
c4d.StatusSetBar(i * 0.00002)
a = math.sqrt(2*i)
if ( test == ta_condition) :
i = 500001
i = i+1

des que la condition du while n'est plus respectée ta boucle s 'arrête

César Vonc
09/09/2013, 13h39
Le soucis est que la condition à vérifier donnera toujours la même réponse étant donné que C4D restera figé tant que la boucle ne sera pas terminée.

Il n'y a apparemment que la fonction TestBreak() qui permet de dire à C4D d'aller voir s'il n'y a pas d'évènement demandant l'arrêt du fil d'exécution.

Sauf que comme l'a signalé Val, GeGetCurrentThread() est trop générique et ne renvoie probablement pas le fil dans lequel s'exécute la boucle.

valkaari
09/09/2013, 15h07
si tu trouves ça m’intéresse hein ^^ ou yann, qui traine dans le coin je t'ai vu !!!

César Vonc
10/09/2013, 00h54
J'ai essayé avec C4DThread, en créant un nouveau fil contenant ma boucle, mais ce coup-ci, je n'arrive pas à l'arrêter avec un évènement extérieur car je ne trouve pas comment le détecter...

La doc stipule pourtant qu'on peut vérifier si l'utilisateur a enfoncé la touche Échap :


C4DThread.TestDBreak(self)
Override this to add user breaks such as pressing ESC. This function is called by TestBreak() (http://code.vonc.fr/c4d/python/help/modules/c4d.threading/C4DThread/index.html?highlight=event#C4DThread.TestBreak).Ma is rien n'est expliqué.

On peut cependant utiliser un compteur dans TestDBreak, d'après Wilfried : http://www.plugincafe.com/forum/forum_posts.asp?TID=6589&PID=27325#27325
Mais bon, je vois pas trop l'intérêt, on peut déjà vérifier le temps à chaque boucle sans même utiliser C4DThread. : /


Voici néanmoins comment j'ai utilisé C4DThread, que rien ne semble pouvoir arrêter malgré le Wait(True) qui est censé couper en fonction des évènements C4D, si j'ai bien compris :



import os
import math
import c4d

from c4d import plugins, utils, bitmaps
from c4d.threading import C4DThread

MODULE_ID = 1000007

class Fil(C4DThread):

def TestDBreak(self) :
#bc = c4d.BaseContainer()
#print c4d.gui.GetInputEvent(c4d.BFM_INPUT_KEYBOARD, bc) # Marche pas :(
print "ee"
return False

def Main(self):
for j in xrange(10) :
if self.TestBreak() :
print "Coupe"
break

for i in xrange(500000) :
a = math.sqrt(2*i)

c4d.StatusSetBar(j * 10)

class Essai(plugins.ObjectData):

def __init__(self):
self.SetOptimizeCache(True)

def GetVirtualObjects(self, op, hh):

f = Fil()
f.Start(c4d.THREADMODE_ASYNC, c4d.THREADPRIORITY_NORMAL)

f.Wait(True)
#f.End(True) Coupe directement

c4d.StatusClear()
print "Fini"
return c4d.BaseObject(c4d.Ocube)

if __name__ == "__main__":
plugins.RegisterObjectPlugin(id=MODULE_ID, str="Essai",
g=Essai,
description="roundedtube", icon=None,
info=c4d.OBJECT_GENERATOR)

César Vonc
12/09/2013, 13h35
Alors, j'ai trouvé une solution qui semble fonctionner, en utilisant la classe threading.Thread de Python plutôt que celle de C4D.

Le calcul de l'objet s'effectue en parallèle et renvoie un neutre tant qu'il n'est pas terminé.
Si le document est pour le rendu, le calcul de l'objet ne s'effectue pas en parallèle (self.fil.join() force l'attente).

Il y a sûrement encore quelques petits détails à régler, je ne sais pas si ça plaît bien à C4D car on peut faire tout ce qu'on veut pendant que l'objet se calcule, ce qui implique pas mal de vérifications.


import os
import math
import c4d
import threading

from c4d import plugins, utils, bitmaps

MODULE_ID = 1000007

class Fil(threading.Thread):
op = None
resultat = None

def __init__(self, op) :
threading.Thread.__init__(self)
self.op = op

def verif(self) :
if not self.op.IsAlive() : return True
if not self.op.GetDocument() : return True
if not self.op.GetDocument().IsAlive() : return True
if not self.op[c4d.ID_BASEOBJECT_GENERATOR_FLAG] : return True
return False

def run(self) :
print "Commence calcul"

for j in xrange(10) :

if self.verif() :
print "Coupe"
return

for i in xrange(1000000) :
a = math.sqrt(2*i)

c4d.StatusSetBar(j * 10)

print "Termine calcul"
self.resultat = c4d.BaseObject(c4d.Ocube)
c4d.StatusClear()
self.op.Touch()

class Essai(plugins.ObjectData):
fil = None

def __init__(self):
self.SetOptimizeCache(True)

def GetVirtualObjects(self, op, hh):

# Si le fil existe déjà, s'il a un résultat calculé, si ce résultat est utilisable
if self.fil and self.fil.resultat and self.fil.resultat.IsAlive() :
# Renvoie le résultat déjà calculé
return self.fil.resultat

else :
# Si le fil existe déjà et s'il est en cours de calcul, renvoyer un neutre
if self.fil and self.fil.isAlive() : return c4d.BaseObject(c4d.Onull)

# Créé le nouveau fil et renvoie un neutre pendant le calcul
self.fil = Fil(op)

self.fil.start()
if (op.GetDocument() != c4d.documents.GetActiveDocument()) : # Si Rendu
self.fil.join() # Attendre que le calcul se termine et renvoyer le résultat
return self.fil.resultat

return c4d.BaseObject(c4d.Onull)


if __name__ == "__main__":
plugins.RegisterObjectPlugin(id=MODULE_ID, str="Essai",
g=Essai,
description="roundedtube", icon=None,
info=c4d.OBJECT_GENERATOR)



Par ailleurs, j'ai voulu au début utiliser la classe multiprocessing de Python qui, d'après ce que j'ai compris, permet d'utiliser tous les processeurs à 100 %, mais pas moyen, j'ai une chiée d'erreurs « pickling » dont je ne comprends pas vraiment le sens.