--- /dev/null
+"""
+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)