network to blender
authorPierre Ratinaud <ratinaud@univ-tlse2.fr>
Tue, 12 Jul 2016 13:29:11 +0000 (15:29 +0200)
committerPierre Ratinaud <ratinaud@univ-tlse2.fr>
Tue, 12 Jul 2016 13:29:11 +0000 (15:29 +0200)
network_to_blender.py [new file with mode: 0644]

diff --git a/network_to_blender.py b/network_to_blender.py
new file mode 100644 (file)
index 0000000..8972ffc
--- /dev/null
@@ -0,0 +1,213 @@
+"""
+Blender script. Draws a node-and-edge network in blender, randomly distributed
+spherically.
+
+14 Sept 2011: Added collision detection between nodes
+
+30 Nov 2012: Rewrote. Switched to JSON, and large Blender speed boosts.
+
+Written by Patrick Fuller, patrickfuller@gmail.com, 11 Sept 11
+
+modifications by Pierre Ratinaud Feb 2014
+"""
+import bpy
+from math import acos, degrees, pi
+from mathutils import Vector
+from copy import copy
+
+import json
+from random import choice
+import sys
+
+# Colors to turn into materials
+#colors = {"purple": (178, 132, 234), "gray": (11, 11, 11),
+#          "green": (114, 195, 0), "red": (255, 0, 75),
+#          "blue": (0, 131, 255), "clear": (0, 131, 255),
+#          "yellow": (255, 187, 0), "light_gray": (118, 118, 118)}
+
+# Normalize to [0,1] and make blender materials
+def make_colors(colors):
+    for key, value in colors.items():
+        value = [x / 255.0 for x in value]
+        bpy.data.materials.new(name=key)
+        bpy.data.materials[key].diffuse_color = value
+        bpy.data.materials[key].specular_intensity = 0.5
+     
+        # Don't specify more parameters if these colors
+        if key == "gray" or key == "light_gray":
+            bpy.data.materials[key].use_transparency = True
+            bpy.data.materials[key].transparency_method = "Z_TRANSPARENCY"
+            bpy.data.materials[key].alpha = 0.2
+     
+        # Transparency parameters
+        else :
+            bpy.data.materials[key].use_transparency = True
+            bpy.data.materials[key].transparency_method = "Z_TRANSPARENCY"
+            bpy.data.materials[key].alpha = 0.6 if key == "clear" else 0.8
+            bpy.data.materials.new(name = key + 'sphere')
+            bpy.data.materials[key + 'sphere'].diffuse_color = value
+            bpy.data.materials[key + 'sphere'].specular_intensity = 0.1
+            bpy.data.materials[key + 'sphere'].use_transparency = True
+            bpy.data.materials[key + 'sphere'].transparency_method = "Z_TRANSPARENCY"
+            bpy.data.materials[key + 'sphere'].alpha = 0.1
+        #bpy.data.materials[key].raytrace_transparency.fresnel = 0.1
+        #bpy.data.materials[key].raytrace_transparency.ior = 1.15
+
+
+def draw_network(network, edge_thickness=0.25, node_size=3, directed=False, spheres = True):
+    """ Takes assembled network/molecule data and draws to blender """
+
+    colors = [tuple(network["nodes"][node]['color']) for node in network["nodes"]]
+    cols = list(set(colors))
+    colors = dict(zip([str(col) for col in cols],cols))
+    colors.update({"light_gray": (118, 118, 118), "gray": (11, 11, 11)})
+    make_colors(colors)
+    
+    # Add some mesh primitives
+    bpy.ops.object.select_all(action='DESELECT')
+    #bpy.ops.mesh.primitive_uv_sphere_add()
+    bpy.ops.mesh.primitive_uv_sphere_add(segments = 64, ring_count = 32)
+    sphere = bpy.context.object
+    bpy.ops.mesh.primitive_cylinder_add()
+    cylinder = bpy.context.object
+    cylinder.active_material = bpy.data.materials["light_gray"]
+    bpy.ops.mesh.primitive_cone_add()
+    cone = bpy.context.object
+    cone.active_material = bpy.data.materials["light_gray"]
+    #bpy.ops.object.text_add(view_align=True)
+    
+
+    # Keep references to all nodes and edges
+    shapes = []
+    # Keep separate references to shapes to be smoothed
+    shapes_to_smooth = []
+    #val to div coordonnate
+    divval = 0.1
+
+    # Draw nodes
+    for key, node in network["nodes"].items():
+
+        # Coloring rule for nodes. Edit this to suit your needs!
+        col = str(tuple(node.get("color", choice(list(colors.keys())))))
+
+        # Copy mesh primitive and edit to make node
+        # (You can change the shape of drawn nodes here)
+        if spheres :
+            node_sphere = sphere.copy()
+            node_sphere.data = sphere.data.copy()
+            node_sphere.location = [val/divval for val in node["location"]]
+            #node_sphere.dimensions = [node_size] * 3
+            node_sphere.dimensions = [node["weight"]/10] * 3
+            #newmat = bpy.data.materials[col]
+            #newmat.alpha = 0.01
+            node_sphere.active_material = bpy.data.materials[col + 'sphere']
+            bpy.context.scene.objects.link(node_sphere)
+            shapes.append(node_sphere)
+            shapes_to_smooth.append(node_sphere)
+        
+        #node_text = text.copy()
+        #node_text.data = text.data.copy()
+        #node_text.location = node["location"]
+        bpy.ops.object.text_add(view_align=False, location = [val/divval for val in node["location"]])
+        #bpy.ops.object.text_add(view_align=False, location = [val for val in node["location"]])
+        bpy.ops.object.editmode_toggle()
+        bpy.ops.font.delete()
+        bpy.ops.font.text_insert(text=key)
+        
+        bpy.ops.object.editmode_toggle()
+        bpy.data.curves[bpy.context.active_object.name].size = node["weight"] /10
+        bpy.data.curves[bpy.context.active_object.name].bevel_depth = 0.044
+        bpy.data.curves[bpy.context.active_object.name].offset = 0
+        bpy.data.curves[bpy.context.active_object.name].extrude = 0.2
+        bpy.data.curves[bpy.context.active_object.name].align = "CENTER"
+        bpy.context.active_object.rotation_euler = [1.5708,0,1.5708]
+        bpy.context.active_object.active_material = bpy.data.materials[col]
+        const = bpy.context.active_object.constraints.new(type='TRACK_TO')
+        const.target = bpy.data.objects['Camera']
+        const.track_axis = "TRACK_Z"
+        const.up_axis = "UP_Y"
+        
+        #bpy.context.scene.objects.link(bpy.context.active_object)
+        #shapes.append(bpy.context.active_object)
+        #sha* 2 + [mag - node_size]
+        shapes_to_smooth.append(bpy.context.active_object)        
+
+    # Draw edges
+    for edge in network["edges"]:
+
+        # Get source and target locations by drilling down into data structure
+        source_loc = network["nodes"][edge["source"]]["location"]
+        source_loc = [val/divval for val in source_loc]
+        target_loc = network["nodes"][edge["target"]]["location"]
+        target_loc = [val / divval for val in target_loc]
+
+        diff = [c2 - c1 for c2, c1 in zip(source_loc, target_loc)]
+        cent = [(c2 + c1) / 2 for c2, c1 in zip(source_loc, target_loc)]
+        mag = sum([(c2 - c1) ** 2
+                  for c1, c2 in zip(source_loc, target_loc)]) ** 0.5
+
+        # Euler rotation calculation
+        v_axis = Vector(diff).normalized()
+        v_obj = Vector((0, 0, 1))
+        v_rot = v_obj.cross(v_axis)
+        angle = acos(v_obj.dot(v_axis))
+
+        # Copy mesh primitive to create edge
+        edge_cylinder = cylinder.copy()
+        edge_cylinder.data = cylinder.data.copy()
+        edge_cylinder.dimensions = [float(edge['weight'])*10] * 2 + [mag - node_size]
+        #edge_cylinder.dimensions = [edge_thickness] * 2 + [mag - node_size]
+        edge_cylinder.location = cent
+        edge_cylinder.rotation_mode = "AXIS_ANGLE"
+        edge_cylinder.rotation_axis_angle = [angle] + list(v_rot)
+        bpy.context.scene.objects.link(edge_cylinder)
+        shapes.append(edge_cylinder)
+        shapes_to_smooth.append(edge_cylinder)
+
+        # Copy another mesh primitive to make an arrow head
+        if directed:
+            arrow_cone = cone.copy()
+            arrow_cone.data = cone.data.copy()
+            arrow_cone.dimensions = [edge_thickness * 4.0] * 3
+            arrow_cone.location = cent
+            arrow_cone.rotation_mode = "AXIS_ANGLE"
+            arrow_cone.rotation_axis_angle = [angle + pi] + list(v_rot)
+            bpy.context.scene.objects.link(arrow_cone)
+            shapes.append(arrow_cone)
+
+    # Remove primitive meshes
+    bpy.ops.object.select_all(action='DESELECT')
+    sphere.select = True
+    cylinder.select = True
+    cone.select = True
+    #text.select = True
+
+    # If the starting cube is there, remove it
+    if "Cube" in bpy.data.objects.keys():
+        bpy.data.objects.get("Cube").select = True
+    bpy.ops.object.delete()
+
+    # Smooth specified shapes
+    for shape in shapes_to_smooth:
+        shape.select = True
+    #bpy.context.scene.objects.active = shapes_to_smooth[0]
+    #bpy.ops.object.shade_smooth()
+
+    # Join shapes
+    for shape in shapes:
+        shape.select = True
+    #bpy.context.scene.objects.active = shapes[0]
+    #bpy.ops.object.join()
+
+    # Center object origin to geometry
+    bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")
+
+    # Refresh scene
+    bpy.context.scene.update()
+
+# If main, load json and run
+if __name__ == "__main__":
+    with open(sys.argv[3]) as network_file:
+        network = json.load(network_file)
+    draw_network(network)