Created
October 7, 2025 14:19
-
-
Save ji-podhead/9487f6cc25f80b2861dc0855c36f9976 to your computer and use it in GitHub Desktop.
blender visualize point cloud
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import bpy | |
| import json | |
| import numpy as np # Wird nicht direkt benötigt, da json.load verwendet wird | |
| from mathutils import Vector | |
| from time import time | |
| def visualize_particles_as_instances(json_filepath, particle_scale=1, instance_radius=0.05, collection_name="VoxelParticleInstances"): | |
| """ | |
| Visualisiert Partikelpositionen aus einer JSON-Datei in Blender, | |
| indem es Low-Poly Icosphären auf den Vertices eines Punktwolken-Meshes instanziiert. | |
| Basierend auf TLouskys Ansatz. | |
| Args: | |
| json_filepath (str): Der Pfad zur JSON-Datei mit den Voxel-Daten. | |
| particle_scale (float): Ein Skalierungsfaktor für die Partikelpositionen (z.B. 1 für keine Skalierung, 25 für Vergrösserung). | |
| instance_radius (float): Der Radius der instanziierten Icosphären. | |
| collection_name (str): Der Name der Collection, in die die Objekte eingefügt werden. | |
| """ | |
| t = time() | |
| C = bpy.context | |
| # Sicherstellen, dass die Datei existiert und laden | |
| try: | |
| with open(json_filepath, 'r') as fh: | |
| data = json.load(fh) | |
| except FileNotFoundError: | |
| print(f"Fehler: Datei nicht gefunden unter '{json_filepath}'") | |
| return | |
| except json.JSONDecodeError: | |
| print(f"Fehler: Konnte JSON-Daten aus '{json_filepath}' nicht lesen. Ist die Datei gültig?") | |
| return | |
| positions = data.get("positions") | |
| if not positions or len(positions) % 3 != 0: | |
| print("Fehler: Ungültige oder fehlende 'positions'-Daten in der JSON-Datei.") | |
| return | |
| num_verts = len(positions) // 3 | |
| print(f'Lade {num_verts} Partikel aus "{json_filepath}"...') | |
| # Collection erstellen oder holen | |
| if collection_name not in bpy.data.collections: | |
| particle_collection = bpy.data.collections.new(collection_name) | |
| C.scene.collection.children.link(particle_collection) | |
| else: | |
| particle_collection = bpy.data.collections[collection_name] | |
| # Optional: Bestehende Objekte in dieser Collection löschen, um Überschneidungen zu vermeiden | |
| for obj_to_remove in [o for o in particle_collection.objects if o.name.startswith(('pc_instancer', 'Icosphere_Instance_Base'))]: | |
| bpy.data.objects.remove(obj_to_remove, do_unlink=True) | |
| print(f"Vorhandene Objekte in '{collection_name}' bereinigt.") | |
| # Create and arrange mesh data for the point cloud | |
| verts = [] | |
| for i in range(num_verts): | |
| x = positions[i*3] * particle_scale | |
| y = positions[i*3+1] * particle_scale | |
| z = positions[i*3+2] * particle_scale | |
| verts.append(Vector((x, y, z))) | |
| me = bpy.data.meshes.new('pc_data_mesh') | |
| me.from_pydata(verts, [], []) | |
| # Create mesh object for the point cloud and link to scene collection | |
| pc_obj = bpy.data.objects.new( 'pc_instancer', me ) | |
| particle_collection.objects.link( pc_obj ) # Link to our specific collection | |
| # Add minimal icosphere to use as instance object | |
| # Sicherstellen, dass die icosphere in der aktuellen Szene erstellt wird, um sie direkt zu bearbeiten | |
| # und dann in die Ziel-Collection zu verschieben | |
| # Temporäre Collection für icosphere, wenn nicht im Kontext der Scene Collection | |
| bpy.ops.mesh.primitive_ico_sphere_add( subdivisions=1, radius=instance_radius, location=(0,0,0) ) | |
| is_obj = C.object | |
| is_obj.name = "Icosphere_Instance_Base" | |
| # Verschiebe die Icosphere in unsere Partikel-Collection und entferne sie aus der aktuellen Collection | |
| if is_obj.name not in particle_collection.objects: | |
| # Finde die Collection, in der die Icosphere gerade ist (oft die Scene Collection) | |
| for col in is_obj.users_collection: | |
| col.objects.unlink(is_obj) | |
| particle_collection.objects.link(is_obj) | |
| # Material für die Instanzen hinzufügen (standardmässig weiss/grau) | |
| if "VoxelInstanceMaterial" not in bpy.data.materials: | |
| instance_mat = bpy.data.materials.new(name="VoxelInstanceMaterial") | |
| instance_mat.use_nodes = True | |
| principled_bsdf = instance_mat.node_tree.nodes.get('Principled BSDF') | |
| if principled_bsdf: | |
| principled_bsdf.inputs['Base Color'].default_value = (0.5, 0.5, 0.5, 1.0) # Grau | |
| principled_bsdf.inputs['Roughness'].default_value = 0.7 | |
| else: | |
| instance_mat = bpy.data.materials["VoxelInstanceMaterial"] | |
| if not is_obj.data.materials: | |
| is_obj.data.materials.append(instance_mat) | |
| else: | |
| is_obj.data.materials[0] = instance_mat | |
| # Set instancing props for the point cloud object | |
| pc_obj.instance_type = 'VERTS' | |
| # Wichtig: show_instancer_for_viewport auf True lassen, sonst sieht man nichts | |
| pc_obj.show_instancer_for_viewport = True # Zeigt die Instanzen im Viewport | |
| pc_obj.show_instancer_for_render = True # Rendert die Instanzen | |
| # Verstecke das Basis-Instanz-Objekt selbst, da wir nur seine Instanzen sehen wollen | |
| is_obj.hide_set(True) | |
| is_obj.hide_render = True | |
| # Set instance parenting (parent icosphere to verts) | |
| # Wähle zuerst den Instancer (pc_obj) und dann die Instanz (is_obj) aus | |
| bpy.ops.object.select_all(action='DESELECT') | |
| pc_obj.select_set(True) | |
| C.view_layer.objects.active = pc_obj | |
| # Hier der entscheidende Schritt: Child (is_obj) wird Parent (pc_obj) | |
| # in der Instanz-Hierarchie hinzugefügt. | |
| # Wähle zuerst das Kind (die Instanz) und dann den Elternteil (den Instancer). | |
| is_obj.select_set(True) | |
| C.view_layer.objects.active = pc_obj # Das aktive Objekt sollte der Instancer sein | |
| # Parent setzen (Instanz-Objekt wird zum Child des Instancer-Objekts) | |
| bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) | |
| print(f'Total time = {time() - t} seconds' ) | |
| print(f"Partikel als Instanzen in Collection '{collection_name}' erstellt.") | |
| print("Die Basis-Icosphere ('Icosphere_Instance_Base') ist im Viewport und Render versteckt.") | |
| # --- ANWENDUNG DES SKRIPTS --- | |
| if __name__ == "__main__": | |
| # Pfad zur JSON-Datei (passe diesen an, wo dein Export-Skript die Datei speichert!) | |
| json_input_file = bpy.path.abspath("//voxel_data.json") | |
| # Skalierungsfaktor für die Positionen (wenn die Koordinaten sehr klein sind) | |
| position_scale_factor = 1.0 # 1.0 für keine zusätzliche Skalierung | |
| # Radius der instanziierten Partikel | |
| particle_instance_radius = 0.05 | |
| # Vorherige Auswahl aufheben | |
| bpy.ops.object.select_all(action='DESELECT') | |
| visualize_particles_as_instances(json_input_file, | |
| particle_scale=position_scale_factor, | |
| instance_radius=particle_instance_radius) | |
| print("Skript beendet.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment