multisplit
[iramuteq] / autres / network_to_blender.py
1 # -*- coding: utf-8 -*-
2 """
3 Blender script. Draws a node-and-edge network in blender, randomly distributed
4 spherically.
5
6 14 Sept 2011: Added collision detection between nodes
7
8 30 Nov 2012: Rewrote. Switched to JSON, and large Blender speed boosts.
9
10 Written by Patrick Fuller, patrickfuller@gmail.com, 11 Sept 11
11
12 modifications by Pierre Ratinaud Feb 2014
13 """
14 #------------------------------------
15 # import des modules python
16 #------------------------------------
17 import bpy
18 from math import acos, degrees, pi
19 from mathutils import Vector
20 from copy import copy
21
22 import json
23 from random import choice
24 import sys
25
26
27 # Colors to turn into materials
28 #colors = {"purple": (178, 132, 234), "gray": (11, 11, 11),
29 #          "green": (114, 195, 0), "red": (255, 0, 75),
30 #          "blue": (0, 131, 255), "clear": (0, 131, 255),
31 #          "yellow": (255, 187, 0), "light_gray": (118, 118, 118)}
32
33
34 # Normalize to [0,1] and make blender materials
35 def make_colors(colors):
36     for key, value in list(colors.items()):
37         value = [x / 255.0 for x in value]
38         bpy.data.materials.new(name=key)
39         bpy.data.materials[key].diffuse_color = value
40         bpy.data.materials[key].specular_intensity = 0.5
41         # Don't specify more parameters if these colors
42         if key == "gray" or key == "light_gray":
43             bpy.data.materials[key].use_transparency = True
44             bpy.data.materials[key].transparency_method = "Z_TRANSPARENCY"
45             bpy.data.materials[key].alpha = 0.2
46         # Transparency parameters
47         else :
48             bpy.data.materials[key].use_transparency = True
49             bpy.data.materials[key].transparency_method = "Z_TRANSPARENCY"
50             bpy.data.materials[key].alpha = 0.6 if key == "clear" else 0.8
51             bpy.data.materials.new(name = key + 'sphere')
52             bpy.data.materials[key + 'sphere'].diffuse_color = value
53             bpy.data.materials[key + 'sphere'].specular_intensity = 0.1
54             bpy.data.materials[key + 'sphere'].use_transparency = True
55             bpy.data.materials[key + 'sphere'].transparency_method = "Z_TRANSPARENCY"
56             bpy.data.materials[key + 'sphere'].alpha = 0.1
57         #bpy.data.materials[key].raytrace_transparency.fresnel = 0.1
58         #bpy.data.materials[key].raytrace_transparency.ior = 1.15
59
60 def draw_network(network, edge_thickness=0.25, node_size=3, directed=False, spheres = True):
61     """ Takes assembled network/molecule data and draws to blender """
62     colors = [tuple(network["nodes"][node]['color']) for node in network["nodes"]]
63     cols = list(set(colors))
64     colors = dict(list(zip([str(col) for col in cols],cols)))
65     colors.update({"light_gray": (118, 118, 118), "gray": (11, 11, 11)})
66     make_colors(colors)
67     # Add some mesh primitives
68     bpy.ops.object.select_all(action='DESELECT')
69     #bpy.ops.mesh.primitive_uv_sphere_add()
70     bpy.ops.mesh.primitive_uv_sphere_add(segments = 64, ring_count = 32)
71     sphere = bpy.context.object
72     bpy.ops.mesh.primitive_cylinder_add()
73     cylinder = bpy.context.object
74     cylinder.active_material = bpy.data.materials["light_gray"]
75     bpy.ops.mesh.primitive_cone_add()
76     cone = bpy.context.object
77     cone.active_material = bpy.data.materials["light_gray"]
78     #bpy.ops.object.text_add(view_align=True)
79     # Keep references to all nodes and edges
80     shapes = []
81     # Keep separate references to shapes to be smoothed
82     shapes_to_smooth = []
83     #val to div coordonnate
84     divval = 0.05
85     # Draw nodes
86     for key, node in list(network["nodes"].items()):
87         # Coloring rule for nodes. Edit this to suit your needs!
88         col = str(tuple(node.get("color", choice(list(colors.keys())))))
89         # Copy mesh primitive and edit to make node
90         # (You can change the shape of drawn nodes here)
91         if spheres :
92             node_sphere = sphere.copy()
93             node_sphere.data = sphere.data.copy()
94             node_sphere.location = [val/divval for val in node["location"]]
95             #node_sphere.dimensions = [node_size] * 3
96             node_sphere.dimensions = [node["weight"]] * 3
97             #newmat = bpy.data.materials[col]
98             #newmat.alpha = 0.01
99             node_sphere.active_material = bpy.data.materials[col + 'sphere']
100             bpy.context.scene.objects.link(node_sphere)
101             shapes.append(node_sphere)
102             shapes_to_smooth.append(node_sphere)
103         #node_text = text.copy()
104         #node_text.data = text.data.copy()
105         #node_text.location = node["location"]
106         bpy.ops.object.text_add(view_align=False, location = [val/divval for val in node["location"]])
107         #bpy.ops.object.text_add(view_align=False, location = [val for val in node["location"]])
108         bpy.ops.object.editmode_toggle()
109         bpy.ops.font.delete()
110         bpy.ops.font.text_insert(text=key)
111         bpy.ops.object.editmode_toggle()
112         bpy.data.curves[bpy.context.active_object.name].size = node["weight"]/2
113         bpy.data.curves[bpy.context.active_object.name].bevel_depth = 0.044
114         bpy.data.curves[bpy.context.active_object.name].offset = 0
115         bpy.data.curves[bpy.context.active_object.name].extrude = 0.2
116         bpy.data.curves[bpy.context.active_object.name].align = "CENTER"
117         bpy.context.active_object.rotation_euler = [1.5708,0,1.5708]
118         bpy.context.active_object.active_material = bpy.data.materials[col]
119         #bpy.ops.object.mode_set(mode='OBJECT')
120         #Extrude the text
121         #bpy.context.object.data.extrude = 0.03
122         #Convert text to mesh
123         #bpy.context.active_object.convert(target='MESH', keep_original=False)
124         const = bpy.context.active_object.constraints.new(type='TRACK_TO')
125         const.target = bpy.data.objects['Camera']
126         const.track_axis = "TRACK_Z"
127         const.up_axis = "UP_Y"
128         #bpy.context.scene.objects.link(bpy.context.active_object)
129         #shapes.append(bpy.context.active_object)
130         #sha* 2 + [mag - node_size]
131         shapes_to_smooth.append(bpy.context.active_object)        
132     # Draw edges
133     for edge in network["edges"]:
134         # Get source and target locations by drilling down into data structure
135         source_loc = network["nodes"][edge["source"]]["location"]
136         source_loc = [val/divval for val in source_loc]
137         target_loc = network["nodes"][edge["target"]]["location"]
138         target_loc = [val / divval for val in target_loc]
139         diff = [c2 - c1 for c2, c1 in zip(source_loc, target_loc)]
140         cent = [(c2 + c1) / 2 for c2, c1 in zip(source_loc, target_loc)]
141         mag = sum([(c2 - c1) ** 2
142                   for c1, c2 in zip(source_loc, target_loc)]) ** 0.5
143         # Euler rotation calculation
144         v_axis = Vector(diff).normalized()
145         v_obj = Vector((0, 0, 1))
146         v_rot = v_obj.cross(v_axis)
147         angle = acos(v_obj.dot(v_axis))
148         # Copy mesh primitive to create edge
149         edge_cylinder = cylinder.copy()
150         edge_cylinder.data = cylinder.data.copy()
151         edge_cylinder.dimensions = [float(edge['weight'])/10] * 2 + [mag - node_size]
152         #edge_cylinder.dimensions = [edge_thickness] * 2 + [mag - node_size]
153         edge_cylinder.location = cent
154         edge_cylinder.rotation_mode = "AXIS_ANGLE"
155         edge_cylinder.rotation_axis_angle = [angle] + list(v_rot)
156         bpy.context.scene.objects.link(edge_cylinder)
157         shapes.append(edge_cylinder)
158         shapes_to_smooth.append(edge_cylinder)
159         # Copy another mesh primitive to make an arrow head
160         if directed:
161             arrow_cone = cone.copy()
162             arrow_cone.data = cone.data.copy()
163             arrow_cone.dimensions = [edge_thickness * 4.0] * 3
164             arrow_cone.location = cent
165             arrow_cone.rotation_mode = "AXIS_ANGLE"
166             arrow_cone.rotation_axis_angle = [angle + pi] + list(v_rot)
167             bpy.context.scene.objects.link(arrow_cone)
168             shapes.append(arrow_cone)
169     # Remove primitive meshes
170     bpy.ops.object.select_all(action='DESELECT')
171     sphere.select = True
172     cylinder.select = True
173     cone.select = True
174     #text.select = True
175     # If the starting cube is there, remove it
176     if "Cube" in list(bpy.data.objects.keys()):
177         bpy.data.objects.get("Cube").select = True
178     bpy.ops.object.delete()
179     # Smooth specified shapes
180     for shape in shapes_to_smooth:
181         shape.select = True
182     #bpy.context.scene.objects.active = shapes_to_smooth[0]
183     #bpy.ops.object.shade_smooth()
184     # Join shapes
185     for shape in shapes:
186         shape.select = True
187     #bpy.context.scene.objects.active = shapes[0]
188     #bpy.ops.object.join()
189     # Center object origin to geometry
190     bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="MEDIAN")
191     # Refresh scene
192     bpy.context.scene.update()
193
194 # If main, load json and run
195 if __name__ == "__main__":
196     with open(sys.argv[3]) as network_file:
197         network = json.load(network_file)
198     draw_network(network)