String It - Fun with Curve Scripting

I’ve been playing with curves and I ended up coming up with this. Basically, you pick 2 or more objects in a scene, hit the “String It” button in the tools panel and it will generate a curve that goes exactly through each objects center. You can switch between bezier and poly curve types too.

I was thinking Christmas lights and popcorn necklaces. Things like that. At any rate, here it is…


# string_it.py (c) 2011 Phil Cote (cotejrp1)
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****

import bpy

bl_info = {
    'name': 'String It',
    'author': 'Phil Cote, cotejrp1, (http://www.blenderpythontutorials.com)',
    'version': (0,1),
    "blender": (2, 5, 8),
    "api": 37702,
    'location': '',
    'description': 'Run a curve through each selected object in a scene.',
    'warning': '', # used for warning icon and text in addons panel
    'category': 'Add Curve'}


def makeBezier( spline, vertList ):
    numPoints = ( len( vertList ) / 3 ) - 1
    spline.bezier_points.add( numPoints )
    spline.bezier_points.foreach_set( "co", vertList )
    for point in spline.bezier_points:
        point.handle_left_type = "AUTO"
        point.handle_right_type = "AUTO"
    
def makePoly( spline, vertList ):
    numPoints = ( len( vertList ) / 4 ) - 1
    spline.points.add( numPoints )
    spline.points.foreach_set( "co", vertList )
    

class StringItOperator(bpy.types.Operator):
    '''Creates a curve that runs through the centers of each selected object.'''
    bl_idname = "curve.string_it_operator"
    bl_label = "String It"
    bl_options = { "REGISTER", "UNDO" }
    
    splineOptionList = [  ( 'poly', 'poly', 'poly' ), ( 'bezier', 'bezier', 'bezier' ), ]
    splineChoice = bpy.props.EnumProperty( name="Spline Type", items=splineOptionList )
        
    @classmethod   
    def poll( self, context ):
        totalSelected = len( [ob for ob in context.selectable_objects if ob.select] )
        return totalSelected > 1
    
    def execute(self, context):
        
        splineType = self.splineChoice.upper()
        scn = context.scene
        obList = [ ob for ob in scn.objects if ob.select ]

        # build the vert data set to use to make the curve
        vertList = []
        for sceneOb in obList:
            vertList.append( sceneOb.location.x )
            vertList.append( sceneOb.location.y )
            vertList.append( sceneOb.location.z )
            if splineType == 'POLY':
                vertList.append( 0 )

        # build the curve itself.
        crv = bpy.data.curves.new( "curve", type = "CURVE" )
        crv.splines.new( type = splineType )
        spline = crv.splines[0]
        if splineType == 'BEZIER':
            makeBezier( spline, vertList )
                
        else: #polyline case.
            makePoly( spline, vertList )
            
        # add the curve to the scene.
        crvOb = bpy.data.objects.new( "curveOb", crv )
        scn.objects.link( crvOb )            
        return {'FINISHED'}


class StringItPanel( bpy.types.Panel ):
    bl_label = "String It"
    bl_region_type = "TOOLS"
    bl_space_type = "VIEW_3D"
    
    def draw( self, context ):
        self.layout.row().operator( "curve.string_it_operator" )
    
    
def register():
    bpy.utils.register_class(StringItOperator)
    bpy.utils.register_class(StringItPanel)


def unregister():
    bpy.utils.unregister_class(StringItOperator)
    bpy.utils.unregister_class(StringItPanel)


if __name__ == "__main__":
    register()

this is cool! I just tried this with using empties, it works. really neat.

you could expand on the script and give subdivision levels to the curves.

Great, I’m loving it, creating tunnels!

it would also be cool if you expand on this script and made
an option for curves “sticky to empty object” or “Use Empty Objects as String Control Operators”,
so when you move the “empty objects” at each end of the string the curve moves freeform with it,
this is why sub-divisions levels on the curve would be nice to have as well.

http://i53.tinypic.com/1556ssh.gif

However, if someone really wanted to get swift they would come up with a new type of curve for Blender,
Like the type of “control point curves” that Rhino3D uses. or even, the b-splines that Cinema4D has
(well C4D lots of different types of splines, but… i like the b-spline)

