Blender: python script to make UV spheres on curves’ locations

This is my first Blender script.

Motivation:

I assume svg is a bridge between data visualizations on the web and Blender. On the browser, D3.js makes SVG as a chart element. You can take SVG by SVG Crowbar. And Blender import SVG as curves.
After that,  how to deal with curves took from SVG in Blender is my challenge.

Curves have a bunch of features. But I want to use curves as location flag. To be more concrete, I want to make objects where curves are, so that I can re-create original svg chart by 3D objects.

The code below implements it.

 

code:

def addUVsphereOnCurves(number,scale=0.1):
    import bpy
    import mathutils
    from mathutils import Vector

    objectList = []
    curveList = []
    lengthList = []
    locationList = []

    #---------Function to get length of curves------------
    # Usage: After select one curve to be active, get_length(bpy.context).
    def get_length(context):

        obj_name_original = context.active_object.name
        bpy.ops.object.duplicate_move()
        # the duplicate is active, apply all transforms to get global coordinates
        bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
        # convert to mesh
        bpy.ops.object.convert(target='MESH', keep_original=False)
        _data = context.active_object.data
        edge_length = 0
        for edge in _data.edges:
            vert0 = _data.vertices[edge.vertices[0]].co
            vert1 = _data.vertices[edge.vertices[1]].co
            edge_length += (vert0-vert1).length
        # deal with trailing float smear
        edge_length = '{:.6f}'.format(edge_length)
        #print(edge_length)
        # stick into clipboard
        context.window_manager.clipboard = edge_length
        bpy.ops.object.delete()
        context.scene.objects.active = context.scene.objects[obj_name_original]
        context.object.select = True
        #Added return to get value.
        return edge_length


    #---------Make object/location/length lists of curves-------------
    # Object list of curves.
    def makeObjectList(number):
        for i in range(number):
            if i < 1:
                objectList.append(bpy.data.objects["Curve"])
            elif 1 <= i < 10:
                objectList.append(bpy.data.objects["Curve.00" + str(i)])
            else:
                objectList.append(bpy.data.objects["Curve.0" + str(i)])

    # Make curve list because bpy.data.objects can't switch 2D to 3D. bpy.data.curves can do.
    def makeCurveList():
        for i in objectList:
            curveList.append(bpy.data.curves[i.name])

    #Switch 3D from 2D of curve not to get error in get_length.
    def switch2dTo3d():
        for i in curveList:
            i.dimensions = '3D'

    # In for loop of objectList. Firstly deselect all object. Activate one object. Select this object. Gettin length of curve, deselect it.
    def executeGetLength():
        bpy.ops.object.select_all(action='DESELECT')
        for i in objectList:
            bpy.context.scene.objects.active = i
            i.select = True
            lengthList.append(get_length(bpy.context))
            i.select = False
     
    # Return from 3D to 2D.
    def switch3dTo2d():
        for i in curveList:
            i.dimensions = '2D'
     
    # Make locationList of curve from objectList.
    def makeLocationList():
        for i in objectList:
            locationList.append(i.location)
     
     
    #--------Add objects where curves are----------
    # Add UVsphere along with locationList. size come from lengthList.
    def AddUVsphere(scale):
        for i,d in enumerate(locationList):
            # divide size by 10 for too large if default.
            bpy.ops.mesh.primitive_uv_sphere_add(location=d, size=float(lengthList[i])*scale)
        
    # Execution function
    makeObjectList(number)
    makeCurveList()
    switch2dTo3d()
    executeGetLength()
    switch3dTo2d()
    makeLocationList()
    AddUVsphere(scale)


# Number is curve's number in order like Curve ~ Curve.065. Scale is scale of UVsphere. 
#addUVsphereOnCurves(number=65,scale=0.1)

get_length(context) to get length of a curve came from Blender Python: Determine length of Curve in Blender

Result:

The original SVG bubble chart is like below.

svg_bubble_for_curve

The result of addUVsphereOnCurves() on the imported curves is below. It looks like a legion of UFOs.
The plane are the curves. The UV spheres are newly-made objects according to the curves’ locations and lengths.

addUVsphere_on_curves2_fit_curvesize1

 

The result of the same code with Japanese prefecture map is below.

The location is accurate, but size of sphere is sometimes not accurate.Since addUVsphereOnCurves() calculate length of a curve not area, perplexed coast lines seems to make sphere bigger than the area.

It’s a drawback of the snippet.

UVsphre_on_japan2_material

 

Usage:

  • First, make curves manually or import SVG so that blender automatically takes as curves. And make sure that the curves’ names are in order like “Curve” ,”Curve.001″, “Curve.002″~.
  • In scripting mode that you can select on top tab to change from default to scripting, click “new” to create text editor. And paste the snippet code on it.
  • Uncomment the last function of the code, and set argument for number of the curve and UV sphere’s scale. if you have “Curve” ~ “Curve.065”, number should be 66.
  • And right click to choose “run script”.

make_uvsphere_on_curve_screenshot

 

Limitation:

  • The function “addUVsphereOnCurves()” calculates length of curve, the returned value is not area. That is, if a curve forms fold inner or outer like coast lines, the correspondent UV sphere is bigger than you expect.
  • addUVsphereOnCurves() only takes curves named default and in default order such as “Curve” ~ “Curve.001” ~ “Curve.031”. I want to refine the code to select target curves. But the code is my first Blender script, so I don’t know how to deal with selector for now.

Leave a Reply