Blender: Python snippet to duplicate object on curves for data visualization

I made the snippet that duplicates an object on curves. Also, the scale of duplicated objects reflect on the curve’s length.

I will make add-on version of the snippet with other functionalities, such as changing origins of duplicated objects and changing position or rotation complying with curve’s length.

dupli-on-curve1_result1_takoyaki2_vertical1_combine2

 

The purpose of the snippet is to make 3D version of data visualization.
You can download svg elements on browser by the Chrome bookmarklet, SVG Crowbar. It means that you can take D3-generated svg data visualization in many data-viz websites and import the svg into Blender as curve.

The resulted curve can be regarded as a template of the position and data.
The snippet can put any object on the curve’s position and change its scale as the curve’s length, so that you  can replicate 3D version of the data visualization in Blender.

 

Usage:

dupli-on-curves_usage1

  1. Select curves and lastly select an object as active object.
  2. Go to scripting screen and make new text in text editor window. Then paste the snippet.
  3. As option, you can change the arguments of the function at the end of snippet, “dupliOnCurve(curve_length_effect=0.5, replace=False)”.
    curve_length_effect is how curve length effects scale of duplicated objects.
    replace=False remains original curves, replace=True delete original curves.
  4. Run the script by right click > “Run Script”.
    The lastly selected object will be duplicated on the curves.

 

The snippet:

I also put the code on Gist: blender_dupl-on-curves1.py.

import bpy

def dupliOnCurve(curve_length_effect=None, replace=None):
    '''
    When you want to duplicate an object on object: Simply do "object > make links > object data".
    The function is optimised for an object to duplicate on curves, althoubh it can be used to duplicate on objects.
    
    Usage:
    1. Multiple selecte curves, and lastly select an object as an object to be duplicated.
    2. Paste the code on text editor which can be found in scripting layout.
    Arguments:  
        curve_length_rate:
            How intense the length of curve affect the scale of duplicated object. 0 is no effect.
            Default is 0.
        replace:
             If True, the selected curves will be removed, and only the duplicated objects reimans.
             If False, the selected curves won't be removed.
    '''
    
    # Default value of curve_length_rate is 0.
    if curve_length_effect == None:
        curve_length_effect = 0
    else:
        curve_length_effect = curve_length_effect
    # Default value of replace is False.
    if replace == True:
        replace == True
    else:
        replace = False
    
    # Name of lastly selected active object to be duplicated.
    active_object_name = bpy.context.scene.objects.active.name
    # Name list of selected curves/objects, except for lastly selected active object.
    selected_object_names = [obj.name for obj in bpy.context.selected_objects if obj.name != active_object_name]
    # List of edge lengths for curves/objects.
    edge_length_list = []
    
    # Convert the selected curves into objects, and add the edge lenght to edge_length_list.
    for selected_object_name in selected_object_names:
        # Asign each selected object.
        selected_object = bpy.data.objects[selected_object_name]

        # If replace == False, the original curve/object is duplicated.
        # (it's not duplication of selected object on curves, just for keeping original curves).
        if replace == False:
            _makeDupli(selected_object)
        # If it's curve, convert the curve into object.
        if selected_object.type == "CURVE":
            _curveToMesh(selected_object)
        
        # calculate the lenght of edge, then append it to edge_length_list.
        if curve_length_effect != 0:
            edge_length = _getLength(selected_object)
            edge_length_list.append(edge_length)
    
    # Calculate average edge length.
    avarage_edge_length = sum(edge_length_list)/len(edge_length_list)
    
    # Re-select and change their scale relfecting edge length.
    bpy.ops.object.select_all(action='DESELECT')
    for i, selected_object_name in enumerate(selected_object_names):

        selected_object = bpy.data.objects[selected_object_name]
        selected_object.select = True

        # Effect for scale = curve_length_effect * (its edge length - average edge lenght) - average edge length.
        try:
            scale_rate = curve_length_effect * (edge_length_list[i] - avarage_edge_length) / avarage_edge_length
        # When avarage_edge_length is 0, set scale_rate=0.
        except ZeroDivisionError:
            scale_rate = 0

        # Change scaled of selected object into the same as scale of duplicated object + scale_rate.
        selected_object.scale.x = bpy.data.objects[active_object_name].scale.x + scale_rate
        selected_object.scale.y = bpy.data.objects[active_object_name].scale.y + scale_rate
        selected_object.scale.z = bpy.data.objects[active_object_name].scale.z + scale_rate
        
    # Re-activate the object which was selected as active object at first.
    bpy.context.scene.objects.active = bpy.data.objects[active_object_name]
    
    # Duplicate active object on the curves by linking the data to them.
    bpy.ops.object.make_links_data(type='OBDATA')

# Duplicate curves to keep original curves.
def _makeDupli(selected_object):
    bpy.ops.object.select_all(action='DESELECT')
    selected_object.select = True
    # Make duplication.
    bpy.ops.object.duplicate_move()
    bpy.ops.object.select_all(action='DESELECT')

# Convert curve into mesh.
def _curveToMesh(selected_object):

    # Select and active only the curve per loop.
    bpy.ops.object.select_all(action='DESELECT')
    selected_object.select = True
    bpy.context.scene.objects.active = selected_object
    # Convert curve into mesh.
    bpy.ops.object.convert(target='MESH', keep_original=False)
    
    bpy.ops.object.select_all(action='DESELECT')

# Calculate edge length of a curve.
def _getLength(selected_object):

    bpy.ops.object.select_all(action='DESELECT')
    selected_object.select = True
    bpy.context.scene.objects.active = selected_object
    
    # Temporary duplicate it.
    bpy.ops.object.duplicate_move()
    
    try:
        # Change origin to center to calculate the lenght easily.
        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

        # Calculate the edge length.
        mesh_data = bpy.context.active_object.data
        edge_length = 0
        for edge in mesh_data.edges:
            vert0 = mesh_data.vertices[edge.vertices[0]].co
            vert1 = mesh_data.vertices[edge.vertices[1]].co
            edge_length += (vert0-vert1).length
    # When the length cannot be calculated, set 0.
    except:
        edge_length = 0

    # Delete temporal duplicated object.
    bpy.ops.object.delete()
    bpy.ops.object.select_all(action='DESELECT')

    return edge_length

# Execute.
dupliOnCurve(curve_length_effect=0.5, replace=False)

Leave a Reply