hi, the two toolshelfs are a good thing.
this method of ui works well.
one is the execute button, which is constant in the ui.
the other is the operator panel which is not constant.

its wastes space and is confusing having them separated.
All consolidated into one shelf tool similar to how “LoopTools” is done is the proper way

holyenigma74,
quite a few scripts use this method & there’s good reasons for it.
accessability is one. if I want to execute this multiple times, I can easily.
The scripts settings for the curve object should not be visable until the curve is created.
You can execute the script then turn off the addon to remove it from the menu for no clutter.

cotejrp1,
hi, a button for bevel “tube” settings would be cool so thickness could be added.

Well, that was a lot more response thatn I expected. Thanks for the kind words and suggestions! :slight_smile:

I kind of get both sides of the discussion between HolyEnigma and Meta-Androcto. I think the real issue can be resolved by some simple caption fixes. The captions as they were before did look a little redundant.

By the way, I threw in a curve closing option into the tool. Here’s the code as it stands now.


# string_it.py (c) 2011 Phil Cote (cotejrp1)
#
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****

import bpy

bl_info = {
    'name': 'String It',
    'author': 'Phil Cote, cotejrp1, (http://www.blenderpythontutorials.com)',
    'version': (0,1),
    "blender": (2, 5, 8),
    "api": 37702,
    'location': '',
    'description': 'Run a curve through each selected object in a scene.',
    'warning': '', # used for warning icon and text in addons panel
    'category': 'Add Curve'}


def makeBezier( spline, vertList ):
    numPoints = ( len( vertList ) / 3 ) - 1
    spline.bezier_points.add( numPoints )
    spline.bezier_points.foreach_set( "co", vertList )
    for point in spline.bezier_points:
        point.handle_left_type = "AUTO"
        point.handle_right_type = "AUTO"
    
def makePoly( spline, vertList ):
    numPoints = ( len( vertList ) / 4 ) - 1
    spline.points.add( numPoints )
    spline.points.foreach_set( "co", vertList )
    

class StringItOperator(bpy.types.Operator):
    '''Creates a curve that runs through the centers of each selected object.'''
    bl_idname = "curve.string_it_operator"
    bl_label = "String It Options"
    bl_options = { "REGISTER", "UNDO" }
    
    splineOptionList = [  ( 'poly', 'poly', 'poly' ), ( 'bezier', 'bezier', 'bezier' ), ]
    splineChoice = bpy.props.EnumProperty( name="Spline Type", items=splineOptionList )
    closeSpline = bpy.props.BoolProperty( name="Close Spline?", default=False )
        
    @classmethod   
    def poll( self, context ):
        totalSelected = len( [ob for ob in context.selectable_objects if ob.select] )
        return totalSelected > 1
    
    def execute(self, context):
        
        splineType = self.splineChoice.upper()
        scn = context.scene
        obList = [ ob for ob in scn.objects if ob.select ]

        # build the vert data set to use to make the curve
        vertList = []
        for sceneOb in obList:
            vertList.append( sceneOb.location.x )
            vertList.append( sceneOb.location.y )
            vertList.append( sceneOb.location.z )
            if splineType == 'POLY':
                vertList.append( 0 )

        # build the curve itself.
        crv = bpy.data.curves.new( "curve", type = "CURVE" )
        crv.splines.new( type = splineType )
        spline = crv.splines[0]
        if splineType == 'BEZIER':
            makeBezier( spline, vertList )
                
        else: #polyline case.
            makePoly( spline, vertList )
        
        spline.use_cyclic_u = self.closeSpline
        
        # add the curve to the scene.
        crvOb = bpy.data.objects.new( "curveOb", crv )
        scn.objects.link( crvOb )            
        return {'FINISHED'}


class StringItPanel( bpy.types.Panel ):
    bl_label = "String It"
    bl_region_type = "TOOLS"
    bl_space_type = "VIEW_3D"
    
    def draw( self, context ):
        self.layout.row().operator( "curve.string_it_operator", text="Make Curve" )
    
    
def register():
    bpy.utils.register_class(StringItOperator)
    bpy.utils.register_class(StringItPanel)


def unregister():
    bpy.utils.unregister_class(StringItOperator)
    bpy.utils.unregister_class(StringItPanel)


if __name__ == "__main__":
    register()