MAJ 04/09/2026
Parfois la mise consécutive de nœuds Python requière une petite
vigilance, pour exemple lorsqu'il faut exécuter des nœuds python avec
plusieurs chemin d'exécution possible, il faut gérer les conditions
d'exécution dans chaque nœud.
Bien que pour les petits/moyens scripts, la gestion des opérations
conditionnel peut être réalisé dans un seul nœud Python, pour les plus
gros script, une alternative peut être de scinder le script en plusieurs
"morceaux", comme lorsqu'un travaille sur un projet Python avec
plusieurs fichiers/classes.
L'objectif est donc d'utiliser des noeuds Python comme
librairies/modules interne.
Un exemple avec un mini projet (chaque couleur représente un module), ici
le but est d'extraire des informations des éléments de la maquette (sous
projets ou utilisateur/créateur ) et d'exporter le résultat en Excel ou au
format csv.
Ce qui donne sous Dynamo le graphe suivant où chaque module est en fait
une classe Python:
2 méthodes pour utiliser ces classes dans le nœud principal (nœud Main) :
- La méthode simple :
Une instance de classe est créée dans les nœuds respectifs ou dans le
nœud Python principal, puis on utilise les propriétés et méthodes des
objets créés.
- La méthode plus complexe :
On importe les classes comme des modules Python interne.
Enfin pour que l'importation soit valide et que l'on puisse filtrer les
imports personnalisés il est nécessaire d'implémenter 2 méthodes :
"find_module()" et "load_module()"
Note 1 :
Les méthodes "find_module()" et
"load_module()" sont dépréciées depuis la version 3.4 de
Python. Les méthodes respectives à utiliser sont
"find_spec()" et (create_module() + exec_module())
À noté que le mécanisme d'importation essaie
find_module() uniquement si le chercheur n'implémente pas
find_spec().
De plus amples informations ici :
https://docs.python.org/fr/3/reference/import.html (voir paragraphe sur les Métas-chemins)
https://docs.python.org/3/library/importlib.html
Un des avantages avec le moteur CPython3 de Dynamo c'est que l'ensemble des nœuds Python sont exécuté dans un même environnement Python.
Nœud Python de Classe Python convertie en module
-
# https://stackoverflow.com/questions/65009309/dynamically-import-module-from-memory-in-python-3-using-hooks
import sys
import clr
import importlib
from importlib.abc import Loader, MetaPathFinder
import types
import logging
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
# OLD LOADER
# class StringLoader(Loader):
#
# def __init__(self, modules):
# self._modules = modules
#
# def has_module(self, fullname):
# return (fullname in self._modules)
#
# def create_module(self, spec):
# if self.has_module(spec.name):
# module = types.ModuleType(spec.name)
# return self._modules[spec.name]()
#
# def exec_module(self, module):
# pass
#
#
# class StringFinder(MetaPathFinder):
#
# def __init__(self, loader):
# self._loader = loader
#
# def find_spec(self, fullname, path, target=None):
# if self._loader.has_module(fullname):
# return importlib.machinery.ModuleSpec(fullname, self._loader)
#
## NEW LOADER
class StringLoader(Loader):
def __init__(self, modules, marker):
self._modules = modules
self._marker = marker
def has_module(self, fullname):
return fullname in self._modules
# Create a genuine Python module instead of returning a class instance
def create_module(self, spec):
if self.has_module(spec.name):
return types.ModuleType(spec.name)
return None
# Populate the module with its class, singleton, and compatible method proxies
def exec_module(self, module):
cls = self._modules[module.__name__]
inst = cls()
module.__dynamo_marker__ = self._marker
module.Instance = inst
# Expose the class under its true name
setattr(module, cls.__name__, cls)
# Expose all public content intelligently
for name in cls.__dict__:
if name.startswith("_"):
continue
attr = getattr(cls, name)
# Check if it's a function and has a compiled code object
if callable(attr) and hasattr(attr, "__code__"):
varnames = attr.__code__.co_varnames
# If the first argument is named 'self', it's an instance method
if varnames and varnames[0] == 'self':
setattr(module, name, getattr(inst, name))
else:
setattr(module, name, attr)
else:
setattr(module, name, attr)
class StringFinder(MetaPathFinder):
def __init__(self, loader, marker):
self._loader = loader
self._marker = marker
def find_spec(self, fullname, path=None, target=None):
if self._loader.has_module(fullname):
return importlib.machinery.ModuleSpec(fullname, self._loader)
return None
class Foo():
guid = "a8c3aa76-f731-4086-ae08-8cb41464e425"
logger = logging.getLogger()
def __init__(self):
self.args = [chr(i) for i in range(97, 103)]
def __name__(self):
return "Foo"
@staticmethod
def setLogger(logger):
Foo.logger = logger
print(Foo.logger.name)
def bar(self):
Foo.logger.info("bar")
return ', '.join(self.args)
def Test(self):
Foo.logger.info("Test")
return Point.ByCoordinates(1,2,-3)
class DataTest():
def __init__(self, i=2):
self._i = i
@property
def number(self):
return self._i
@number.setter
def number(self, value):
self._i = value
@property
def double(self):
return self._i * 2
moduleName = Foo.__name__
# MAKE A DICT OF MODULES
modules = {moduleName: Foo}
# Stable marker identifies only this custom finder
marker = "DynamoModule:{0}:{1}".format(Foo.guid, moduleName)
# PURGE ONLY PREVIOUS FINDERS REGISTERED BY THIS MODULE
sys.meta_path[:] = [m for m in sys.meta_path if getattr(m, "_marker", None) != marker]
# DEL PREVIOUS MODULE IN sys.modules TO LOAD THE CURRENT CLASS DEFINITION
if moduleName in sys.modules:
del sys.modules[moduleName]
# IMPORT A GENUINE MODULE THROUGH sys.meta_path
loader = StringLoader(modules, marker)
finder = StringFinder(loader, marker)
sys.meta_path.append(finder)
import Foo
####################################################################################
##### NOW MODULE Foo IS ACCESSIBLE IN ALL OTHER PYTHON NODES JUST NEED IMPORT ######
####################################################################################
OUT = 0
Nœud Python de l'utilisation de Classe Python
import sys
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Foo
import Foo2
pta = Foo.Test()
ptb = Foo2.Test()
OUT = Line.ByStartPointEndPoint(pta, ptb), Foo.bar()
-
-
Note 2:
- afin de facilité l'import les classes passées à la
variable OUT n'ont pas de paramètres dans leur
constructeur.
- la variable/propriété "guid" (généré aléatoirement)
permet de filtrer les imports d'autres librairies afin qu'elles
ne passent pas par ce
hook.
Le contenu des différents nœuds Python
- Les modules
- La classe pour l'exportation
- La classe pour l'interface "Utilisateur"
- La classe pour l'Analyse des Éléments de la maquette
- Enfin le nœud principal où l'on gère l'import des modules et les différentes conditions suivant le choix d'utilisateur
Outre d'avoir un script avec des méthodes mieux organisées, un
des avantages c'est l'ajout facilité ultérieur de nouvelles
fonctionnalités / méthodes.
L'exemple des scripts ci-dessus au format Dynamo est disponible ici









0 commentaires:
Enregistrer un commentaire