From the provided data file, you can obtain this kind of rendering, with (for example) either creating splines (curves) or creating a mesh with skin modifier.


The script allows to:
Load the data from the indicated site
Convert it to inner objects (NeuronPoint class below)
Optionally add some random intermediate points between the loaded coordinates
And
Either convert them to curve, creating a new curve for each data ramification
Or create a mesh from this point and add a skin modifier in order to obtain the neuron branches
Here is the commented code and blend file:
import bpy
import random
from mathutils import Vector
# Store the information from the SWC text file
class NeuronPoint:
def __init__(self, id, type, x, y, z, radius, parent_id):
self.id = id
self.type = type
self.x = x
self.y = y
self.z = z
self.radius = radius
self.parent_id = parent_id
self.parent = None
self.children = []
@property
def coordinates(self):
return Vector( (self.x, self.y, self.z) )
# Read data from the file
def read_neuron_points( file_name ):
file = open(file_name, "r")
points = {}
for line in [l for l in file if l and l[0] != '#']:
string_data = line.split()
if string_data:
id = int(string_data[0]) - 1
neuron_type = int(string_data[1])
x = float(string_data[2])
y = float(string_data[3])
z = float(string_data[4])
radius = float(string_data[5])
parent_id = int(string_data[6]) - 1
neuron_point = NeuronPoint(id, neuron_type, x, y, z, radius, parent_id)
points[id] = neuron_point
for point in points.values():
point.parent = points.get( point.parent_id )
if point.parent:
point.parent.children.append( point )
return [p for p in points.values()]
def random_vector( random_amount ):
return Vector( (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1)) ) * random_amount
# Cuts the data to have additional random points
def make_intermediate_points( points, point, min_distance, random_amount ):
point_co = point.coordinates
parent = point.parent
parent_co = parent.coordinates
vector = point_co - parent_co
distance = vector.length
# Do it only if distance is above the parameter
if distance > min_distance:
# Cut in half + random
intermediate_co = parent_co + (vector * 0.5) + random_vector(random_amount)
intermediate_radius = (parent.radius + point.radius) * 0.5
# Insert the new point in the hierarchy
new_point = NeuronPoint( len(points), point.type, *intermediate_co, intermediate_radius, parent.id )
points.append( new_point )
point.parent = new_point
point.parent_id = new_point.id
parent.children.remove( point )
parent.children.append( new_point )
new_point.parent = parent
new_point.children.append( point )
# Cut around this new point
make_intermediate_points( points, point, min_distance, random_amount )
make_intermediate_points( points, new_point, min_distance, random_amount )
# Cuts the data to have additional random points
def subdivide_points( points, min_distance, random_amount ):
for point in [p for p in points if p.parent]:
make_intermediate_points( points, point, min_distance, random_amount )
return points
def mesh_from_neuron_points( context, name, points, scale_factor = 0.01, radius_factor = 1 ):
# Gets vertices and edges from the points data
verts = [p.coordinates * scale_factor for p in points]
edges = [(p.id, p.parent_id) for p in points if p.parent]
# Create a mesh
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(verts, edges, [])
# Create an object with this mesh
obj = bpy.data.objects.new(name, mesh)
# Add a subdivision surface (smooth the edges)
subdivision = obj.modifiers.new( "subdivision", 'SUBSURF' )
subdivision.render_levels = 1
subdivision.levels = 1
# Add a skin modifier in order to have thickness
skin = obj.modifiers.new( "skin", 'SKIN' )
skin.branch_smoothing = 0.5
skin.use_smooth_shade = True
# Smooth again with another subdivision
subdivision = obj.modifiers.new( "subdivision", 'SUBSURF' )
# Associates the radius to each skin vertex
for s, p in zip( obj.data.skin_vertices[''].data, points ):
radius = p.radius * radius_factor
if not p.children:
radius *= 0.1
s.radius = (radius, radius)
# Link the object to the scene
context.scene.collection.objects.link(obj)
return obj
# Cuts the point set into continuous spline parts, creating a new part for each ramification
def splines_points_from_points( points ):
result = []
base_points = [p for p in points if not p.children]
while base_points:
next_points = []
for point in base_points:
current = [point]
result.append(current)
while point.parent:
point = point.parent
current.append(point)
if len(point.children) > 1:
next_points.append( point )
break
base_points = next_points
for spline in result:
spline.reverse()
return result
def curve_from_neuron_points( context, name, points, scale_factor = 0.01, radius_factor = 1 ):
# Create a curve with some bevel depth
curve = bpy.data.curves.new(name=name, type='CURVE')
curve.dimensions = '3D'
curve.bevel_depth = 1
# Create an object with it
obj = bpy.data.objects.new(name, curve)
# Calculate splines parts
splines_points = splines_points_from_points( points )
for spline_points in splines_points:
# Create a spline for each part
bezier_curve = curve.splines.new('BEZIER')
bezier_curve.bezier_points.add(len(spline_points)-1)
# Set the points
for bezier, point in zip(bezier_curve.bezier_points, spline_points):
bezier.co = point.coordinates * scale_factor
bezier.radius = point.radius * radius_factor
if not point.children:
bezier.radius *= 0.01
# Link object to the scene
context.scene.collection.objects.link(obj)
# Toggle handle type (faster than doing it point by point)
obj.select_set( True )
context.view_layer.objects.active = obj
bpy.ops.object.editmode_toggle()
bpy.ops.curve.select_all(action='SELECT')
bpy.ops.curve.handle_type_set(type='AUTOMATIC')
bpy.ops.object.editmode_toggle()
return obj
# Get the file name relative to this blend file
file_name = bpy.path.abspath("//test.txt")
# Read the file into points
points = read_neuron_points( file_name )
# Some parameters to handle result scale
scale_factor = 0.1
radius_factor = 0.1
# Subdivide the model with some random (optional)
points = subdivide_points( points, min_distance = 4, random_amount = 1 )
# Create the object as mesh with skin modifier
#obj = mesh_from_neuron_points( bpy.context, "test", points, scale_factor = scale_factor, radius_factor = radius_factor )
# Create the object as curve
obj = curve_from_neuron_points( bpy.context, "test", points, scale_factor = scale_factor, radius_factor = radius_factor )
# Set a material on it, if defined
mat = bpy.data.materials.get( "Material" )
if mat:
obj.data.materials.append(mat)
