Dans ce fichier, un robot a été modélisé et animé. Les morceaux de robots (les Québécois de ma génération verront la référence :D) se trouvent dans une collection nommée “Robot parts”
On voudrait expérimenter avec de la simulation de particules (la première simulation intégrée au Geo Nodes) à partir de la géométrie du robot.
Comme le robot est composé de plusieurs objets distincts, les amener à la main en tant que “Object Info” dans un système de geo nodes serait fastidieux (il y a une centaine objets!).
Commençons par le créer un objet pour avoir un système de geo nodes. Un Plane suffit.
⚠️
Assurez-vous de ne pas être dans la collection “Robot parts”!
Aller dans le système de Geo Nodes et faire New en s’assurant que le Plane est sélectionné. Nommer le geo nodes “Particle Sim” et l’épingler.
Maintenant on va écrire un script Python qui va aller chercher tous les objets de la collection “Robot parts”, les ajouter de manière ordonnée avec full cable management en tant que noeuds Object Info et les connecter à un Join Geometry.
Aller dans l’espace de travail “Scripting”.
Faites “Text → New”
Copier le code suivant:
import bpydef add_object_info_nodes_from_collection(collection_name): # Get the active object and its Geo Node tree obj = bpy.context.active_object if not obj or not obj.modifiers or obj.modifiers[-1].type != 'NODES': print("No active object with a Geometry Nodes modifier selected!") return geo_nodes = obj.modifiers[-1].node_group nodes = geo_nodes.nodes links = geo_nodes.links # Find or create a Join Geometry node join_node = None for node in nodes: if node.type == 'JOIN_GEOMETRY': join_node = node break if not join_node: join_node = nodes.new(type='GeometryNodeJoinGeometry') # Load the collection collection = bpy.data.collections.get(collection_name) if not collection: print(f"Collection '{collection_name}' not found!") return # Create a Frame node to hold all Object Info nodes frame = nodes.new(type='NodeFrame') frame.label = f"{collection_name} Objects" frame.name = f"{collection_name}_Frame" frame.width = 400 # Grid layout settings spacing_x = 200 # Horizontal spacing between columns spacing_y = -200 # Vertical spacing between rows nodes_per_column = 10 created_nodes = [] max_col = 0 max_row = 0 for index, obj_in_collection in enumerate(collection.objects): # Calculate grid position col = index // nodes_per_column row = index % nodes_per_column max_col = max(max_col, col) max_row = max(max_row, row) x = -600 + (col * spacing_x) y = row * spacing_y # Create Object Info node obj_info = nodes.new(type='GeometryNodeObjectInfo') obj_info.parent = frame obj_info.location = (x, y) obj_info.inputs['As Instance'].default_value = False obj_info.inputs['Object'].default_value = obj_in_collection if hasattr(obj_info, 'transform_space'): obj_info.transform_space = 'RELATIVE' # Create a Reroute node reroute = nodes.new(type='NodeReroute') reroute.location = (x + 150, y) # Connect Object Info to Reroute links.new(obj_info.outputs['Geometry'], reroute.inputs[0]) # Connect Reroute to Join Geometry links.new(reroute.outputs[0], join_node.inputs['Geometry']) created_nodes.append(obj_info) # Calculate Frame Size (optional: Blender does it automatically if you move nodes manually) frame.location = (-650, 150) frame_width = (max_col + 1) * spacing_x frame_height = (max_row + 1) * abs(spacing_y) # Move Join Geometry node to right of Frame join_node.location = (-600 + frame_width + 300, 0) print(f"Added {len(created_nodes)} Object Info nodes from collection '{collection_name}', framed, with reroutes, 10x10 grid, Join Geometry neatly positioned.")# Direct call (no popup)add_object_info_nodes_from_collection("Robot parts")
En gros, le script crée des Object Info pour chaque objet trouvé dans la collection “Robot parts”, les configure en mode “relative”, les grouper dans un Frame nommé “Robot Parts Objects” et les connecte à un noeud “Join Geometry”.
Déplacer le noeud Group Output assez loin à droite
Cliquer sur le bouton “Run script” (le bouton “Play” du Script Editor.
⚠️
J’ai configuré mon Scripting workspace pour qu’on voit à droite le 3D Viewport ainsi que le Geo Node.
Aussi, il se peut que le système de nodes crée cache les noeuds Group Input et Group Output. Comme les Object Info et le Join Geo sont sélectionnés, vous pouvez les déplacer (G then move with the mouse).
On a terminé avec le script. Revenir au workspace Geometry Nodes.
Connecter la sortie de Join Geometry à l’entrée Group Output. Déconnecter Group Input (on ne veut pas voir le Plane).
Cacher les objects Robot Parts dans l’Outliner, on veut voir le résultat du système de geo nodes seulement.
Faites Play dans le timeline!
Comme les objects sont la géométrie du robot tel qu’animé, l’animation est conservée dans la version geo nodes. Chouette non?
On est prêt pour de la simulation de particules!
Simulation 1 - Onion Skin shadow trail
Ajouter une Simulation Zone et connecter la sortie du Join Geomtery à l’entrée d’un 2e Join geometry qui, lui, se trouve dans la simulation Zone.
Faites Play.
Que constatez-vous?
That’s right! Les geo nodes, sans Simulation zone, sont “stateless”, c’est-à dire que tout est calculé à chaque frame.
Mais avec un simulation Zone, tout d’un coup, les frames précédents sont accumulés! On vient de créer un “Stateful” machine!
Ok, et si on voulait retenir seulement, disons, les 5 frames précédents seulement?
On veut effacer tout ce qui est plus ancien que “n” frames. Comment faire?
On va commencer par garder en mémoire la géométrie courante:
Maintenant, il nous suffit de retirer du Join Geometry de la Simulation Zone, les géométries des frames plus petits qu’une certaine distance du Frame courant.
Ici, on “Delete Geometry” tout ce qui fait partie de “Selection”.
Ce qui aliment le socket Selection du noeud Delete Geometry, c’est le noeud Compare → Less than, qui évalue la valeur de “Frame_courant” (dans le noeud Named Attributed) par rapport au No de frame de l’animation (Scene Time) moins une certaine valeur, ici 4.
Ce que ça fait effectivement, c’est effacer toutes les géométries dont le No de frame est plus petit que le frame courant moins 4.
L’effet est un trail Onion Skin.
Je réalise que je devrais nommer “Frame_courant” à “stored_frame” plutôt, car il n’est plus courant dès qu’il ne l’est plus :) Je le fais de suite:
⚠️
Ah oui, si vous changez quelque chose dans la simulation zone, faut revenir au frame 1 pour recalculer la cache et voir l’effet du chngement.