major update to curve tools.
authorBrendon Murphy <meta.androcto1@gmail.com>
Tue, 8 Jul 2014 15:19:31 +0000 (01:19 +1000)
committerBrendon Murphy <meta.androcto1@gmail.com>
Tue, 8 Jul 2014 15:19:31 +0000 (01:19 +1000)
code rewrite thanks to guy lateur.

curve_tools.py [deleted file]
curve_tools/CurveIntersections.py [new file with mode: 0644]
curve_tools/Curves.py [new file with mode: 0644]
curve_tools/Math.py [new file with mode: 0644]
curve_tools/Operators.py [new file with mode: 0644]
curve_tools/Panel.py [new file with mode: 0644]
curve_tools/Properties.py [new file with mode: 0644]
curve_tools/Surfaces.py [new file with mode: 0644]
curve_tools/Util.py [new file with mode: 0644]
curve_tools/__init__.py [new file with mode: 0644]

diff --git a/curve_tools.py b/curve_tools.py
deleted file mode 100644 (file)
index a418877..0000000
+++ /dev/null
@@ -1,1352 +0,0 @@
-# #####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 LICENSE BLOCK #####
-
-bl_info = {
-    "name": "Curve Tools",
-    "author": "Zak",
-    "version": (0, 1, 5),
-    "blender": (2, 59, 0),
-    "location": "Properties > Object data",
-    "description": "Creates driven Lofts or Birails between curves",
-    "warning": "may be buggy or incomplete",
-    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
-        "Scripts/Curve/Curve_Tools",
-    "tracker_url": "https://developer.blender.org/T27720",
-    "category": "Add Curve"}
-
-### UPDATES
-#1.5
-
-#-Fixed birail function
-#-Added Curve Snap to W key specials menu.
-#-Removed some functions that arent needed and wrapped into the operators.
-#-nurbs with weights for loft and birail
-#-Panel Moved to view 3d tools
-#-inserted TODO comments
-#-tried to implement live tension and bias for Hermite interpolation by driving the mesh but
-#i dont know why, the code is executed all the time even if you dont change the variables.
-#-snap to curves affects all the curves on the scene
-#-i was able to preserve handle types when split or subdivide
-
-
-#1.4
-#-incorporate curve snap
-#assign a copy transform to helper
-#-nurbs implemented (in progress)
-
-import bpy
-from mathutils import *
-from bpy.props import *
-
-print("----------")
-
-
-### PROPERTIES
-class sprops(bpy.types.PropertyGroup):
-    pass
-
-
-bpy.utils.register_class(sprops)
-
-#bpy.selection will store objects names in the order they were selected
-bpy.selection=[]
-
-
-#dodriver a simple checker to chosse whether  you want a driven mesh or not.
-bpy.types.Scene.dodriver = BoolProperty(name = "dodriver",                                      default=False)
-
-#interpolation types
-myitems = (('0','Linear', ''),('1','Cubic',''),('2','Catmull',''), ('3','Hermite',''))
-bpy.types.Scene.intype = EnumProperty(name="intype", items = myitems, default='3')
-
-#number of steps and spans to be created
-bpy.types.Scene.steps = IntProperty(name="steps", default=12, min=2)
-bpy.types.Scene.spans = IntProperty(name="spans", default=12, min=2)
-
-#parameters for Hermite interpolation
-bpy.types.Scene.tension = FloatProperty(name = "tension", min=0.0, default=0.0)
-bpy.types.Scene.bias = FloatProperty(name = "bias", min=0.0, default = 0.5)
-
-#proportional birail
-bpy.types.Scene.proportional = BoolProperty(name="proportional", default=False)
-
-#this stores the result of calculating the curve length
-bpy.types.Scene.clen = FloatProperty(name="clen", default=0.0, precision=5)
-
-#minimun distance for merge curve tool
-bpy.types.Scene.limit = FloatProperty(name="limit", default=0.1, precision=3)
-
-
-### SELECT BY ORDER BLOCK
-
-#i dont know what to do with this. Im not using it yet.
-def selected_points(curve):
-
-    selp = []
-    for spl in curve.splines:
-        if spl.type=="BEZIER":
-            points = spl.bezier_points
-            for p in points:
-                if p.select_control_point:
-                    selp.append(p)
-
-        elif spl.type=="NURBS":
-            points = spl.points
-            for p in points:
-                if p.select:
-                    selp.append(p)
-    return selp
-
-#writes bpy.selection when a new object is selected or deselected
-#it compares bpy.selection with bpy.context.selected_objects
-
-def select():
-
-    #print(bpy.context.mode)
-    if bpy.context.mode=="OBJECT":
-        obj = bpy.context.object
-        sel = len(bpy.context.selected_objects)
-
-        if sel==0:
-            bpy.selection=[]
-        else:
-            if sel==1:
-                bpy.selection=[]
-                bpy.selection.append(obj)
-            elif sel>len(bpy.selection):
-                for sobj in bpy.context.selected_objects:
-                    if (sobj in bpy.selection)==False:
-                        bpy.selection.append(sobj)
-
-            elif sel<len(bpy.selection):
-                for it in bpy.selection:
-                    if (it in bpy.context.selected_objects)==False:
-                        bpy.selection.remove(it)
-
-    #on edit mode doesnt work well
-
-
-#executes selection by order at 3d view
-class Selection(bpy.types.Header):
-    bl_label = "Selection"
-    bl_space_type = "VIEW_3D"
-
-    def __init__(self):
-        #print("hey")
-        select()
-
-    def draw(self, context):
-        layout = self.layout
-        row = layout.row()
-        row.label("Sel: "+str(len(bpy.selection)))
-
-### GENERAL CURVE FUNCTIONS
-
-#distance between 2 points
-def dist(p1, p2):
-    return (p2-p1).magnitude
-
-#sets cursors position for debugging porpuses
-def cursor(pos):
-    bpy.context.scene.cursor_location = pos
-
-#cuadratic bezier value
-def quad(p, t):
-    return p[0]*(1.0-t)**2.0 + 2.0*t*p[1]*(1.0-t) + p[2]*t**2.0
-
-#cubic bezier value
-def cubic(p, t):
-    return p[0]*(1.0-t)**3.0 + 3.0*p[1]*t*(1.0-t)**2.0 + 3.0*p[2]*(t**2.0)*(1.0-t) + p[3]*t**3.0
-
-#gets a bezier segment's control points on global coordinates
-def getbezpoints(spl, mt, seg=0):
-    points = spl.bezier_points
-    p0 = mt * points[seg].co
-    p1 = mt * points[seg].handle_right
-    p2 = mt * points[seg+1].handle_left
-    p3 = mt * points[seg+1].co
-    return p0, p1, p2, p3
-
-#gets nurbs polygon control points on global coordinates
-def getnurbspoints(spl, mw):
-    pts = []
-    ws = []
-    for p in spl.points:
-        v = Vector(p.co[0:3])*mw
-        pts.append(v)
-        ws.append(p.weight)
-    return pts , ws
-
-#calcs a nurbs knot vector
-def knots(n, order, type=0):#0 uniform 1 endpoints 2 bezier
-
-    kv = []
-
-    t = n+order
-    if type==0:
-        for i in range(0, t):
-            kv.append(1.0*i)
-
-    elif type==1:
-        k=0.0
-        for i in range(1, t+1):
-            kv.append(k)
-            if i>=order and i<=n:
-                k+=1.0
-    elif type==2:
-        if order==4:
-            k=0.34
-            for a in range(0,t):
-                if a>=order and a<=n: k+=0.5
-                kv.append(floor(k))
-                k+=1.0/3.0
-
-        elif order==3:
-            k=0.6
-            for a in range(0, t):
-                if a >=order and a<=n: k+=0.5
-                kv.append(floor(k))
-
-    ##normalize the knot vector
-    for i in range(0, len(kv)):
-        kv[i]=kv[i]/kv[-1]
-
-    return kv
-
-#nurbs curve evaluation
-def C(t, order, points, weights, knots):
-    #c = Point([0,0,0])
-    c = Vector()
-    rational = 0
-    i = 0
-    while i < len(points):
-        b = B(i, order, t, knots)
-        p = points[i] * (b * weights[i])
-        c = c + p
-        rational = rational + b*weights[i]
-        i = i + 1
-
-    return c * (1.0/rational)
-
-#nurbs basis function
-def B(i,k,t,knots):
-    ret = 0
-    if k>0:
-        n1 = (t-knots[i])*B(i,k-1,t,knots)
-        d1 = knots[i+k] - knots[i]
-        n2 = (knots[i+k+1] - t) * B(i+1,k-1,t,knots)
-        d2 = knots[i+k+1] - knots[i+1]
-        if d1 > 0.0001 or d1 < -0.0001:
-            a = n1 / d1
-        else:
-            a = 0
-        if d2 > 0.0001 or d2 < -0.0001:
-            b = n2 / d2
-        else:
-            b = 0
-        ret = a + b
-        #print "B i = %d, k = %d, ret = %g, a = %g, b = %g\n"%(i,k,ret,a,b)
-    else:
-        if knots[i] <= t and t <= knots[i+1]:
-            ret = 1
-        else:
-            ret = 0
-    return ret
-
-#calculates a global parameter t along all control points
-#t=0 begining of the curve
-#t=1 ending of the curve
-
-def calct(obj, t):
-
-    spl=None
-    mw = obj.matrix_world
-    if obj.data.splines.active==None:
-        if len(obj.data.splines)>0:
-            spl=obj.data.splines[0]
-    else:
-        spl = obj.data.splines.active
-
-    if spl==None:
-        return False
-
-    if spl.type=="BEZIER":
-        points = spl.bezier_points
-        nsegs = len(points)-1
-
-        d = 1.0/nsegs
-        seg = int(t/d)
-        t1 = t/d - int(t/d)
-
-        if t==1:
-            seg-=1
-            t1 = 1.0
-
-        p = getbezpoints(spl,mw, seg)
-
-        coord = cubic(p, t1)
-
-        return coord
-
-    elif spl.type=="NURBS":
-        data = getnurbspoints(spl, mw)
-        pts = data[0]
-        ws = data[1]
-        order = spl.order_u
-        n = len(pts)
-        ctype = spl.use_endpoint_u
-        kv = knots(n, order, ctype)
-
-        coord = C(t, order-1, pts, ws, kv)
-
-        return coord
-
-#length of the curve
-def arclength(objs):
-    length = 0.0
-
-    for obj in objs:
-        if obj.type=="CURVE":
-            prec = 1000 #precision
-            inc = 1/prec #increments
-
-            ### TODO: set a custom precision value depending the number of curve points
-            #that way it can gain on accuracy in less operations.
-
-            #subdivide the curve in 1000 lines and sum its magnitudes
-            for i in range(0, prec):
-                ti = i*inc
-                tf = (i+1)*inc
-                a = calct(obj, ti)
-                b = calct(obj, tf)
-                r = (b-a).magnitude
-                length+=r
-
-    return length
-
-
-class ArcLengthOperator(bpy.types.Operator):
-
-    bl_idname = "curve.arc_length_operator"
-    bl_label = "Measures the length of a curve"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-        objs = context.selected_objects
-        context.scene.clen = arclength(objs)
-        return {'FINISHED'}
-
-### LOFT INTERPOLATIONS
-
-#objs = selected objects
-#i = object index
-#t = parameter along u direction
-#tr = parameter along v direction
-
-#linear
-def intl(objs, i, t, tr):
-    p1 = calct(objs[i],t)
-    p2 = calct(objs[i+1], t)
-
-    r = p1 + (p2 - p1)*tr
-
-    return r
-
-#tipo = interpolation type
-#tension and bias are for hermite interpolation
-#they can be changed to obtain different lofts.
-
-#cubic
-def intc(objs, i, t, tr, tipo=3, tension=0.0, bias=0.0):
-
-    ncurves =len(objs)
-
-    #if 2 curves go to linear interpolation regardless the one you choose
-    if ncurves<3:
-        return intl(objs, i, t, tr)
-    else:
-
-        #calculates the points to be interpolated on each curve
-        if i==0:
-            p0 = calct(objs[i], t)
-            p1 = p0
-            p2 = calct(objs[i+1], t)
-            p3 = calct(objs[i+2], t)
-        else:
-            if ncurves-2 == i:
-                p0 = calct(objs[i-1], t)
-                p1 = calct(objs[i], t)
-                p2 = calct(objs[i+1], t)
-                p3 = p2
-            else:
-                p0 = calct(objs[i-1], t)
-                p1 = calct(objs[i], t)
-                p2 = calct(objs[i+1], t)
-                p3 = calct(objs[i+2], t)
-
-
-    #calculates the interpolation between those points
-    #i used methods from this page: http://paulbourke.net/miscellaneous/interpolation/
-
-    if tipo==0:
-        #linear
-        return intl(objs, i, t, tr)
-    elif tipo == 1:
-        #natural cubic
-        t2 = tr*tr
-        a0 = p3-p2-p0+p1
-        a1 = p0-p1-a0
-        a2 = p2-p0
-        a3 = p1
-        return a0*tr*t2 + a1*t2+a2*tr+a3
-    elif tipo == 2:
-        #catmull it seems to be working. ill leave it for now.
-        t2 = tr*tr
-        a0 = -0.5*p0 +1.5*p1 -1.5*p2 +0.5*p3
-        a1 = p0 - 2.5*p1 + 2*p2 -0.5*p3
-        a2 = -0.5*p0 + 0.5 *p2
-        a3 = p1
-        return a0*tr*tr + a1*t2+a2*tr+a3
-
-    elif tipo == 3:
-        #hermite
-        tr2 = tr*tr
-        tr3 = tr2*tr
-        m0 = (p1-p0)*(1+bias)*(1-tension)/2
-        m0+= (p2-p1)*(1-bias)*(1-tension)/2
-        m1 = (p2-p1)*(1+bias)*(1-tension)/2
-        m1+= (p3-p2)*(1-bias)*(1-tension)/2
-        a0 = 2*tr3 - 3*tr2 + 1
-        a1 = tr3 - 2 * tr2+ tr
-        a2 = tr3 - tr2
-        a3 = -2*tr3 + 3*tr2
-
-        return a0*p1+a1*m0+a2*m1+a3*p2
-
-
-#handles loft driver expression
-#example: loftdriver('Loft', 'BezierCurve;BezierCurve.001;BezierCurve.002', 3)
-
-#name: its the name of the mesh to be driven
-#objs: the  names of the curves that drives the mesh
-#3 interpolation type
-
-def loftdriver(name, objs, intype):
-    #print("ejecutando "+name)
-    intype = int(intype)
-
-    tension = 0.0
-    bias = 0.5
-    #if the loft object still exists proceed normal
-    try:
-        resobj = bpy.data.objects[name]
-        spans = resobj["spans"]
-        steps = resobj["steps"]
-        if intype==3: #hermite
-            tension = resobj['tension']
-            bias = resobj['bias']
-
-    #if not delete the driver
-    except:
-        curve = bpy.context.object
-        for it in curve.keys():
-            if it == "driver":
-                curve.driver_remove('["driver"]')
-        return False
-
-    objs = objs.split(";")
-    #objs = objs[0:-1]
-
-
-    #retrieves the curves from the objs string
-    for i, l in enumerate(objs):
-        objs[i] = bpy.data.objects[l]
-
-
-
-    #calcs the new vertices coordinates if we change the curves.
-    vxs = loft(objs, steps, spans, intype, tension, bias)
-
-    #apply the new cordinates to the loft object
-    me = resobj.data
-
-    for i in range(0, len(me.vertices)):
-        me.vertices[i].co = vxs[i]
-    me.update()
-    return spans
-
-#NOTES:
-#loftdriver function will fail or produce weird results if:
-#the user changes resobj["spans"] or resobj["steps"]
-#if we delete any vertex from the loft object
-
-### TODO:check if thats the case to remove the drivers
-
-#creates the drivers expressions for each curve
-def createloftdriver(objs, res, intype):
-
-    line = ""
-    for obj in objs:
-        line+=obj.name+";"
-    line=line[0:-1]
-    name = res.name
-
-    interp = str(intype)
-
-    for obj in objs:
-        obj["driver"] = 1.0
-
-        obj.driver_add('["driver"]')
-        obj.animation_data.drivers[0].driver.expression = "loftdriver('"+ name +"', '" + line + "', "+interp+")"
-
-
-    ### creating this driver will execute loft all the time without reason,
-    #and if i cant drive the mesh i cannot implement live tension and bias
-
-#   res['driver'] = 1.0
-#   if res.animation_data==None:
-#       res.animation_data_create()
-#   res.driver_add('["driver"]')
-#   res.animation_data.drivers[0].driver.expression = "loftdriver('"+ name +"', '" + line + "', "+interp+")"
-
-#calculates the vertices position of the loft object
-def loft(objs, steps, spans, interpolation=1, tension=0.0, bias=0.5):
-    verts=[]
-
-    for i in range(0, len(objs)):
-
-        for j in range(0,steps+1):
-            t = 1.0*j/steps
-            verts.append(calct(objs[i], t))
-
-        temp2=[]
-        if i<len(objs)-1:
-            for l in range(1, spans):
-                tr = 1.0*l/spans
-                for k in range(0, steps+1):
-                    t=1.0*k/steps
-                    if interpolation:
-                        pos = intc(objs, i, t, tr, interpolation, tension, bias)
-                    else:
-                        pos = intl(objs,i, t, tr)
-
-                    temp2.append(pos)
-            verts.extend(temp2)
-    return verts
-
-
-#loft operator
-
-class LoftOperator(bpy.types.Operator):
-    """Tooltip"""
-    bl_idname = "mesh.loft_operator"
-    bl_label = "Loft between bezier curves"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-        #retrieves the curves in the order they were selected
-        objs = bpy.selection
-
-        spans = context.scene.spans
-        steps = context.scene.steps
-
-        intype = int(context.scene.intype)
-
-        verts = loft(objs, steps, spans, intype)
-
-        nfaces = steps*spans*(len(objs)-1)
-        faces=[]
-        for i in range(0, nfaces):
-            d = int(i/steps)
-            f = [i+d,i+d+1, i+d+steps+2, i+d+steps+1]
-            #inverts normals
-            #f = [i+d,i+d+steps+1, i+d+steps+2, i+d+1]
-            faces.append(f)
-
-
-        me = bpy.data.meshes.new("Loft")
-        me.from_pydata(verts,[], faces)
-        me.update()
-        newobj = bpy.data.objects.new("Loft", me)
-        #newobj.data = me
-        scn = context.scene
-        scn.objects.link(newobj)
-        scn.objects.active = newobj
-        newobj.select = True
-        bpy.ops.object.shade_smooth()
-
-        #the object stores its own steps and spans
-        #this way the driver will know how to deform the mesh
-        newobj["steps"] = steps
-        newobj["spans"] = spans
-
-        if intype==3:
-            newobj['tension'] = context.scene.tension
-            newobj['bias'] = context.scene.bias
-
-
-        if context.scene.dodriver:
-            createloftdriver(objs, newobj, intype)
-
-        return {'FINISHED'}
-
-class UpdateFix(bpy.types.Operator):
-    """Tooltip"""
-    bl_idname = "mesh.update_fix"
-    bl_label = "Update fix"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-        #print("------------")
-#       for it in bpy.app.driver_namespace:
-#           print(it)
-        bpy.app.driver_namespace['loftdriver'] = loftdriver
-        bpy.app.driver_namespace['birail1driver'] = birail1driver
-        for obj in context.scene.objects:
-            if obj.type=="CURVE" and obj.animation_data!=None and len(obj.animation_data.drivers)>0:
-                for drv in obj.animation_data.drivers:
-                    if drv.data_path=='["driver"]':
-                        cad = drv.driver.expression
-                        drv.driver.expression = ""
-                        drv.driver.expression = cad
-
-        return {'FINISHED'}
-
-
-#derives a curve at a given parameter
-def deriv(curve, t, unit=False):
-
-    a = t + 0.001
-    if t==1: a=t-0.001
-
-    pos = calct(curve, t)
-    der = (pos-calct(curve, a))/(t-a)
-    if unit:
-        der = der/der.magnitude
-    return der
-
-### BIRAIL1 BLOCK
-
-
-#see explanation video about the construction
-#http://vimeo.com/25455967
-
-#calculates birail vertices
-
-### TODO: when the 3 curves are coplanar it should fail, cause the cross product. check that
-def birail1(objs, steps, spans, proportional):
-
-    profile=objs[0]
-    ### TODO: identify which path is left or right
-    path1 = objs[1]
-    path2 = objs[2]
-
-    trans = []
-
-    r0 = [calct(path1,0), calct(path2, 0)]
-    r0mag = (r0[1]-r0[0]).magnitude
-
-    for i in range(0, steps):
-        u = i/(steps-1)
-        appr0 = r0[0]+(r0[1]-r0[0])*u
-        trans.append(calct(profile, u)-appr0)
-
-    der10 = deriv(path1, 0)
-    der20 = deriv(path2, 0)
-
-    verts = []
-
-    mult = 1.0
-
-    for i in range(0, spans):
-        v = i/(spans-1)
-        r = [calct(path1, v),calct(path2, v)]
-        rmag = (r[1]-r[0]).magnitude
-
-        der1 = deriv(path1, v)
-        der2 = deriv(path2, v)
-
-        angle1 = der10.angle(der1)
-        angle2 = der20.angle(der2)
-
-        #if angle1!=0.0 and angle2!=0: we can avoid some operations by doing this check but im lazy
-        cr1 = der1.cross(der10)
-        rot1 = Matrix.Rotation(-angle1, 3, cr1)
-
-        cr2 = der2.cross(der20)
-        rot2 = Matrix.Rotation(-angle2, 3, cr2)
-
-        if proportional:
-            mult = rmag/r0mag
-
-        for j in range(0, steps):
-            u = j/(steps-1)
-
-            app = r[0]+(r[1]-r[0])*u
-
-            newtr1 = trans[j].copy()
-            newtr1.rotate(rot1)
-
-            newtr2 = trans[j].copy()
-            newtr2.rotate(rot2)
-
-            r1 = (newtr1-trans[j])*(1-u)
-            r2 = (newtr2-trans[j])*(u)
-
-            res = r1+r2+app+mult*trans[j]
-
-            verts.append(res)
-
-    return verts
-
-
-#same as loft driver
-### TODO check if it is registered
-def birail1driver(name, objs):
-
-    objs = objs.split(";")
-    #objs = objs[0:-1]
-
-    for i, l in enumerate(objs):
-        objs[i] = bpy.data.objects[l]
-
-    try:
-        resobj = bpy.data.objects[name]
-        spans = resobj["spans"]
-        steps = resobj["steps"]
-        prop = resobj["prop"]
-
-    except:
-        curve = bpy.context.object
-        curve.driver_remove('["driver"]')
-        return False
-
-    vxs = birail1(objs, steps, spans, prop)
-
-    me = resobj.data
-
-    for i in range(0, len(me.vertices)):
-        me.vertices[i].co = vxs[i]
-    me.update()
-    return spans
-
-def createbirail1driver(objs, res):
-
-    line = ""
-    for obj in objs:
-        line+=obj.name+";"
-    line=line[0:-1]
-    for obj in objs:
-        obj["driver"] = 1.0
-        obj.driver_add('["driver"]')
-        obj.animation_data.drivers[0].driver.expression = "birail1driver('"+ res.name +"', '" + line + "')"
-
-### TODO: check polls and if initial variables are ok to perform the birail
-class Birail1Operator(bpy.types.Operator):
-
-    bl_idname = "mesh.birail1_operator"
-    bl_label = "Birail between 3 bezier curves"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-
-        objs = bpy.selection
-
-        if len(objs)!=3:
-            self.report({'ERROR'},"Please select 3 curves")
-            return {'FINISHED'}
-
-        scn = context.scene
-        spans = scn.spans
-        steps = scn.steps
-        prop = scn.proportional
-
-        verts = birail1(objs, steps, spans, prop)
-
-        if verts!=[]:
-            faces=[]
-
-            nfaces = (steps-1)*(spans-1)
-
-            for i in range(0, nfaces):
-                d = int(i/(steps-1))
-                f = [i+d+1, i+d, i+d+steps, i+d+steps+1 ]
-                faces.append(f)
-
-            me = bpy.data.meshes.new("Birail")
-            me.from_pydata(verts,[], faces)
-            me.update()
-            newobj = bpy.data.objects.new("Birail", me)
-            newobj.data = me
-
-            scn.objects.link(newobj)
-            scn.objects.active = newobj
-            newobj.select = True
-            bpy.ops.object.shade_smooth()
-            newobj['steps']=steps
-            newobj['spans']=spans
-            newobj['prop']=prop
-
-            if scn.dodriver:
-                createbirail1driver(objs, newobj)
-
-        return {'FINISHED'}
-
-#register the drivers
-bpy.app.driver_namespace['loftdriver'] = loftdriver
-bpy.app.driver_namespace['birail1driver'] = birail1driver
-
-### MERGE SPLINES BLOCK
-
-#reads spline points
-#spl spline to read
-#rev reads the spline forward or backwards
-def readspline(spl, rev=0):
-    res = []
-
-    if spl.type=="BEZIER":
-        points = spl.bezier_points
-        for p in points:
-            if rev:
-                h2 = p.handle_left
-                h1 = p.handle_right
-                h2type = p.handle_left_type
-                h1type = p.handle_right_type
-            else:
-                h1 = p.handle_left
-                h2 = p.handle_right
-                h1type = p.handle_left_type
-                h2type = p.handle_right_type
-
-            co = p.co
-            res.append([h1, co, h2, h1type, h2type])
-    if rev:
-        res.reverse()
-
-    return res
-
-#returns a new merged spline
-#cu curve object
-#pts1 points from the first spline
-#pts2 points from the second spline
-
-def merge(cu, pts1, pts2):
-    newspl = cu.data.splines.new(type="BEZIER")
-    for i, p in enumerate(pts1):
-
-        if i>0: newspl.bezier_points.add()
-        newspl.bezier_points[i].handle_left = p[0]
-        newspl.bezier_points[i].co = p[1]
-        newspl.bezier_points[i].handle_right = p[2]
-        newspl.bezier_points[i].handle_left_type = p[3]
-        newspl.bezier_points[i].handle_right_type = p[4]
-
-    newspl.bezier_points[-1].handle_right_type="FREE"
-    newspl.bezier_points[-1].handle_left_type="FREE"
-
-    newspl.bezier_points[-1].handle_right = pts2[0][2]
-
-
-    for j in range(1, len(pts2)):
-
-        newspl.bezier_points.add()
-        newspl.bezier_points[-1].handle_left = pts2[j][0]
-        newspl.bezier_points[-1].co = pts2[j][1]
-        newspl.bezier_points[-1].handle_right = pts2[j][2]
-        newspl.bezier_points[-1].handle_left_type = pts2[j][3]
-        newspl.bezier_points[-1].handle_right_type = pts2[j][4]
-
-    return newspl
-
-#looks if the splines first and last points are close to another spline
-### TODO: Check if the objects selected are valid
-### if possible implement nurbs
-
-class MergeSplinesOperator(bpy.types.Operator):
-
-    bl_idname = "curve.merge_splines"
-    bl_label = "Merges spline points inside a limit"
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object != None
-
-    def execute(self, context):
-        curves = []
-        limit = context.scene.limit
-        print("merguing")
-        for obj in context.selected_objects:
-            if obj.type=="CURVE":
-                curves.append(obj)
-
-        for cu in curves:
-            splines = []
-            for spl in cu.data.splines:
-                splines.append(spl)
-            print(splines)
-            #compares all the splines inside a curve object
-            for spl1 in splines:
-                for spl2 in splines:
-                    print(spl1, spl2)
-                    if spl1!=spl2 and spl1.type==spl2.type=="BEZIER" and spl1.use_cyclic_u==spl2.use_cyclic_u==False:
-                        print("not cyclic")
-                        if len(spl1.bezier_points)>1  and len(spl2.bezier_points)>1:
-
-                            #edges of the 2 splines
-                            p1i = spl1.bezier_points[0].co
-                            p1f = spl1.bezier_points[-1].co
-                            p2i = spl2.bezier_points[0].co
-                            p2f = spl2.bezier_points[-1].co
-
-                            if dist(p1i, p2i)<limit:
-                                print("join p1i p2i")
-
-                                p1 = readspline(spl2, 1)
-                                p2 = readspline(spl1)
-                                res=merge(cu, p1, p2)
-                                cu.data.splines.remove(spl1)
-                                cu.data.splines.remove(spl2)
-                                splines.append(res)
-                                break
-                            elif dist(p1i, p2f)<limit:
-                                print("join p1i p2f")
-                                p1 = readspline(spl2)
-                                p2 = readspline(spl1)
-                                res = merge(cu, p1, p2)
-                                cu.data.splines.remove(spl1)
-                                cu.data.splines.remove(spl2)
-                                splines.append(res)
-                                break
-                            elif dist(p1f, p2i)<limit:
-                                print("join p1f p2i")
-                                p1 = readspline(spl1)
-                                p2 = readspline(spl2)
-                                res = merge(cu, p1, p2)
-                                cu.data.splines.remove(spl1)
-                                cu.data.splines.remove(spl2)
-                                splines.append(res)
-                                break
-                            elif dist(p1f, p2f)<limit:
-                                print("unir p1f p2f")
-                                p1 = readspline(spl1)
-                                p2 = readspline(spl2, 1)
-                                res = merge(cu, p1, p2)
-                                cu.data.splines.remove(spl1)
-                                cu.data.splines.remove(spl2)
-                                splines.append(res)
-                                break
-
-        #splines.remove(spl1)
-        return {'FINISHED'}
-
-### NURBS WEIGHTS
-
-class NurbsWeightsPanel(bpy.types.Panel):
-    bl_label = "Nurbs Weights"
-    bl_space_type = "VIEW_3D"
-    bl_region_type = "TOOLS"
-    #bl_context = "data"
-
-    @classmethod
-    def poll(cls, context):
-        if context.active_object != None and context.active_object.type =="CURVE" and context.active_object.data.splines.active!=None and context.active_object.data.splines.active.type=="NURBS":
-            return True
-        else:
-            return False
-
-
-    def draw(self, context):
-        layout = self.layout
-
-        obj = context.object
-
-        for p in obj.data.splines.active.points:
-            if p.select:
-                row = layout.row()
-                row.prop(p,  "weight")
-
-### CUT / SUBDIVIDE CURVE
-#obj curve object
-#t parameter to perform the cut or split
-#method True = subdivide, Flase= Split
-
-def cutcurve(obj, t, method=True):
-
-    #flocal to global transforms or viceversa
-
-
-    #retrieves the active spline or the first spline if there no one active
-
-    spline=None
-    if obj.data.splines.active==None:
-        for sp in obj.data.splines:
-            if sp.type=="BEZIER":
-                spline= sp
-                break
-    else:
-        if obj.data.splines.active.type!="BEZIER":
-            return False
-        else:
-            spline=obj.data.splines.active
-
-    if spline==None: return False
-
-    points = spline.bezier_points
-    nsegs = len(points)-1
-
-    #transform global t into local t1
-    d = 1.0/nsegs
-    seg = int(t/d)
-    t1 = t/d-int(t/d)
-    if t>=1.0:
-        t=1.0
-        seg-=1
-        t1 = 1.0
-
-    #if t1 is not inside a segment dont perform any action
-    if t1>0.0 and t1<1.0:
-        mw = obj.matrix_world
-        mwi = obj.matrix_world.copy().inverted()
-
-        pts = getbezpoints(spline, mw, seg)
-
-        #position on the curve to perform the action
-        pos = calct(obj, t)
-
-        #De Casteljau's algorithm to get the handles
-        #http://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm
-        h1 = pts[0]+(pts[1]-pts[0])*t1
-        h4 = pts[2]+(pts[3]-pts[2])*t1
-        r = pts[1]+(pts[2]-pts[1])*t1
-        h2 = h1+(r-h1)*t1
-        h3 = r+(h4-r)*t1
-
-
-        if method:
-            #SUBDIVIDE
-            splp = []
-            type = "ALIGNED"
-            for i, p in enumerate(points):
-                ph1 = p.handle_left*mw
-                pco = p.co*mw
-                ph2 = p.handle_right*mw
-                ph1type = p.handle_left_type
-                ph2type = p.handle_right_type
-                splp.append([ph1, pco, ph2, ph1type, ph2type])
-                p.handle_left_type = type
-                p.handle_right_type = type
-
-                if i==seg:
-                    splp[-1][2]=h1
-                    splp.append([h2, pos, h3, type, type])
-
-                if i==seg+1:
-                    splp[-1][0]=h4
-                    splp[-1][3]=type
-                    splp[-1][4]=type
-                #if i dont set all the handles to "FREE"
-                #it returns weirds result
-                ### TODO: find out how to preserve handle's types
-
-            points.add()
-            for i, p in enumerate(points):
-                p.handle_left_type = "FREE"
-                p.handle_right_type ="FREE"
-                p.handle_left = splp[i][0]*mwi
-                p.co = splp[i][1]*mwi
-                p.handle_right=splp[i][2]*mwi
-                p.handle_left_type = splp[i][3]
-                p.handle_right_type =splp[i][4]
-        else:
-            #SPLIT CURVE
-            spl1 = []
-            spl2 = []
-            k=0 #changes to 1 when the first spline is processed
-            type = "ALIGNED"
-            for i, p in enumerate(points):
-                ph1 = p.handle_left*mw
-                pco = p.co*mw
-                ph2 = p.handle_right*mw
-                ph1type = p.handle_left_type
-                ph2type = p.handle_right_type
-                if k==0:
-                    spl1.append([ph1, pco, ph2, ph1type, ph2type])
-                else:
-                    spl2.append([ph1, pco, ph2, ph1type, ph2type])
-
-                if i==seg:
-                    spl1[-1][2]=h1
-                    spl1.append([h2, pos, h3, type, type])
-                    spl2.append([h2, pos, h3, type, type])
-                    k=1
-
-                if i==seg+1:
-                    spl2[-1][0]=h4
-                    spl2[-1][3]=type
-                    spl2[-1][4]=type
-
-            sp1 = obj.data.splines.new(type="BEZIER")
-            for i, p in enumerate(spl1):
-                if i>0: sp1.bezier_points.add()
-                sp1.bezier_points[i].handle_left_type = "FREE"
-                sp1.bezier_points[i].handle_right_type ="FREE"
-                sp1.bezier_points[i].handle_left = spl1[i][0]*mwi
-                sp1.bezier_points[i].co = spl1[i][1]*mwi
-                sp1.bezier_points[i].handle_right=spl1[i][2]*mwi
-                #i tried to preserve the handles here but
-                #didnt work well
-
-                sp1.bezier_points[i].handle_left_type = spl1[i][3]
-                sp1.bezier_points[i].handle_right_type =spl1[i][4]
-
-            sp2 = obj.data.splines.new(type="BEZIER")
-            for i, p in enumerate(spl2):
-                if i>0: sp2.bezier_points.add()
-                sp2.bezier_points[i].handle_left_type = "FREE"
-                sp2.bezier_points[i].handle_right_type = "FREE"
-                sp2.bezier_points[i].handle_left = spl2[i][0]*mwi
-                sp2.bezier_points[i].co = spl2[i][1]*mwi
-                sp2.bezier_points[i].handle_right=spl2[i][2]*mwi
-                sp2.bezier_points[i].handle_left_type = spl2[i][3]
-                sp2.bezier_points[i].handle_right_type =spl2[i][4]
-
-            obj.data.splines.remove(spline)
-
-class CutCurveOperator(bpy.types.Operator):
-    """Subdivide / Split a bezier curve"""
-    bl_idname = "curve.cut_operator"
-    bl_label = "Cut curve operator"
-
-    #cut or split
-    method = bpy.props.BoolProperty(default=False)
-    t = 0.0
-
-    @classmethod
-    def poll(self, context):
-        if context.active_object!=None:
-            return context.active_object.type=="CURVE"
-        else:
-            return False
-
-
-    def modal(self, context, event):
-
-        if event.type == 'MOUSEMOVE':
-            #full screen width
-            #not tested for multiple monitors
-            fullw = context.window_manager.windows[0].screen.areas[0].regions[0].width
-
-            self.t = event.mouse_x/fullw
-
-            #limit t to [0,...,1]
-            if self.t<0:
-                self.t=0.0
-            elif self.t>1.0:
-                self.t=1.0
-
-            obj = context.object
-            pos = calct(obj, self.t)
-
-            #if calct() detects a non bezier spline returns false
-            if pos==False:
-                return {'CANCELLED'}
-            cursor(pos)
-
-        elif event.type == 'LEFTMOUSE':
-            #print(self.method, self.t)
-            cutcurve(context.object, self.t, self.method)
-            return {'FINISHED'}
-
-        elif event.type in {'RIGHTMOUSE', 'ESC'}:
-            #print("Cancelled")
-
-            return {'CANCELLED'}
-
-        return {'RUNNING_MODAL'}
-
-    def invoke(self, context, event):
-
-        if context.object:
-            context.window_manager.modal_handler_add(self)
-            return {'RUNNING_MODAL'}
-        else:
-            self.report({'WARNING'}, "No active object, could not finish")
-            return {'CANCELLED'}
-
-### CURVE SNAP BLOCK
-
-class AllowCurveSnap(bpy.types.Operator):
-    bl_idname = "curve.allow_curve_snap"
-    bl_label = "Allow Curve Snap"
-
-    add = bpy.props.BoolProperty()
-
-    @classmethod
-    def poll(cls, context):
-        return context.active_object!=None
-
-    def execute(self, context):
-        add = self.add
-
-        scn = context.scene
-
-        if add==False:
-            for helper in context.scene.objects:
-                for key  in helper.keys():
-                    print(key)
-                    if key=="is_snap_helper" and helper[key]==1:
-                        scn.objects.unlink(helper)
-        else:
-            #objs = context.selected_objects
-            objs = context.scene.objects
-            for obj in objs:
-                if obj.type=="CURVE":
-
-                    res = obj.data.resolution_u
-
-                    obj.data.resolution_u = 100
-
-                    me = obj.to_mesh(scene=scn, apply_modifiers=True,settings = "PREVIEW" )
-                    obj.data.resolution_u = res
-                    newobj = bpy.data.objects.new(obj.name+"_snap", me)
-                    scn.objects.link(newobj)
-                    newobj.layers = obj.layers
-                    newobj.matrix_world = obj.matrix_world
-                    newobj["is_snap_helper"]=True
-                    newobj.hide_render=True
-                    newobj.hide_select = True
-                    cons = newobj.constraints.new(type="COPY_TRANSFORMS")
-                    cons.target =obj
-
-        return {'FINISHED'}
-
-def menu_func(self, context):
-    self.layout.operator("curve.allow_curve_snap").add=True
-    self.layout.operator("curve.allow_curve_snap", text = "Delete Snap Helpers").add=False
-
-### PANEL
-class CurvePanel(bpy.types.Panel):
-    bl_label = "Curve Tools"
-    bl_space_type = "VIEW_3D"
-    bl_region_type = "TOOLS"
-    #bl_options = {'REGISTER', 'UNDO'}
-    #bl_context = "data"
-    bl_options = {'DEFAULT_CLOSED'}
-    steps = IntProperty(min=2, default = 12)
-
-    @classmethod
-    def poll(cls, context):
-        return (context.active_object != None) and (context.active_object.type=="CURVE")
-    def draw(self, context):
-        layout = self.layout
-
-        obj = context.object
-        scn = context.scene
-
-        align = True
-        row = layout.row(align=align)
-
-        row.prop(context.scene, "intype", text = "")
-        row.prop(context.scene, "dodriver", text = "Driven")
-        if scn.intype=='3': #Hermite interp
-            row = layout.row(align=align)
-            row.prop(scn, "tension")
-            row.prop(scn, "bias")
-
-        row = layout.row(align=align)
-        row.prop(context.scene, "steps")
-        row.prop(context.scene, "spans")
-        row = layout.row(align=align)
-        row.operator("mesh.loft_operator", text = "Loft")
-        row.operator("mesh.update_fix", text = "Update Fix")
-        row = layout.row(align=align)
-        row.operator("mesh.birail1_operator", text = "Birail 1")
-        row.prop(context.scene, "proportional", text = "Proportional")
-        row = layout.row(align=align)
-        row.operator("curve.arc_length_operator", text = "Calc Length")
-        row.prop(context.scene, "clen", text = "")
-        row = layout.row(align=align)
-        row.operator("curve.merge_splines", text = "Merge")
-        row.prop(context.scene,"limit",  text = "Limit")
-        row = layout.row(align=align)
-        row.operator("curve.cut_operator", text="Subdivide").method=True
-        row.operator("curve.cut_operator", text="Split").method=False
-
-#       col1 = row.column()
-#       col1.prop(context.scene, "intype", text = "")
-#       col1.prop(context.scene, "dodriver", text = "Driven")
-#       row = layout.row(align=align)
-#       col2 = row.column(align=align)
-#       col2.prop(context.scene, "steps")
-#       col2.prop(context.scene, "spans")
-#       row = layout.row(align=align)
-#       row.operator("mesh.loft_operator", text = "Loft")
-#       row.operator("mesh.update_fix", text = "Update Fix")
-#       row = layout.row(align=align)
-#       row.operator("mesh.birail1_operator", text = "Birail 1")
-#       row.prop(context.scene, "proportional", text = "Proportional")
-#       row = layout.row(align=align)
-#       row.operator("curve.arc_length_operator", text = "Calc Length")
-#       row.prop(context.scene, "clen", text = "")
-#       row = layout.row(align=align)
-#       row.operator("curve.merge_splines", text = "Merge")
-#       row.prop(context.scene,"limit",  text = "Limit")
-#       row = layout.row(align=align)
-#       row.operator("curve.cut_operator", text="Subdivide").method=True
-#       row.operator("curve.cut_operator", text="Split").method=False
-
-classes = [AllowCurveSnap, Selection, LoftOperator, Birail1Operator,
-            ArcLengthOperator, UpdateFix, MergeSplinesOperator, CutCurveOperator, NurbsWeightsPanel, CurvePanel]
-
-bpy.app.driver_namespace['loftdriver'] = loftdriver
-bpy.app.driver_namespace['birail1driver'] = birail1driver
-
-def register():
-
-    domenu=1
-    for op in dir(bpy.types):
-        if op=="CURVE_OT_allow_curve_snap":
-            domenu=0
-            break
-    if domenu:
-        bpy.types.VIEW3D_MT_object_specials.append(menu_func)
-
-    for c in classes:
-        bpy.utils.register_class(c)
-
-def unregister():
-    for c in classes:
-        bpy.utils.unregister_class(c)
-
-    bpy.types.VIEW3D_MT_object_specials.remove(menu_func)
-
-
-if __name__ == "__main__":
-    register()
diff --git a/curve_tools/CurveIntersections.py b/curve_tools/CurveIntersections.py
new file mode 100644 (file)
index 0000000..8affa8d
--- /dev/null
@@ -0,0 +1,790 @@
+from . import Math
+from . import Curves
+from . import Util
+
+from mathutils import *
+
+import bpy
+
+
+algoPOV = None
+algoDIR = None
+
+
+class BezierSegmentIntersectionPoint:
+    def __init__(self, segment, parameter, intersectionPoint):
+        self.segment = segment
+        self.parameter = parameter
+        self.intersectionPoint = intersectionPoint
+        
+        
+class BezierSegmentsIntersector:
+    def __init__(self, segment1, segment2, worldMatrix1, worldMatrix2):
+        self.segment1 = segment1
+        self.segment2 = segment2
+        self.worldMatrix1 = worldMatrix1
+        self.worldMatrix2 = worldMatrix2
+    
+    
+    def CalcFirstIntersection(self, nrSamples1, nrSamples2):
+        algorithm = bpy.context.scene.curvetools.IntersectCurvesAlgorithm
+        
+        if algorithm == '3D': return self.CalcFirstRealIntersection3D(nrSamples1, nrSamples2)
+        
+        if algorithm == 'From View':
+            global algoDIR
+            if not algoDIR is None: return self.CalcFirstRealIntersectionFromViewDIR(nrSamples1, nrSamples2)
+            
+            global algoPOV
+            if not algoPOV is None: return self.CalcFirstRealIntersectionFromViewPOV(nrSamples1, nrSamples2)
+        
+        return None
+        
+    
+    def CalcFirstIntersection3D(self, nrSamples1, nrSamples2):
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        limitDistance = bpy.context.scene.curvetools.LimitDistance
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, intersectionPointData[2])
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, intersectionPointData[3])
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+        
+    
+    def CalcFirstRealIntersection3D(self, nrSamples1, nrSamples2):
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        limitDistance = bpy.context.scene.curvetools.LimitDistance
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
+                if intersectionPointData is None: continue
+                
+                # intersection point can't be an existing point
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / (fltNrSamples1))
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or (Math.IsSamePoint(P1, worldPoint1, limitDistance)):
+                    intersectionPoint1 = None
+                else:
+                    intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / (fltNrSamples2))
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or (Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
+                    intersectionPoint2 = None
+                else:
+                    intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+        
+    
+    def CalcFirstIntersectionFromViewDIR(self, nrSamples1, nrSamples2):
+        global algoDIR
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+        
+    
+    def CalcFirstRealIntersectionFromViewDIR(self, nrSamples1, nrSamples2):
+        global algoDIR
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        limitDistance = bpy.context.scene.curvetools.LimitDistance
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
+                if intersectionPointData is None: continue
+                
+                # intersection point can't be an existing point
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / (fltNrSamples1))
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or (Math.IsSamePoint(P1, worldPoint1, limitDistance)):
+                    intersectionPoint1 = None
+                else:
+                    intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / (fltNrSamples2))
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or (Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
+                    intersectionPoint2 = None
+                else:
+                    intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+        
+    
+    def CalcFirstIntersectionFromViewPOV(self, nrSamples1, nrSamples2):
+        global algoPOV
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+        
+    
+    def CalcFirstRealIntersectionFromViewPOV(self, nrSamples1, nrSamples2):
+        global algoPOV
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        limitDistance = bpy.context.scene.curvetools.LimitDistance
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
+                if intersectionPointData is None: continue
+                
+                # intersection point can't be an existing point
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                if (Math.IsSamePoint(P0, worldPoint1, limitDistance)) or (Math.IsSamePoint(P1, worldPoint1, limitDistance)):
+                    intersectionPoint1 = None
+                else:
+                    intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                if (Math.IsSamePoint(Q0, worldPoint2, limitDistance)) or (Math.IsSamePoint(Q1, worldPoint2, limitDistance)):
+                    intersectionPoint2 = None
+                else:
+                    intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                
+                return [intersectionPoint1, intersectionPoint2]
+                
+        return None
+    
+    
+    
+    
+    def CalcIntersections(self, nrSamples1, nrSamples2):
+        algorithm = bpy.context.scene.curvetools.IntersectCurvesAlgorithm
+        
+        if algorithm == '3D': return self.CalcIntersections3D(nrSamples1, nrSamples2)
+        
+        if algorithm == 'From View':
+            global algoDIR
+            if not algoDIR is None: return self.CalcIntersectionsFromViewDIR(nrSamples1, nrSamples2)
+            
+            global algoPOV
+            if not algoPOV is None: return self.CalcIntersectionsFromViewPOV(nrSamples1, nrSamples2)
+        
+        return [[], []]
+        
+    
+    def CalcIntersections3D(self, nrSamples1, nrSamples2):
+        rvIntersections1 = []
+        rvIntersections2 = []
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        limitDistance = bpy.context.scene.curvetools.LimitDistance
+        
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointLineSegments(P0, P1, Q0, Q1, limitDistance)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                rvIntersections1.append(intersectionPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                rvIntersections2.append(intersectionPoint2)
+                
+        return [rvIntersections1, rvIntersections2]
+        
+    
+    def CalcIntersectionsFromViewDIR(self, nrSamples1, nrSamples2):
+        global algoDIR
+        
+        rvIntersections1 = []
+        rvIntersections2 = []
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsDIR(P0, P1, Q0, Q1, algoDIR)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                rvIntersections1.append(intersectionPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                rvIntersections2.append(intersectionPoint2)
+                
+        return [rvIntersections1, rvIntersections2]
+        
+    
+    def CalcIntersectionsFromViewPOV(self, nrSamples1, nrSamples2):
+        global algoPOV
+        
+        rvIntersections1 = []
+        rvIntersections2 = []
+        
+        fltNrSamples1 = float(nrSamples1)
+        fltNrSamples2 = float(nrSamples2)
+        
+        for iSample1 in range(nrSamples1):
+            segPar10 = float(iSample1) / fltNrSamples1
+            segPar11 = float(iSample1 + 1) / fltNrSamples1
+            P0 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar10)
+            P1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = segPar11)
+            
+            for iSample2 in range(nrSamples2):
+                segPar20 = float(iSample2) / fltNrSamples2
+                segPar21 = float(iSample2 + 1) / fltNrSamples2
+                Q0 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar20)
+                Q1 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = segPar21)
+                
+                intersectionPointData = Math.CalcIntersectionPointsLineSegmentsPOV(P0, P1, Q0, Q1, algoPOV)
+                if intersectionPointData is None: continue
+                
+                intersectionSegment1Parameter = segPar10 + (intersectionPointData[0] / fltNrSamples1)
+                worldPoint1 = self.worldMatrix1 * self.segment1.CalcPoint(parameter = intersectionSegment1Parameter)
+                intersectionPoint1 = BezierSegmentIntersectionPoint(self.segment1, intersectionSegment1Parameter, worldPoint1)
+                rvIntersections1.append(intersectionPoint1)
+                
+                intersectionSegment2Parameter = segPar20 + (intersectionPointData[1] / fltNrSamples2)
+                worldPoint2 = self.worldMatrix2 * self.segment2.CalcPoint(parameter = intersectionSegment2Parameter)
+                intersectionPoint2 = BezierSegmentIntersectionPoint(self.segment2, intersectionSegment2Parameter, worldPoint2)
+                rvIntersections2.append(intersectionPoint2)
+                
+        return [rvIntersections1, rvIntersections2]
+
+
+class BezierSplineIntersectionPoint:
+    def __init__(self, spline, bezierSegmentIntersectionPoint):
+        self.spline = spline
+        self.bezierSegmentIntersectionPoint = bezierSegmentIntersectionPoint
+        
+        
+class BezierSplinesIntersector:
+    def __init__(self, spline1, spline2, worldMatrix1, worldMatrix2):
+        self.spline1 = spline1
+        self.spline2 = spline2
+        self.worldMatrix1 = worldMatrix1
+        self.worldMatrix2 = worldMatrix2
+        
+        
+    def CalcIntersections(self):
+        rvIntersections1 = []
+        rvIntersections2 = []
+        
+        try: nrSamplesPerSegment1 = int(self.spline1.resolution / self.spline1.nrSegments)
+        except: nrSamplesPerSegment1 = 2
+        if nrSamplesPerSegment1 < 2: nrSamplesPerSegment1 = 2
+        
+        try: nrSamplesPerSegment2 = int(self.spline2.resolution / self.spline2.nrSegments)
+        except: nrSamplesPerSegment2 = 2
+        if nrSamplesPerSegment2 < 2: nrSamplesPerSegment2 = 2
+        
+        for segment1 in self.spline1.segments:
+            for segment2 in self.spline2.segments:
+                segmentsIntersector = BezierSegmentsIntersector(segment1, segment2, self.worldMatrix1, self.worldMatrix2)
+                segmentIntersections = segmentsIntersector.CalcIntersections(nrSamplesPerSegment1, nrSamplesPerSegment2)
+                if segmentIntersections is None: continue
+                
+                segment1Intersections = segmentIntersections[0]
+                for segmentIntersection in segment1Intersections: 
+                    splineIntersection = BezierSplineIntersectionPoint(self.spline1, segmentIntersection)
+                    rvIntersections1.append(splineIntersection)
+                
+                segment2Intersections = segmentIntersections[1]
+                for segmentIntersection in segment2Intersections: 
+                    splineIntersection = BezierSplineIntersectionPoint(self.spline2, segmentIntersection)
+                    rvIntersections2.append(splineIntersection)
+        
+        return [rvIntersections1, rvIntersections2]
+        
+        
+class CurvesIntersector:
+    @staticmethod
+    def FromSelection():
+        selObjects = bpy.context.selected_objects
+        if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
+        
+        blenderActiveCurve = bpy.context.active_object
+        blenderOtherCurve = selObjects[0]
+        if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
+        
+        aCurve = Curves.Curve(blenderActiveCurve)
+        oCurve = Curves.Curve(blenderOtherCurve)
+        
+        return CurvesIntersector(aCurve, oCurve)
+    
+
+    @staticmethod
+    def ResetGlobals():
+        global algoPOV
+        algoPOV = None
+        global algoDIR
+        algoDIR = None
+    
+
+    @staticmethod
+    def InitGlobals():
+        CurvesIntersector.ResetGlobals()
+        
+        
+        algo = bpy.context.scene.curvetools.IntersectCurvesAlgorithm
+        if algo == 'From View':
+            regionView3D = Util.GetFirstRegionView3D()
+            if regionView3D is None: 
+                print("### ERROR: regionView3D is None. Stopping.")
+                return
+                
+            viewPerspective = regionView3D.view_perspective
+            print("--", "viewPerspective:", viewPerspective)
+            
+            
+            if viewPerspective == 'ORTHO':
+                viewMatrix = regionView3D.view_matrix
+                print("--", "viewMatrix:")
+                print(viewMatrix)
+                
+                global algoDIR
+                algoDIR = Vector((viewMatrix[2][0], viewMatrix[2][1], viewMatrix[2][2]))
+                print("--", "algoDIR:", algoDIR)
+
+            # ## TODO: doesn't work properly
+            if viewPerspective == 'PERSP':
+                viewMatrix = regionView3D.view_matrix
+                print("--", "viewMatrix:")
+                print(viewMatrix)
+                
+                global algoPOV
+                algoPOV = regionView3D.view_location.copy()
+                print("--", "algoPOV:", algoPOV)
+                
+                otherPOV = Vector((viewMatrix[0][3], viewMatrix[1][3], viewMatrix[2][3]))
+                print("--", "otherPOV:", otherPOV)
+                
+                localPOV = Vector((0, 0, 0))
+                globalPOV = viewMatrix * localPOV
+                print("--", "globalPOV:", globalPOV)
+
+                
+                perspMatrix = regionView3D.perspective_matrix
+                print("--", "perspMatrix:")
+                print(perspMatrix)
+                
+                globalPOVPersp = perspMatrix * localPOV
+                print("--", "globalPOVPersp:", globalPOVPersp)
+                
+            if viewPerspective == 'CAMERA':
+                camera = bpy.context.scene.camera
+                if camera is None: 
+                    print("### ERROR: camera is None. Stopping.")
+                    return
+                    
+                print("--", "camera:", camera)
+                cameraData = camera.data
+                print("--", "cameraData.type:", cameraData.type)
+                
+                cameraMatrix = camera.matrix_world
+                print("--", "cameraMatrix:")
+                print(cameraMatrix)
+
+                if cameraData.type == 'ORTHO':
+                    cameraMatrix = camera.matrix_world
+                
+                    global algoDIR
+                    #algoDIR = Vector((cameraMatrix[2][0], cameraMatrix[2][1], cameraMatrix[2][2]))
+                    algoDIR = Vector((- cameraMatrix[0][2], - cameraMatrix[1][2], - cameraMatrix[2][2]))
+                    print("--", "algoDIR:", algoDIR)
+
+                if cameraData.type == 'PERSP':
+                    global algoPOV
+                    algoPOV = camera.location.copy()
+                    print("--", "algoPOV:", algoPOV)
+
+        
+    def __init__(self, activeCurve, otherCurve):
+        self.activeCurve = activeCurve
+        self.otherCurve = otherCurve
+        
+        CurvesIntersector.InitGlobals()
+
+        
+    def CalcIntersections(self):
+        rvIntersections1 = []
+        rvIntersections2 = []
+        
+        worldMatrix1 = self.activeCurve.curve.matrix_world
+        worldMatrix2 = self.otherCurve.curve.matrix_world
+        
+        for spline1 in self.activeCurve.splines:
+            for spline2 in self.otherCurve.splines:
+                splineIntersector = BezierSplinesIntersector(spline1, spline2, worldMatrix1, worldMatrix2)
+                splineIntersections = splineIntersector.CalcIntersections()
+                if splineIntersections is None: continue
+                
+                spline1Intersections = splineIntersections[0]
+                for splineIntersection in spline1Intersections: rvIntersections1.append(splineIntersection)
+                
+                spline2Intersections = splineIntersections[1]
+                for splineIntersection in spline2Intersections: rvIntersections2.append(splineIntersection)
+        
+        return [rvIntersections1, rvIntersections2]
+        
+        
+    def CalcAndApplyIntersections(self):
+        mode = bpy.context.scene.curvetools.IntersectCurvesMode
+        
+        if mode == 'Empty': return self.CalcAndApplyEmptyAtIntersections()
+        if mode == 'Insert': return self.CalcAndApplyInsertAtIntersections()
+        if mode == 'Split': return self.CalcAndApplySplitAtIntersections()
+        
+        return [0, 0]
+        
+        
+    def CalcAndApplyEmptyAtIntersections(self):
+        intersections = self.CalcIntersections()
+        intersectionsActive = intersections[0]
+        intersectionsOther = intersections[1]
+        
+        nrActive = 0
+        nrOther = 0
+        
+        affect = bpy.context.scene.curvetools.IntersectCurvesAffect
+        
+        if (affect == 'Both') or (affect == 'Active'):
+            for splineIntersection in intersectionsActive:
+                iPoint = splineIntersection.bezierSegmentIntersectionPoint.intersectionPoint
+                bpy.ops.object.empty_add(type='PLAIN_AXES', view_align=False, location=(iPoint.x, iPoint.y, iPoint.z), rotation=(0, 0, 0))
+                nrActive += 1
+        
+        if (affect == 'Both') or (affect == 'Other'):
+            for splineIntersection in intersectionsOther:
+                iPoint = splineIntersection.bezierSegmentIntersectionPoint.intersectionPoint
+                bpy.ops.object.empty_add(type='PLAIN_AXES', view_align=False, location=(iPoint.x, iPoint.y, iPoint.z), rotation=(0, 0, 0))
+                nrOther += 1
+        
+        return [nrActive, nrOther]
+        
+        
+    def CalcAndApplyInsertAtIntersections(self):
+        nrActive = 0
+        nrOther = 0
+        
+        affect = bpy.context.scene.curvetools.IntersectCurvesAffect
+        affectA = (affect == 'Both') or (affect == 'Active')
+        affectO = (affect == 'Both') or (affect == 'Other')
+        
+        
+        for iSplineA in range(len(self.activeCurve.splines)):
+            splineA = self.activeCurve.splines[iSplineA]
+            nrSegmentsA = len(splineA.segments)
+            resPerSegA = splineA.resolutionPerSegment
+        
+            for iSplineO in range(len(self.otherCurve.splines)):
+                splineO = self.otherCurve.splines[iSplineO]
+                nrSegmentsO = len(splineO.segments)
+                resPerSegO = splineO.resolutionPerSegment
+                
+                
+                iSegA = 0
+                while True:
+                    segA = splineA.segments[iSegA]
+                    
+                    iSegO = 0
+                    while True:
+                        segO = splineO.segments[iSegO]
+                        
+                        segIntersector = BezierSegmentsIntersector(segA, segO, self.activeCurve.worldMatrix, self.otherCurve.worldMatrix)
+                        segFirstIntersection = segIntersector.CalcFirstIntersection(resPerSegA, resPerSegO)
+                        
+                        if not segFirstIntersection is None:
+                            intPointA = segFirstIntersection[0]
+                            intPointO = segFirstIntersection[1]
+                            if (not intPointA is None) and (not intPointO is None):    # else does something weird if 1 of them is None.. 
+                                if affectA:
+                                    if not intPointA is None:
+                                        splineA.InsertPoint(segA, intPointA.parameter)
+                                        
+                                        nrActive += 1
+                                        nrSegmentsA += 1
+                                        
+                                if affectO:
+                                    if not intPointO is None:
+                                        splineO.InsertPoint(segO, intPointO.parameter)
+                                        
+                                        nrOther += 1
+                                        nrSegmentsO += 1
+   
+   
+                        iSegO += 1
+                        if not (iSegO < nrSegmentsO): break
+                            
+                    iSegA += 1
+                    if not (iSegA < nrSegmentsA): break
+            
+            
+                if affectO: splineO.RefreshInScene()
+            
+            if affectA: splineA.RefreshInScene()
+        
+        
+        return [nrActive, nrOther]
+        
+        
+    def CalcAndApplySplitAtIntersections(self):
+        nrActive = 0
+        nrOther = 0
+        
+        affect = bpy.context.scene.curvetools.IntersectCurvesAffect
+        affectA = (affect == 'Both') or (affect == 'Active')
+        affectO = (affect == 'Both') or (affect == 'Other')
+        
+        nrSplinesA = len(self.activeCurve.splines)
+        nrSplinesO = len(self.otherCurve.splines)
+        
+        iSplineA = 0
+        while True:
+            splineA = self.activeCurve.splines[iSplineA]
+            nrSegmentsA = len(splineA.segments)
+            resPerSegA = splineA.resolutionPerSegment
+        
+            iSplineO = 0
+            while True:
+                splineO = self.otherCurve.splines[iSplineO]
+                nrSegmentsO = len(splineO.segments)
+                resPerSegO = splineO.resolutionPerSegment
+                
+                
+                iSegA = 0
+                while True:
+                    segA = splineA.segments[iSegA]
+                    
+                    iSegO = 0
+                    while True:
+                        segO = splineO.segments[iSegO]
+                        
+                        segIntersector = BezierSegmentsIntersector(segA, segO, self.activeCurve.worldMatrix, self.otherCurve.worldMatrix)
+                        segFirstIntersection = segIntersector.CalcFirstIntersection(resPerSegA, resPerSegO)
+                        
+                        if not segFirstIntersection is None:
+                            intPointA = segFirstIntersection[0]
+                            intPointO = segFirstIntersection[1]
+                            if (not intPointA is None) and (not intPointO is None):    # else does something weird if 1 of them is None.. 
+                                if affectA:
+                                    if not intPointA is None:
+                                        print("--", "splineA.Split():")
+                                        newSplinesA = splineA.Split(segA, intPointA.parameter)
+                                        if not newSplinesA is None:
+                                            newResolutions = splineA.CalcDivideResolution(segA, intPointA.parameter)
+                                            newSplinesA[0].resolution = newResolutions[0]
+                                            newSplinesA[1].resolution = newResolutions[1]
+                                                                                    
+                                            splineA = newSplinesA[0]
+                                            self.activeCurve.splines[iSplineA] = splineA
+                                            self.activeCurve.splines.insert(iSplineA + 1, newSplinesA[1])
+                                        
+                                            nrActive += 1
+                                            
+                                if affectO:
+                                    if not intPointO is None:
+                                        print("--", "splineO.Split():")
+                                        newSplinesO = splineO.Split(segO, intPointO.parameter)
+                                        if not newSplinesO is None:
+                                            newResolutions = splineO.CalcDivideResolution(segO, intPointO.parameter)
+                                            newSplinesO[0].resolution = newResolutions[0]
+                                            newSplinesO[1].resolution = newResolutions[1]
+                                        
+                                            splineO = newSplinesO[0]
+                                            self.otherCurve.splines[iSplineO] = splineO
+                                            self.otherCurve.splines.insert(iSplineO + 1, newSplinesO[1])
+                                            
+                                            nrOther += 1
+
+                                            
+                        nrSegmentsO = len(splineO.segments)
+                        iSegO += 1
+                        if not (iSegO < nrSegmentsO): break
+                        
+                    nrSegmentsA = len(splineA.segments)       
+                    iSegA += 1
+                    if not (iSegA < nrSegmentsA): break
+            
+                nrSplinesO = len(self.otherCurve.splines)
+                iSplineO += 1
+                if not (iSplineO < nrSplinesO): break
+            
+            nrSplinesA = len(self.activeCurve.splines)
+            iSplineA += 1
+            if not (iSplineA < nrSplinesA): break
+        
+        if affectA:
+            print("")
+            print("--", "self.activeCurve.RebuildInScene():")
+            self.activeCurve.RebuildInScene()
+        if affectO: 
+            print("")
+            print("--", "self.otherCurve.RebuildInScene():")
+            self.otherCurve.RebuildInScene()
+        
+        return [nrActive, nrOther]
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
+        
\ No newline at end of file
diff --git a/curve_tools/Curves.py b/curve_tools/Curves.py
new file mode 100644 (file)
index 0000000..4c46fea
--- /dev/null
@@ -0,0 +1,594 @@
+from . import Math
+
+import bpy
+
+
+class BezierPoint:
+    @staticmethod
+    def FromBlenderBezierPoint(blenderBezierPoint):
+        return BezierPoint(blenderBezierPoint.handle_left, blenderBezierPoint.co, blenderBezierPoint.handle_right)
+        
+        
+    def __init__(self, handle_left, co, handle_right):
+        self.handle_left = handle_left
+        self.co = co
+        self.handle_right = handle_right
+    
+    
+    def Copy(self):
+        return BezierPoint(self.handle_left.copy(), self.co.copy(), self.handle_right.copy())
+        
+    def Reversed(self):
+        return BezierPoint(self.handle_right, self.co, self.handle_left)
+        
+    def Reverse(self):
+        tmp = self.handle_left
+        self.handle_left = self.handle_right
+        self.handle_right = tmp
+
+
+class BezierSegment:
+    @staticmethod
+    def FromBlenderBezierPoints(blenderBezierPoint1, blenderBezierPoint2):
+        bp1 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint1)
+        bp2 = BezierPoint.FromBlenderBezierPoint(blenderBezierPoint2)
+
+        return BezierSegment(bp1, bp2)
+        
+        
+    def Copy(self):
+        return BezierSegment(self.bezierPoint1.Copy(), self.bezierPoint2.Copy())
+        
+    def Reversed(self):
+        return BezierSegment(self.bezierPoint2.Reversed(), self.bezierPoint1.Reversed())
+        
+    def Reverse(self):
+        # make a copy, otherwise neighboring segment may be affected
+        tmp = self.bezierPoint1.Copy()
+        self.bezierPoint1 = self.bezierPoint2.Copy()
+        self.bezierPoint2 = tmp
+        self.bezierPoint1.Reverse()
+        self.bezierPoint2.Reverse()
+    
+    
+    def __init__(self, bezierPoint1, bezierPoint2):
+        # bpy.types.BezierSplinePoint
+        # ## NOTE/TIP: copy() helps with repeated (intersection) action -- ??
+        self.bezierPoint1 = bezierPoint1.Copy()
+        self.bezierPoint2 = bezierPoint2.Copy()
+        
+        self.ctrlPnt0 = self.bezierPoint1.co
+        self.ctrlPnt1 = self.bezierPoint1.handle_right
+        self.ctrlPnt2 = self.bezierPoint2.handle_left
+        self.ctrlPnt3 = self.bezierPoint2.co
+        
+        self.coeff0 = self.ctrlPnt0
+        self.coeff1 = self.ctrlPnt0 * (-3.0) + self.ctrlPnt1 * (+3.0)
+        self.coeff2 = self.ctrlPnt0 * (+3.0) + self.ctrlPnt1 * (-6.0) + self.ctrlPnt2 * (+3.0)
+        self.coeff3 = self.ctrlPnt0 * (-1.0) + self.ctrlPnt1 * (+3.0) + self.ctrlPnt2 * (-3.0) + self.ctrlPnt3
+        
+        
+    def CalcPoint(self, parameter = 0.5):
+        parameter2 = parameter * parameter
+        parameter3 = parameter * parameter2
+        
+        rvPoint = self.coeff0 + self.coeff1 * parameter + self.coeff2 * parameter2 + self.coeff3 * parameter3
+        
+        return rvPoint
+        
+        
+    def CalcDerivative(self, parameter = 0.5):
+        parameter2 = parameter * parameter
+        
+        rvPoint = self.coeff1 + self.coeff2 * parameter * 2.0 + self.coeff3 * parameter2 * 3.0
+        
+        return rvPoint
+        
+
+    def CalcLength(self, nrSamples = 2):
+        nrSamplesFloat = float(nrSamples)
+        rvLength = 0.0
+        for iSample in range(nrSamples):
+            par1 = float(iSample) / nrSamplesFloat
+            par2 = float(iSample + 1) / nrSamplesFloat
+            
+            point1 = self.CalcPoint(parameter = par1)
+            point2 = self.CalcPoint(parameter = par2)
+            diff12 = point1 - point2
+            
+            rvLength += diff12.magnitude
+        
+        return rvLength
+        
+    
+    #http://en.wikipedia.org/wiki/De_Casteljau's_algorithm
+    def CalcSplitPoint(self, parameter = 0.5):
+        par1min = 1.0 - parameter
+        
+        bez00 = self.ctrlPnt0
+        bez01 = self.ctrlPnt1
+        bez02 = self.ctrlPnt2
+        bez03 = self.ctrlPnt3
+        
+        bez10 = bez00 * par1min + bez01 * parameter
+        bez11 = bez01 * par1min + bez02 * parameter
+        bez12 = bez02 * par1min + bez03 * parameter
+        
+        bez20 = bez10 * par1min + bez11 * parameter
+        bez21 = bez11 * par1min + bez12 * parameter
+        
+        bez30 = bez20 * par1min + bez21 * parameter
+        
+        bezPoint1 = BezierPoint(self.bezierPoint1.handle_left, bez00, bez10)
+        bezPointNew = BezierPoint(bez20, bez30, bez21)
+        bezPoint2 = BezierPoint(bez12, bez03, self.bezierPoint2.handle_right)
+        
+        return [bezPoint1, bezPointNew, bezPoint2]
+            
+
+class BezierSpline:
+    @staticmethod
+    def FromSegments(listSegments):
+        rvSpline = BezierSpline(None)
+        
+        rvSpline.segments = listSegments
+        
+        return rvSpline
+    
+    
+    def __init__(self, blenderBezierSpline):
+        if not blenderBezierSpline is None:
+            if blenderBezierSpline.type != 'BEZIER': 
+                print("## ERROR:", "blenderBezierSpline.type != 'BEZIER'")
+                raise Exception("blenderBezierSpline.type != 'BEZIER'")
+            if len(blenderBezierSpline.bezier_points) < 1:
+                if not blenderBezierSpline.use_cyclic_u:
+                    print("## ERROR:", "len(blenderBezierSpline.bezier_points) < 1")
+                    raise Exception("len(blenderBezierSpline.bezier_points) < 1")
+        
+        self.bezierSpline = blenderBezierSpline
+        
+        self.resolution = 12
+        self.isCyclic = False
+        if not self.bezierSpline is None:
+            self.resolution = self.bezierSpline.resolution_u
+            self.isCyclic = self.bezierSpline.use_cyclic_u
+            
+        self.segments = self.SetupSegments()
+        
+        
+    def __getattr__(self, attrName):
+        if attrName == "nrSegments":
+            return len(self.segments)
+        
+        if attrName == "bezierPoints":
+            rvList = []
+            
+            for seg in self.segments: rvList.append(seg.bezierPoint1)
+            if not self.isCyclic: rvList.append(self.segments[-1].bezierPoint2)
+            
+            return rvList
+        
+        if attrName == "resolutionPerSegment":
+            try: rvResPS = int(self.resolution / self.nrSegments)
+            except: rvResPS = 2
+            if rvResPS < 2: rvResPS = 2
+            
+            return rvResPS
+        
+        if attrName == "length":
+            return self.CalcLength()
+        
+        return None
+        
+        
+    def SetupSegments(self):
+        rvSegments = []
+        if self.bezierSpline is None: return rvSegments
+        
+        nrBezierPoints = len(self.bezierSpline.bezier_points)
+        for iBezierPoint in range(nrBezierPoints - 1):
+            bezierPoint1 = self.bezierSpline.bezier_points[iBezierPoint]
+            bezierPoint2 = self.bezierSpline.bezier_points[iBezierPoint + 1]
+            rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2))
+        if self.isCyclic:
+            bezierPoint1 = self.bezierSpline.bezier_points[-1]
+            bezierPoint2 = self.bezierSpline.bezier_points[0]
+            rvSegments.append(BezierSegment.FromBlenderBezierPoints(bezierPoint1, bezierPoint2))
+        
+        return rvSegments
+        
+        
+    def UpdateSegments(self, newSegments):
+        prevNrSegments = len(self.segments)
+        diffNrSegments = len(newSegments) - prevNrSegments
+        if diffNrSegments > 0:
+            newBezierPoints = []
+            for segment in newSegments: newBezierPoints.append(segment.bezierPoint1)
+            if not self.isCyclic: newBezierPoints.append(newSegments[-1].bezierPoint2)
+        
+            self.bezierSpline.bezier_points.add(diffNrSegments)
+            
+            for i, bezPoint in enumerate(newBezierPoints):
+                blBezPoint = self.bezierSpline.bezier_points[i]
+                
+                blBezPoint.tilt = 0
+                blBezPoint.radius = 1.0
+                
+                blBezPoint.handle_left_type = 'FREE'
+                blBezPoint.handle_left = bezPoint.handle_left
+                blBezPoint.co = bezPoint.co
+                blBezPoint.handle_right_type = 'FREE'
+                blBezPoint.handle_right = bezPoint.handle_right    
+
+            self.segments = newSegments
+        else:
+            print("### WARNING: UpdateSegments(): not diffNrSegments > 0")
+            
+    
+    def Reversed(self):
+        revSegments = []
+        
+        for iSeg in reversed(range(self.nrSegments)): revSegments.append(self.segments[iSeg].Reversed())
+        
+        rvSpline = BezierSpline.FromSegments(revSegments)
+        rvSpline.resolution = self.resolution
+        rvSpline.isCyclic = self.isCyclic
+        
+        return rvSpline
+            
+    
+    def Reverse(self):
+        revSegments = []
+        
+        for iSeg in reversed(range(self.nrSegments)): 
+            self.segments[iSeg].Reverse()
+            revSegments.append(self.segments[iSeg])
+        
+        self.segments = revSegments
+        
+        
+    def CalcDivideResolution(self, segment, parameter):
+        if not segment in self.segments:
+            print("### WARNING: InsertPoint(): not segment in self.segments")
+            return None
+            
+        iSeg = self.segments.index(segment)
+        dPar = 1.0 / self.nrSegments
+        splinePar = dPar * (parameter + float(iSeg))
+        
+        res1 = int(splinePar * self.resolution)
+        if res1 < 2:
+            print("### WARNING: CalcDivideResolution(): res1 < 2 -- res1: %d" % res1, "-- setting it to 2")
+            res1 = 2
+            
+        res2 = int((1.0 - splinePar) * self.resolution)
+        if res2 < 2:
+            print("### WARNING: CalcDivideResolution(): res2 < 2 -- res2: %d" % res2, "-- setting it to 2")
+            res2 = 2
+        
+        return [res1, res2]
+        # return [self.resolution, self.resolution]
+        
+        
+    def CalcPoint(self, parameter):
+        nrSegs = self.nrSegments
+        
+        segmentIndex = int(nrSegs * parameter)
+        if segmentIndex < 0: segmentIndex = 0
+        if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1
+        
+        segmentParameter = nrSegs * parameter - segmentIndex
+        if segmentParameter < 0.0: segmentParameter = 0.0
+        if segmentParameter > 1.0: segmentParameter = 1.0
+        
+        return self.segments[segmentIndex].CalcPoint(parameter = segmentParameter)
+        
+        
+    def CalcDerivative(self, parameter):
+        nrSegs = self.nrSegments
+        
+        segmentIndex = int(nrSegs * parameter)
+        if segmentIndex < 0: segmentIndex = 0
+        if segmentIndex > (nrSegs - 1): segmentIndex = nrSegs - 1
+        
+        segmentParameter = nrSegs * parameter - segmentIndex
+        if segmentParameter < 0.0: segmentParameter = 0.0
+        if segmentParameter > 1.0: segmentParameter = 1.0
+        
+        return self.segments[segmentIndex].CalcDerivative(parameter = segmentParameter)
+        
+        
+    def InsertPoint(self, segment, parameter):
+        if not segment in self.segments:
+            print("### WARNING: InsertPoint(): not segment in self.segments")
+            return
+        iSeg = self.segments.index(segment)
+        nrSegments = len(self.segments)
+            
+        splitPoints = segment.CalcSplitPoint(parameter = parameter)
+        bezPoint1 = splitPoints[0]
+        bezPointNew = splitPoints[1]
+        bezPoint2 = splitPoints[2]
+        
+        segment.bezierPoint1.handle_right = bezPoint1.handle_right
+        segment.bezierPoint2 = bezPointNew
+        
+        if iSeg < (nrSegments - 1): 
+            nextSeg = self.segments[iSeg + 1]
+            nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left
+        else:
+            if self.isCyclic:
+                nextSeg = self.segments[0]
+                nextSeg.bezierPoint1.handle_left = bezPoint2.handle_left
+            
+        
+        newSeg = BezierSegment(bezPointNew, bezPoint2)
+        self.segments.insert(iSeg + 1, newSeg)
+        
+        
+    def Split(self, segment, parameter):
+        if not segment in self.segments:
+            print("### WARNING: InsertPoint(): not segment in self.segments")
+            return None
+        iSeg = self.segments.index(segment)
+        nrSegments = len(self.segments)
+            
+        splitPoints = segment.CalcSplitPoint(parameter = parameter)
+        bezPoint1 = splitPoints[0]
+        bezPointNew = splitPoints[1]
+        bezPoint2 = splitPoints[2]
+        
+                
+        newSpline1Segments = []
+        for iSeg1 in range(iSeg): newSpline1Segments.append(self.segments[iSeg1])
+        if len(newSpline1Segments) > 0: newSpline1Segments[-1].bezierPoint2.handle_right = bezPoint1.handle_right
+        newSpline1Segments.append(BezierSegment(bezPoint1, bezPointNew))
+        
+        newSpline2Segments = []
+        newSpline2Segments.append(BezierSegment(bezPointNew, bezPoint2))
+        for iSeg2 in range(iSeg + 1, nrSegments): newSpline2Segments.append(self.segments[iSeg2])
+        if len(newSpline2Segments) > 1: newSpline2Segments[1].bezierPoint1.handle_left = newSpline2Segments[0].bezierPoint2.handle_left
+        
+        
+        newSpline1 = BezierSpline.FromSegments(newSpline1Segments)
+        newSpline2 = BezierSpline.FromSegments(newSpline2Segments)
+        
+        return [newSpline1, newSpline2]
+
+
+    def Join(self, spline2, mode = 'At midpoint'):
+        if mode == 'At midpoint':
+            self.JoinAtMidpoint(spline2)
+            return
+            
+        if mode == 'Insert segment':
+            self.JoinInsertSegment(spline2)
+            return
+            
+        print("### ERROR: Join(): unknown mode:", mode)        
+
+
+    def JoinAtMidpoint(self, spline2):
+        bezPoint1 = self.segments[-1].bezierPoint2
+        bezPoint2 = spline2.segments[0].bezierPoint1
+    
+        mpHandleLeft = bezPoint1.handle_left.copy()
+        mpCo = (bezPoint1.co + bezPoint2.co) * 0.5
+        mpHandleRight = bezPoint2.handle_right.copy()
+        mpBezPoint = BezierPoint(mpHandleLeft, mpCo, mpHandleRight)
+        
+        self.segments[-1].bezierPoint2 = mpBezPoint
+        spline2.segments[0].bezierPoint1 = mpBezPoint
+        for seg2 in spline2.segments: self.segments.append(seg2)
+        
+        self.resolution += spline2.resolution
+        self.isCyclic = False    # is this ok?
+
+
+    def JoinInsertSegment(self, spline2):
+        self.segments.append(BezierSegment(self.segments[-1].bezierPoint2, spline2.segments[0].bezierPoint1))
+        for seg2 in spline2.segments: self.segments.append(seg2)
+        
+        self.resolution += spline2.resolution    # extra segment will usually be short -- impact on resolution negligable
+        
+        self.isCyclic = False    # is this ok?
+        
+        
+    def RefreshInScene(self):
+        bezierPoints = self.bezierPoints
+        
+        currNrBezierPoints = len(self.bezierSpline.bezier_points)
+        diffNrBezierPoints = len(bezierPoints) - currNrBezierPoints
+        if diffNrBezierPoints > 0: self.bezierSpline.bezier_points.add(diffNrBezierPoints)
+        
+        for i, bezPoint in enumerate(bezierPoints):
+            blBezPoint = self.bezierSpline.bezier_points[i]
+            
+            blBezPoint.tilt = 0
+            blBezPoint.radius = 1.0
+            
+            blBezPoint.handle_left_type = 'FREE'
+            blBezPoint.handle_left = bezPoint.handle_left
+            blBezPoint.co = bezPoint.co
+            blBezPoint.handle_right_type = 'FREE'
+            blBezPoint.handle_right = bezPoint.handle_right
+
+        self.bezierSpline.use_cyclic_u = self.isCyclic
+        self.bezierSpline.resolution_u = self.resolution
+        
+    
+    def CalcLength(self):
+        try: nrSamplesPerSegment = int(self.resolution / self.nrSegments)
+        except: nrSamplesPerSegment = 2
+        if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2
+        
+        rvLength = 0.0
+        for segment in self.segments:
+            rvLength += segment.CalcLength(nrSamples = nrSamplesPerSegment)
+        
+        return rvLength
+        
+    
+    def GetLengthIsSmallerThan(self, threshold):
+        try: nrSamplesPerSegment = int(self.resolution / self.nrSegments)
+        except: nrSamplesPerSegment = 2
+        if nrSamplesPerSegment < 2: nrSamplesPerSegment = 2
+        
+        length = 0.0
+        for segment in self.segments:
+            length += segment.CalcLength(nrSamples = nrSamplesPerSegment)
+            if not length < threshold: return False
+        
+        return True
+        
+        
+class Curve:
+    def __init__(self, blenderCurve):
+        self.curve = blenderCurve
+        self.curveData = blenderCurve.data
+        
+        self.splines = self.SetupSplines()
+        
+        
+    def __getattr__(self, attrName):
+        if attrName == "nrSplines":
+            return len(self.splines)
+        
+        if attrName == "length":
+            return self.CalcLength()
+        
+        if attrName == "worldMatrix":
+            return self.curve.matrix_world
+        
+        if attrName == "location":
+            return self.curve.location
+        
+        return None
+        
+        
+    def SetupSplines(self):
+        rvSplines = []
+        for spline in self.curveData.splines:
+            if spline.type != 'BEZIER':
+                print("## WARNING: only bezier splines are supported, atm; other types are ignored")
+                continue
+            
+            try: newSpline = BezierSpline(spline)
+            except: 
+                print("## EXCEPTION: newSpline = BezierSpline(spline)")
+                continue
+            
+            rvSplines.append(newSpline)
+        
+        return rvSplines
+        
+        
+    def RebuildInScene(self):
+        self.curveData.splines.clear()
+        
+        for spline in self.splines:
+            blSpline = self.curveData.splines.new('BEZIER')
+            blSpline.use_cyclic_u = spline.isCyclic
+            blSpline.resolution_u = spline.resolution
+            
+            bezierPoints = []
+            for segment in spline.segments: bezierPoints.append(segment.bezierPoint1)
+            if not spline.isCyclic: bezierPoints.append(spline.segments[-1].bezierPoint2)
+            #else: print("????", "spline.isCyclic")
+            
+            nrBezierPoints = len(bezierPoints)
+            blSpline.bezier_points.add(nrBezierPoints - 1)
+                        
+            for i, blBezPoint in enumerate(blSpline.bezier_points):
+                bezPoint = bezierPoints[i]
+                
+                blBezPoint.tilt = 0
+                blBezPoint.radius = 1.0
+                
+                blBezPoint.handle_left_type = 'FREE'
+                blBezPoint.handle_left = bezPoint.handle_left
+                blBezPoint.co = bezPoint.co
+                blBezPoint.handle_right_type = 'FREE'
+                blBezPoint.handle_right = bezPoint.handle_right
+        
+    
+    def CalcLength(self):
+        rvLength = 0.0
+        for spline in self.splines:
+            rvLength += spline.length
+        
+        return rvLength
+        
+        
+    def RemoveShortSplines(self, threshold):
+        splinesToRemove = []
+        
+        for spline in self.splines:
+            if spline.GetLengthIsSmallerThan(threshold): splinesToRemove.append(spline)
+            
+        for spline in splinesToRemove: self.splines.remove(spline)
+        
+        return len(splinesToRemove)
+        
+        
+    def JoinNeighbouringSplines(self, startEnd, threshold, mode):
+        nrJoins = 0
+        
+        while True:
+            firstPair = self.JoinGetFirstPair(startEnd, threshold)
+            if firstPair is None: break
+            
+            firstPair[0].Join(firstPair[1], mode)
+            self.splines.remove(firstPair[1])
+            
+            nrJoins += 1
+            
+        return nrJoins
+            
+    
+    def JoinGetFirstPair(self, startEnd, threshold):
+        nrSplines = len(self.splines)
+        
+        if startEnd:
+            for iCurrentSpline in range(nrSplines):
+                currentSpline = self.splines[iCurrentSpline]
+                
+                for iNextSpline in range(iCurrentSpline + 1, nrSplines):
+                    nextSpline = self.splines[iNextSpline]
+                    
+                    currEndPoint = currentSpline.segments[-1].bezierPoint2.co
+                    nextStartPoint = nextSpline.segments[0].bezierPoint1.co
+                    if Math.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
+                    
+                    nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
+                    currStartPoint = currentSpline.segments[0].bezierPoint1.co
+                    if Math.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
+                    
+            return None
+        else:
+            for iCurrentSpline in range(nrSplines):
+                currentSpline = self.splines[iCurrentSpline]
+                
+                for iNextSpline in range(iCurrentSpline + 1, nrSplines):
+                    nextSpline = self.splines[iNextSpline]
+                    
+                    currEndPoint = currentSpline.segments[-1].bezierPoint2.co
+                    nextStartPoint = nextSpline.segments[0].bezierPoint1.co
+                    if Math.IsSamePoint(currEndPoint, nextStartPoint, threshold): return [currentSpline, nextSpline]
+                    
+                    nextEndPoint = nextSpline.segments[-1].bezierPoint2.co
+                    currStartPoint = currentSpline.segments[0].bezierPoint1.co
+                    if Math.IsSamePoint(nextEndPoint, currStartPoint, threshold): return [nextSpline, currentSpline]
+                    
+                    if Math.IsSamePoint(currEndPoint, nextEndPoint, threshold):
+                        nextSpline.Reverse()
+                        #print("## ", "nextSpline.Reverse()")
+                        return [currentSpline, nextSpline]
+                    
+                    if Math.IsSamePoint(currStartPoint, nextStartPoint, threshold):
+                        currentSpline.Reverse()
+                        #print("## ", "currentSpline.Reverse()")
+                        return [currentSpline, nextSpline]
+                        
+            return None
diff --git a/curve_tools/Math.py b/curve_tools/Math.py
new file mode 100644 (file)
index 0000000..208f769
--- /dev/null
@@ -0,0 +1,137 @@
+from mathutils import *
+
+
+def IsSamePoint(v31, v32, limitDistance):
+    if (v31 - v32).magnitude < limitDistance: return True
+    
+    return False
+
+    
+class Plane:
+    @staticmethod
+    def XY():
+        p1 = Vector((0, 0, 0))
+        p2 = Vector((1, 0, 0))
+        p3 = Vector((0, 1, 0))
+        
+        return Plane(p1, p2, p3)
+        
+
+    # plane equation: (p - position).dot(normal) = 0
+    def __init__(self, P1, P2, P3):
+        self.normal = (P2 - P1).cross(P3 - P1)
+        self.normal.normalize()
+        
+        self.position = P1
+        
+        
+    def CalcIntersectionPointLineSegment(self, PL1, PL2):
+        DL = PL2 - PL1
+        
+        try: rvPar = ((self.position - PL1).dot(self.normal)) / (DL.dot(self.normal))
+        except: return None
+        
+        return rvPar
+        
+        
+    def CalcNormalParameter(self, vector):
+        return (vector - self.position).dot(self.normal)
+        
+        
+    def CalcProjection(self, vector):
+        normalParameter = self.CalcNormalParameter(vector)
+        
+        rvv3 = vector - (self.normal * normalParameter)
+        
+        return [normalParameter, rvv3]
+        
+        
+    
+# http://geomalgorithms.com/a07-_distance.html
+def CalcClosestPointLineSegments(v3P0, v3P1, v3Q0, v3Q1):
+    u = v3P1 - v3P0
+    v = v3Q1 - v3Q0
+    
+    w0 = v3P0 - v3Q0
+    a = u.dot(u)
+    b = u.dot(v)
+    c = v.dot(v)
+    d = u.dot(w0)
+    e = v.dot(w0)
+    
+    
+    try: parP = (b * e - c * d) / (a * c - b * b)
+    except: return None
+    
+    try: parQ = (a * e - b * d) / (a * c - b * b)
+    except: return None
+    
+    
+    return [parP, parQ]
+
+    
+def CalcIntersectionPointLineSegments(v3P0, v3P1, v3Q0, v3Q1, limitDistance):
+    rvList = CalcClosestPointLineSegments(v3P0, v3P1, v3Q0, v3Q1)
+    if rvList is None: return None
+    
+    
+    parP = rvList[0]
+    if parP < 0.0: return None
+    if parP > 1.0: return None
+    
+    parQ = rvList[1]
+    if parQ < 0.0: return None
+    if parQ > 1.0: return None
+    
+    
+    pointP = v3P0 + ((v3P1 - v3P0) * parP)
+    pointQ = v3Q0 + ((v3Q1 - v3Q0) * parQ)
+    if not IsSamePoint(pointP, pointQ, limitDistance): return None
+    
+    return [parP, parQ, pointP, pointQ]
+
+    
+def CalcIntersectionPointsLineSegmentsPOV(v3P0, v3P1, v3Q0, v3Q1, v3POV):
+    planeQ = Plane(v3POV, v3Q0, v3Q1)
+    parP = planeQ.CalcIntersectionPointLineSegment(v3P0, v3P1)
+    if parP is None: return None
+    if parP < 0.0: return None
+    if parP > 1.0: return None
+    
+    planeP = Plane(v3POV, v3P0, v3P1)
+    parQ = planeP.CalcIntersectionPointLineSegment(v3Q0, v3Q1)
+    if parQ is None: return None
+    if parQ < 0.0: return None
+    if parQ > 1.0: return None
+    
+    return [parP, parQ]
+
+    
+def CalcIntersectionPointsLineSegmentsDIR(v3P0, v3P1, v3Q0, v3Q1, v3DIR):
+    v3POV = v3Q0 + v3DIR
+    planeQ = Plane(v3POV, v3Q0, v3Q1)
+    parP = planeQ.CalcIntersectionPointLineSegment(v3P0, v3P1)
+    if parP is None: return None
+    if parP < 0.0: return None
+    if parP > 1.0: return None
+    
+    v3POV = v3P0 + v3DIR
+    planeP = Plane(v3POV, v3P0, v3P1)
+    parQ = planeP.CalcIntersectionPointLineSegment(v3Q0, v3Q1)
+    if parQ is None: return None
+    if parQ < 0.0: return None
+    if parQ > 1.0: return None
+    
+    return [parP, parQ]
+    
+
+
+def CalcRotationMatrix(v3From, v3To):
+    cross = v3From.cross(v3To)
+    
+    try: angle = v3From.angle(v3To)
+    except: return Matrix.Identity(4)
+    
+    return Matrix.Rotation(angle, 4, cross)        # normalize axis?
+    
+    
diff --git a/curve_tools/Operators.py b/curve_tools/Operators.py
new file mode 100644 (file)
index 0000000..ac226e2
--- /dev/null
@@ -0,0 +1,426 @@
+import time
+import threading
+
+import bpy
+from bpy.props import *
+
+from . import Properties
+from . import Curves
+from . import CurveIntersections
+from . import Util
+from . import Surfaces
+
+
+
+class OperatorSelectionInfo(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorselectioninfo"
+    bl_label = "Selection Info"
+    bl_description = "Maintains a list of selected objects in the order they were selected"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        selectedObjectNames = Properties.CurveTools2SelectedObject.GetSelectedObjectNames()
+        selectedBlenderObjectNames = Properties.CurveTools2SelectedObject.GetSelectedBlenderObjectNames()
+        
+        sleepTime = 0.02
+        
+        lock = threading.Lock()
+        lock_holder = threading.Thread(target = Properties.CurveTools2SelectedObject.UpdateThreadTarget, args=(lock, sleepTime, selectedObjectNames, selectedBlenderObjectNames), name='OperatorSelectionInfoThread')
+        # lock_holder = threading.Thread(target = Properties.CurveTools2SelectedObject.UpdateThreadTarget2, args=(lock, sleepTime, selectedObjectNames, selectedBlenderObjectNames, context), name='OperatorSelectionInfoThread')
+        lock_holder.setDaemon(True)
+        lock_holder.start()
+        
+        return True
+
+            
+    def execute(self, context):
+        nrSelectedObjects = bpy.context.scene.curvetools.NrSelectedObjects
+        
+        self.report({'INFO'}, "Selection Info: nrSelectedObjects: %d" % nrSelectedObjects)
+        
+        selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+        selectedObjectValues = selectedObjects.values()
+        for i, selectedObject in enumerate(selectedObjectValues):
+            print("--", "selected object %d of %d: %s" % (i + 1, nrSelectedObjects, selectedObject.name))
+        
+        return {'FINISHED'}
+
+
+
+# 1 CURVE SELECTED
+# ################
+class OperatorCurveInfo(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorcurveinfo"
+    bl_label = "Info"
+    bl_description = "Displays general info about the active/selected curve"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1Curve()
+
+            
+    def execute(self, context):
+        curve = Curves.Curve(context.active_object)
+        
+        nrSplines = len(curve.splines)
+        nrSegments = 0
+        nrEmptySplines = 0
+        for spline in curve.splines: 
+            nrSegments += spline.nrSegments
+            if spline.nrSegments < 1: nrEmptySplines += 1
+        
+        
+        self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
+        
+        return {'FINISHED'}
+
+
+
+class OperatorCurveLength(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorcurvelength"
+    bl_label = "Length"
+    bl_description = "Calculates the length of the active/selected curve"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1Curve()
+
+            
+    def execute(self, context):
+        curve = Curves.Curve(context.active_object)
+        
+        context.scene.curvetools.CurveLength = curve.length
+        
+        return {'FINISHED'}
+
+
+
+class OperatorSplinesInfo(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsplinesinfo"
+    bl_label = "Info"
+    bl_description = "Displays general info about the splines of the active/selected curve"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1Curve()
+
+            
+    def execute(self, context):
+        curve = Curves.Curve(context.active_object)
+        nrSplines = len(curve.splines)
+        
+        print("")
+        print("OperatorSplinesInfo:", "nrSplines:", nrSplines)        
+        
+        nrEmptySplines = 0
+        for iSpline, spline in enumerate(curve.splines):
+            print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, spline.nrSegments))        
+            
+            if spline.nrSegments < 1: 
+                nrEmptySplines += 1
+                print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")        
+        
+        
+        self.report({'INFO'}, "nrSplines: %d; nrEmptySplines: %d" % (nrSplines, nrEmptySplines) + " -- more info: see console")
+        
+        return {'FINISHED'}
+
+
+
+class OperatorSegmentsInfo(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsegmentsinfo"
+    bl_label = "Info"
+    bl_description = "Displays general info about the segments of the active/selected curve"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1Curve()
+
+            
+    def execute(self, context):
+        curve = Curves.Curve(context.active_object)
+        nrSplines = len(curve.splines)
+        nrSegments = 0
+        
+        print("")
+        print("OperatorSegmentsInfo:", "nrSplines:", nrSplines)        
+        
+        nrEmptySplines = 0
+        for iSpline, spline in enumerate(curve.splines):
+            nrSegmentsSpline = spline.nrSegments
+            print("--", "spline %d of %d: nrSegments: %d" % (iSpline + 1, nrSplines, nrSegmentsSpline))        
+            
+            if nrSegmentsSpline < 1: 
+                nrEmptySplines += 1
+                print("--", "--", "## WARNING: spline has no segments and will therefor be ignored in any further calculations")
+                continue
+                
+            for iSegment, segment in enumerate(spline.segments):
+                print("--", "--", "segment %d of %d coefficients:" % (iSegment + 1, nrSegmentsSpline))        
+                print("--", "--", "--", "C0: %.6f, %.6f, %.6f" % (segment.coeff0.x, segment.coeff0.y, segment.coeff0.z))
+                
+            nrSegments += nrSegmentsSpline
+        
+        self.report({'INFO'}, "nrSplines: %d; nrSegments: %d; nrEmptySplines: %d" % (nrSplines, nrSegments, nrEmptySplines))
+        
+        return {'FINISHED'}
+
+
+
+class OperatorOriginToSpline0Start(bpy.types.Operator):
+    bl_idname = "curvetools2.operatororigintospline0start"
+    bl_label = "OriginToSpline0Start"
+    bl_description = "Sets the origin of the active/selected curve to the starting point of the (first) spline. Nice for curve modifiers."
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1Curve()
+
+            
+    def execute(self, context):
+        blCurve = context.active_object
+        blSpline = blCurve.data.splines[0]
+        newOrigin = blCurve.matrix_world * blSpline.bezier_points[0].co
+    
+        origOrigin = bpy.context.scene.cursor_location.copy()
+        print("--", "origOrigin: %.6f, %.6f, %.6f" % (origOrigin.x, origOrigin.y, origOrigin.z))
+        print("--", "newOrigin: %.6f, %.6f, %.6f" % (newOrigin.x, newOrigin.y, newOrigin.z))
+        
+        bpy.context.scene.cursor_location = newOrigin
+        bpy.ops.object.origin_set(type='ORIGIN_CURSOR')
+        bpy.context.scene.cursor_location = origOrigin
+        
+        self.report({'INFO'}, "TODO: OperatorOriginToSpline0Start")
+        
+        return {'FINISHED'}
+
+        
+
+# 2 CURVES SELECTED
+# #################
+class OperatorIntersectCurves(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorintersectcurves"
+    bl_label = "Intersect"
+    bl_description = "Intersects selected curves"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected2Curves()
+
+            
+    def execute(self, context):
+        print("### TODO: OperatorIntersectCurves.execute()")
+        
+        algo = context.scene.curvetools.IntersectCurvesAlgorithm
+        print("-- algo:", algo)
+                    
+            
+        mode = context.scene.curvetools.IntersectCurvesMode
+        print("-- mode:", mode)
+        # if mode == 'Split':
+            # self.report({'WARNING'}, "'Split' mode is not implemented yet -- <<STOPPING>>")
+            # return {'CANCELLED'}
+        
+        affect = context.scene.curvetools.IntersectCurvesAffect
+        print("-- affect:", affect)
+        
+        
+        curveIntersector = CurveIntersections.CurvesIntersector.FromSelection()
+        rvIntersectionNrs = curveIntersector.CalcAndApplyIntersections()
+        
+        self.report({'INFO'}, "Active curve points: %d; other curve points: %d" % (rvIntersectionNrs[0], rvIntersectionNrs[1]))
+        
+        return {'FINISHED'}
+
+
+
+class OperatorLoftCurves(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorloftcurves"
+    bl_label = "Loft"
+    bl_description = "Lofts selected curves"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected2Curves()
+
+            
+    def execute(self, context):
+        #print("### TODO: OperatorLoftCurves.execute()")
+        
+        loftedSurface = Surfaces.LoftedSurface.FromSelection()
+        loftedSurface.AddToScene()
+        
+        self.report({'INFO'}, "OperatorLoftCurves.execute()")
+        
+        return {'FINISHED'}
+
+
+
+class OperatorSweepCurves(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsweepcurves"
+    bl_label = "Sweep"
+    bl_description = "Sweeps the active curve along to other curve (rail)"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected2Curves()
+
+            
+    def execute(self, context):
+        #print("### TODO: OperatorSweepCurves.execute()")
+        
+        sweptSurface = Surfaces.SweptSurface.FromSelection()
+        sweptSurface.AddToScene()
+        
+        self.report({'INFO'}, "OperatorSweepCurves.execute()")
+        
+        return {'FINISHED'}
+
+        
+
+# 3 CURVES SELECTED
+# #################
+class OperatorBirail(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorbirail"
+    bl_label = "Birail"
+    bl_description = "Generates a birailed surface from 3 selected curves -- in order: rail1, rail2 and profile"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected3Curves()
+
+            
+    def execute(self, context):        
+        birailedSurface = Surfaces.BirailedSurface.FromSelection()
+        birailedSurface.AddToScene()
+        
+        self.report({'INFO'}, "OperatorBirail.execute()")
+        
+        return {'FINISHED'}
+
+
+        
+# 1 OR MORE CURVES SELECTED
+# #########################
+class OperatorSplinesSetResolution(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsplinessetresolution"
+    bl_label = "SplinesSetResolution"
+    bl_description = "Sets the resolution of all splines"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1OrMoreCurves()
+
+            
+    def execute(self, context):
+        splRes = context.scene.curvetools.SplineResolution
+        selCurves = Util.GetSelectedCurves()
+        
+        for blCurve in selCurves:
+            for spline in blCurve.data.splines:
+                spline.resolution_u = splRes
+        
+        return {'FINISHED'}
+        
+        
+        
+class OperatorSplinesRemoveZeroSegment(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsplinesremovezerosegment"
+    bl_label = "SplinesRemoveZeroSegment"
+    bl_description = "Removes splines with no segments -- they seem to creep up, sometimes.."
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1OrMoreCurves()
+
+            
+    def execute(self, context):
+        selCurves = Util.GetSelectedCurves()
+        
+        for blCurve in selCurves:
+            curve = Curves.Curve(blCurve)
+            nrSplines = curve.nrSplines
+            
+            splinesToRemove = []
+            for spline in curve.splines:
+                if len(spline.segments) < 1: splinesToRemove.append(spline)
+            nrRemovedSplines = len(splinesToRemove)    
+                
+            for spline in splinesToRemove: curve.splines.remove(spline)
+            
+            if nrRemovedSplines > 0: curve.RebuildInScene()
+            
+            self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
+        
+        return {'FINISHED'}
+
+
+
+class OperatorSplinesRemoveShort(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsplinesremoveshort"
+    bl_label = "SplinesRemoveShort"
+    bl_description = "Removes splines with a length smaller than the threshold"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1OrMoreCurves()
+
+            
+    def execute(self, context):
+        threshold = context.scene.curvetools.SplineRemoveLength
+        selCurves = Util.GetSelectedCurves()
+        
+        for blCurve in selCurves:
+            curve = Curves.Curve(blCurve)
+            nrSplines = curve.nrSplines
+            
+            nrRemovedSplines = curve.RemoveShortSplines(threshold)
+            if nrRemovedSplines > 0: curve.RebuildInScene()
+            
+            self.report({'INFO'}, "Removed %d of %d splines" % (nrRemovedSplines, nrSplines))
+        
+        return {'FINISHED'}
+
+
+
+class OperatorSplinesJoinNeighbouring(bpy.types.Operator):
+    bl_idname = "curvetools2.operatorsplinesjoinneighbouring"
+    bl_label = "SplinesJoinNeighbouring"
+    bl_description = "Joins neighbouring splines within a distance smaller than the threshold"
+    
+    
+    @classmethod
+    def poll(cls, context):
+        return Util.Selected1OrMoreCurves()
+
+            
+    def execute(self, context):
+        selCurves = Util.GetSelectedCurves()
+        
+        for blCurve in selCurves:
+            curve = Curves.Curve(blCurve)
+            nrSplines = curve.nrSplines
+            
+            threshold = context.scene.curvetools.SplineJoinDistance
+            startEnd = context.scene.curvetools.SplineJoinStartEnd
+            mode = context.scene.curvetools.SplineJoinMode
+            
+            nrJoins = curve.JoinNeighbouringSplines(startEnd, threshold, mode)
+            if nrJoins > 0: curve.RebuildInScene()
+            
+            self.report({'INFO'}, "Applied %d joins on %d splines; resulting nrSplines: %d" % (nrJoins, nrSplines, curve.nrSplines))
+        
+        return {'FINISHED'}
diff --git a/curve_tools/Panel.py b/curve_tools/Panel.py
new file mode 100644 (file)
index 0000000..e8d44d5
--- /dev/null
@@ -0,0 +1,140 @@
+import bpy
+
+from . import Operators
+
+
+class Panel(bpy.types.Panel):
+    bl_label = "Curve Tools 2"
+    bl_space_type = "VIEW_3D"
+    bl_region_type = "TOOLS"
+    bl_options = {'DEFAULT_CLOSED'}
+    bl_category = "Addons"
+
+    
+    @classmethod
+    def poll(cls, context):
+        if len(context.selected_objects) > 0:
+            return (context.active_object.type == "CURVE")
+        
+        
+    def draw(self, context):
+        # Z. selection
+        self.layout.label(text = "selection:")
+        boxSelection = self.layout.box()
+        
+        # A.1 curve info/length
+        row = boxSelection.row(align = True)
+        row.operator("curvetools2.operatorselectioninfo", text = "Selection Info")
+        row.prop(context.scene.curvetools, "NrSelectedObjects", text = "")
+        
+        
+        # A. 1 curve
+        self.layout.label(text = "1 curve:")
+        box1Curve = self.layout.box()
+        
+        # A.1 curve info/length
+        row = box1Curve.row(align = True)
+        row.operator("curvetools2.operatorcurveinfo", text = "Curve info")
+
+        row = box1Curve.row(align = True)
+        row.operator("curvetools2.operatorcurvelength", text = "Calc Length")
+        row.prop(context.scene.curvetools, "CurveLength", text = "")
+        
+        # A.2 splines info
+        row = box1Curve.row(align = True)
+        row.operator("curvetools2.operatorsplinesinfo", text = "Curve splines info")
+        
+        # A.3 segments info
+        row = box1Curve.row(align = True)
+        row.operator("curvetools2.operatorsegmentsinfo", text = "Curve segments info")
+        
+        # A.4 origin to spline0start
+        row = box1Curve.row(align = True)
+        row.operator("curvetools2.operatororigintospline0start", text = "Set origin to spline start")
+
+        
+
+        # B. 2 curves
+        self.layout.label(text = "2 curves:")
+        box2Curves = self.layout.box()
+        
+        # B.1 curve intersections
+        boxIntersect = box2Curves.box()
+        
+        row = boxIntersect.row(align = True)
+        row.operator("curvetools2.operatorintersectcurves", text = "Intersect curves")
+
+        row = boxIntersect.row(align = True)
+        row.prop(context.scene.curvetools, "LimitDistance", text = "LimitDistance")
+        #row.active = (context.scene.curvetools.IntersectCurvesAlgorithm == '3D')
+
+        row = boxIntersect.row(align = True)
+        row.prop(context.scene.curvetools, "IntersectCurvesAlgorithm", text = "Algorithm")
+
+        row = boxIntersect.row(align = True)
+        row.prop(context.scene.curvetools, "IntersectCurvesMode", text = "Mode")
+
+        row = boxIntersect.row(align = True)
+        row.prop(context.scene.curvetools, "IntersectCurvesAffect", text = "Affect")
+        
+        
+        # B.2 surface generation
+        boxSurface = box2Curves.box()
+        
+        row = boxSurface.row(align = True)
+        row.operator("curvetools2.operatorloftcurves", text = "Loft curves")
+        
+        row = boxSurface.row(align = True)
+        row.operator("curvetools2.operatorsweepcurves", text = "Sweep curves")
+
+        
+
+        # C. 3 curves
+        self.layout.label(text = "3 curves:")
+        box3Curves = self.layout.box()
+        
+        row = box3Curves.row(align = True)
+        row.operator("curvetools2.operatorbirail", text = "Birail")
+
+        
+        
+        # D. 1 or more curves
+        self.layout.label(text = "1 or more curves:")
+        box1OrMoreCurves = self.layout.box()
+        
+        # D.1 set spline resolution
+        boxSplineRes = box1OrMoreCurves.box()
+        
+        row = boxSplineRes.row(align = True)
+        row.operator("curvetools2.operatorsplinessetresolution", text = "Set resolution")
+        row.prop(context.scene.curvetools, "SplineResolution", text = "")
+        
+        
+        # D.2 remove splines
+        boxSplineRemove = box1OrMoreCurves.box()
+        
+        row = boxSplineRemove.row(align = True)
+        row.operator("curvetools2.operatorsplinesremovezerosegment", text = "Remove 0-segments splines")
+        
+        row = boxSplineRemove.row(align = True)
+        row.operator("curvetools2.operatorsplinesremoveshort", text = "Remove short splines")
+
+        row = boxSplineRemove.row(align = True)
+        row.prop(context.scene.curvetools, "SplineRemoveLength", text = "Threshold remove")
+        
+        
+        # D.3 join splines
+        boxSplineJoin = box1OrMoreCurves.box()
+        
+        row = boxSplineJoin.row(align = True)
+        row.operator("curvetools2.operatorsplinesjoinneighbouring", text = "Join neighbouring splines")
+
+        row = boxSplineJoin.row(align = True)
+        row.prop(context.scene.curvetools, "SplineJoinDistance", text = "Threshold join")
+
+        row = boxSplineJoin.row(align = True)
+        row.prop(context.scene.curvetools, "SplineJoinStartEnd", text = "Only at start & end")
+
+        row = boxSplineJoin.row(align = True)
+        row.prop(context.scene.curvetools, "SplineJoinMode", text = "Join mode")
+        
diff --git a/curve_tools/Properties.py b/curve_tools/Properties.py
new file mode 100644 (file)
index 0000000..9481184
--- /dev/null
@@ -0,0 +1,90 @@
+import time
+
+import bpy
+from bpy.props import *
+
+
+
+class CurveTools2SelectedObjectHeader(bpy.types.Header):
+    bl_label = "Selection"
+    bl_space_type = "VIEW_3D"
+    
+    def __init__(self):
+        self.update()
+
+        
+    def update(self):
+        blenderSelectedObjects = bpy.context.selected_objects
+        selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+        
+        selectedObjectsToRemove = []
+        for selectedObject in selectedObjects:
+            if not selectedObject.IsElementOf(blenderSelectedObjects): selectedObjectsToRemove.append(selectedObject)
+        for selectedObject in selectedObjectsToRemove: selectedObjects.remove(selectedObject)
+        
+        blenderObjectsToAdd = []
+        for blenderObject in blenderSelectedObjects:
+            if not CurveTools2SelectedObject.ListContains(selectedObjects, blenderObject): blenderObjectsToAdd.append(blenderObject)
+        for blenderObject in blenderObjectsToAdd:
+            newSelectedObject = CurveTools2SelectedObject(blenderObject)
+            selectedObjects.append(newSelectedObject)
+
+        
+    def draw(self, context):
+        selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+        nrSelectedObjects = len(selectedObjects)
+        
+        layout = self.layout
+        row = layout.row()
+        row.label("Sel:", nrSelectedObjects)
+
+
+class CurveTools2SelectedObject(bpy.types.PropertyGroup):
+    name = StringProperty(name = "name", default = "??")
+
+    
+    @staticmethod
+    def UpdateThreadTarget(lock, sleepTime, selectedObjectNames, selectedBlenderObjectNames):
+        time.sleep(sleepTime)
+        
+        newSelectedObjectNames = []
+        
+        for name in selectedObjectNames:
+            if name in selectedBlenderObjectNames: newSelectedObjectNames.append(name)
+            
+        for name in selectedBlenderObjectNames:
+            if not (name in selectedObjectNames): newSelectedObjectNames.append(name)
+            
+        # sometimes it still complains about the context
+        try:
+            nrNewSelectedObjects = len(newSelectedObjectNames)
+            bpy.context.scene.curvetools.NrSelectedObjects = nrNewSelectedObjects
+            
+            selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+            selectedObjects.clear()
+            for i in range(nrNewSelectedObjects): selectedObjects.add()
+            for i, newSelectedObjectName in enumerate(newSelectedObjectNames):
+                selectedObjects[i].name = newSelectedObjectName
+        except: pass
+
+        
+    @staticmethod
+    def GetSelectedObjectNames():
+        selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+        
+        rvNames = []
+        selectedObjectValues = selectedObjects.values()
+        for selectedObject in selectedObjectValues: rvNames.append(selectedObject.name)
+        
+        return rvNames
+        
+        
+    @staticmethod
+    def GetSelectedBlenderObjectNames():
+        blenderSelectedObjects = bpy.context.selected_objects
+        
+        rvNames = []
+        for blObject in blenderSelectedObjects: rvNames.append(blObject.name)
+        
+        return rvNames
+        
diff --git a/curve_tools/Surfaces.py b/curve_tools/Surfaces.py
new file mode 100644 (file)
index 0000000..8ce556b
--- /dev/null
@@ -0,0 +1,457 @@
+import bpy
+import bmesh
+
+from . import Math
+from . import Curves
+
+
+
+class LoftedSplineSurface:
+    def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolution):
+        self.splineA = activeSpline
+        self.splineO = otherSpline
+        
+        self.bMesh = bMesh
+        self.vert0Index = vert0Index
+        self.resolution = resolution
+
+        
+    def Apply(self, worldMatrixA, worldMatrixO):
+        #deltaPar = 1.0 / float(self.resolution - 1)
+        
+        par = 0.0
+        pointA = worldMatrixA * self.splineA.CalcPoint(par)
+        pointO = worldMatrixO * self.splineO.CalcPoint(par)
+        self.bMesh.verts[self.vert0Index].co = pointA
+        self.bMesh.verts[self.vert0Index + 1].co = pointO
+        
+        fltResm1 = float(self.resolution - 1)
+        for i in range(1, self.resolution):
+            par = float(i) / fltResm1
+        
+            pointA = worldMatrixA * self.splineA.CalcPoint(par)
+            pointO = worldMatrixO * self.splineO.CalcPoint(par)
+            self.bMesh.verts[self.vert0Index + 2 * i].co = pointA
+            self.bMesh.verts[self.vert0Index + 2 * i + 1].co = pointO
+
+        
+    def AddFaces(self):
+        currIndexA = self.vert0Index
+        currIndexO = self.vert0Index + 1
+        
+        bmVerts = self.bMesh.verts
+        for i in range(1, self.resolution):
+            nextIndexA = self.vert0Index + 2 * i
+            nextIndexO = nextIndexA + 1
+            
+            self.bMesh.faces.new([bmVerts[currIndexA], bmVerts[currIndexO], bmVerts[nextIndexO], bmVerts[nextIndexA]])
+            
+            currIndexA = nextIndexA
+            currIndexO = nextIndexO
+            
+
+class LoftedSurface:
+    @staticmethod
+    def FromSelection():
+        selObjects = bpy.context.selected_objects
+        if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
+        
+        blenderActiveCurve = bpy.context.active_object
+        blenderOtherCurve = selObjects[0]
+        if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
+        
+        aCurve = Curves.Curve(blenderActiveCurve)
+        oCurve = Curves.Curve(blenderOtherCurve)
+        
+        name = "TODO: autoname"
+        
+        return LoftedSurface(aCurve, oCurve, name)
+    
+
+    def __init__(self, activeCurve, otherCurve, name = "LoftedSurface"):
+        self.curveA = activeCurve
+        self.curveO = otherCurve
+        self.name  = name
+        
+        self.nrSplines = self.curveA.nrSplines
+        if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
+        
+        self.bMesh = bmesh.new()
+        
+        self.splineSurfaces = self.SetupSplineSurfaces()
+        
+        self.Apply()
+        
+        
+    def SetupSplineSurfaces(self):
+        rvSplineSurfaces = []
+        
+        currV0Index = 0
+        for i in range(self.nrSplines):
+            splineA = self.curveA.splines[i]
+            splineO = self.curveO.splines[i]
+            
+            res = splineA.resolution
+            if splineO.resolution < res: res = splineO.resolution
+            
+            for iv in range(2 * res): self.bMesh.verts.new()
+            
+            splSurf = LoftedSplineSurface(splineA, splineO, self.bMesh, currV0Index, res)
+            splSurf.AddFaces()
+            rvSplineSurfaces.append(splSurf)
+            
+            currV0Index += 2 * res
+        
+        return rvSplineSurfaces
+        
+        
+    def Apply(self):
+        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
+        
+        
+    def AddToScene(self):
+        mesh = bpy.data.meshes.new("Mesh" + self.name)
+        
+        self.bMesh.to_mesh(mesh)
+        mesh.update()
+        
+        meshObject = bpy.data.objects.new(self.name, mesh)
+        
+        bpy.context.scene.objects.link(meshObject)
+
+
+
+# active spline is swept over other spline (rail)
+class SweptSplineSurface:
+    def __init__(self, activeSpline, otherSpline, bMesh, vert0Index, resolutionA, resolutionO):
+        self.splineA = activeSpline
+        self.splineO = otherSpline
+        
+        self.bMesh = bMesh
+        self.vert0Index = vert0Index
+        self.resolutionA = resolutionA
+        self.resolutionO = resolutionO
+
+        
+    def Apply(self, worldMatrixA, worldMatrixO):
+        localPointsA = []
+        fltResAm1 = float(self.resolutionA - 1)
+        for i in range(self.resolutionA):
+            par = float(i) / fltResAm1
+            pointA = self.splineA.CalcPoint(par)
+            localPointsA.append(pointA)
+
+        
+        worldPointsO = []
+        localDerivativesO = []
+        fltResOm1 = float(self.resolutionO - 1)
+        for i in range(self.resolutionO):
+            par = float(i) / fltResOm1
+            
+            pointO = self.splineO.CalcPoint(par)
+            worldPointsO.append(worldMatrixO * pointO)
+            
+            derivativeO = self.splineO.CalcDerivative(par)
+            localDerivativesO.append(derivativeO)
+        
+        
+        currWorldMatrixA = worldMatrixA
+        worldMatrixOInv = worldMatrixO.inverted()
+        prevDerivativeO = localDerivativesO[0]
+        for iO in range(self.resolutionO):
+            currDerivativeO = localDerivativesO[iO]
+            localRotMatO = Math.CalcRotationMatrix(prevDerivativeO, currDerivativeO)
+            
+            currLocalAToLocalO = worldMatrixOInv * currWorldMatrixA       
+            worldPointsA = []
+            for iA in range(self.resolutionA):
+                pointALocalToO = currLocalAToLocalO * localPointsA[iA]
+                rotatedPointA = localRotMatO * pointALocalToO
+                worldPointsA.append(worldMatrixO * rotatedPointA)
+                
+            worldOffsetsA = []
+            worldPoint0A = worldPointsA[0]
+            for i in range(self.resolutionA): worldOffsetsA.append(worldPointsA[i] - worldPoint0A)
+            
+            
+            for iA in range(self.resolutionA):
+                iVert = self.vert0Index + (self.resolutionA * iO) + iA
+                currVert = worldPointsO[iO] + worldOffsetsA[iA]
+                self.bMesh.verts[iVert].co = currVert
+                
+            prevDerivativeO = currDerivativeO
+            currWorldMatrixA = worldMatrixO * localRotMatO * currLocalAToLocalO
+
+        
+    def AddFaces(self):
+        bmVerts = self.bMesh.verts
+        
+        for iO in range(self.resolutionO - 1):
+            for iA in range(self.resolutionA - 1):
+                currIndexA1 = self.vert0Index + (self.resolutionA * iO) + iA
+                currIndexA2 = currIndexA1 + 1
+                nextIndexA1 = self.vert0Index + (self.resolutionA * (iO + 1)) + iA
+                nextIndexA2 = nextIndexA1 + 1
+        
+                self.bMesh.faces.new([bmVerts[currIndexA1], bmVerts[currIndexA2], bmVerts[nextIndexA2], bmVerts[nextIndexA1]])
+        
+        
+
+class SweptSurface:
+    @staticmethod
+    def FromSelection():
+        selObjects = bpy.context.selected_objects
+        if len(selObjects) != 2: raise Exception("len(selObjects) != 2") # shouldn't be possible
+        
+        blenderActiveCurve = bpy.context.active_object
+        blenderOtherCurve = selObjects[0]
+        if blenderActiveCurve == blenderOtherCurve: blenderOtherCurve = selObjects[1]
+        
+        aCurve = Curves.Curve(blenderActiveCurve)
+        oCurve = Curves.Curve(blenderOtherCurve)
+        
+        name = "TODO: autoname"
+        
+        return SweptSurface(aCurve, oCurve, name)
+    
+
+    def __init__(self, activeCurve, otherCurve, name = "SweptSurface"):
+        self.curveA = activeCurve
+        self.curveO = otherCurve
+        self.name  = name
+        
+        self.nrSplines = self.curveA.nrSplines
+        if self.curveO.nrSplines < self.nrSplines: self.nrSplines = self.curveO.nrSplines
+        
+        self.bMesh = bmesh.new()
+        
+        self.splineSurfaces = self.SetupSplineSurfaces()
+        
+        self.Apply()
+        
+        
+    def SetupSplineSurfaces(self):
+        rvSplineSurfaces = []
+        
+        currV0Index = 0
+        for i in range(self.nrSplines):
+            splineA = self.curveA.splines[i]
+            splineO = self.curveO.splines[i]
+            
+            resA = splineA.resolution
+            resO = splineO.resolution
+            
+            for iv in range(resA * resO): self.bMesh.verts.new()
+            
+            splSurf = SweptSplineSurface(splineA, splineO, self.bMesh, currV0Index, resA, resO)
+            splSurf.AddFaces()
+            rvSplineSurfaces.append(splSurf)
+            
+            currV0Index += resA * resO
+        
+        return rvSplineSurfaces
+        
+        
+    def Apply(self):
+        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.curveA.worldMatrix, self.curveO.worldMatrix)
+        
+        
+    def AddToScene(self):
+        mesh = bpy.data.meshes.new("Mesh" + self.name)
+        
+        self.bMesh.to_mesh(mesh)
+        mesh.update()
+        
+        meshObject = bpy.data.objects.new(self.name, mesh)
+        
+        bpy.context.scene.objects.link(meshObject)
+        
+
+
+# profileSpline is swept over rail1Spline and scaled/rotated to have its endpoint on rail2Spline
+class BirailedSplineSurface:
+    def __init__(self, rail1Spline, rail2Spline, profileSpline, bMesh, vert0Index, resolutionRails, resolutionProfile):
+        self.rail1Spline = rail1Spline
+        self.rail2Spline = rail2Spline
+        self.profileSpline = profileSpline
+        
+        self.bMesh = bMesh
+        self.vert0Index = vert0Index
+        self.resolutionRails = resolutionRails
+        self.resolutionProfile = resolutionProfile
+
+        
+    def Apply(self, worldMatrixRail1, worldMatrixRail2, worldMatrixProfile):
+        localPointsProfile = []
+        fltResProfilem1 = float(self.resolutionProfile - 1)
+        for i in range(self.resolutionProfile):
+            par = float(i) / fltResProfilem1
+            pointProfile = self.profileSpline.CalcPoint(par)
+            localPointsProfile.append(pointProfile)
+
+        
+        worldPointsRail1 = []
+        localDerivativesRail1 = []
+        worldPointsRail2 = []
+        fltResRailsm1 = float(self.resolutionRails - 1)
+        for i in range(self.resolutionRails):
+            par = float(i) / fltResRailsm1
+            
+            pointRail1 = self.rail1Spline.CalcPoint(par)
+            worldPointsRail1.append(worldMatrixRail1 * pointRail1)
+            
+            derivativeRail1 = self.rail1Spline.CalcDerivative(par)
+            localDerivativesRail1.append(derivativeRail1)
+            
+            pointRail2 = self.rail2Spline.CalcPoint(par)
+            worldPointsRail2.append(worldMatrixRail2 * pointRail2)
+        
+        
+        currWorldMatrixProfile = worldMatrixProfile
+        worldMatrixRail1Inv = worldMatrixRail1.inverted()
+        prevDerivativeRail1 = localDerivativesRail1[0]
+        for iRail in range(self.resolutionRails):
+            currDerivativeRail1 = localDerivativesRail1[iRail]
+            localRotMatRail1 = Math.CalcRotationMatrix(prevDerivativeRail1, currDerivativeRail1)
+            
+            currLocalProfileToLocalRail1 = worldMatrixRail1Inv * currWorldMatrixProfile       
+            worldPointsProfileRail1 = []
+            for iProfile in range(self.resolutionProfile):
+                pointProfileLocalToRail1 = currLocalProfileToLocalRail1 * localPointsProfile[iProfile]
+                rotatedPointProfile = localRotMatRail1 * pointProfileLocalToRail1
+                worldPointsProfileRail1.append(worldMatrixRail1 * rotatedPointProfile)
+                
+            worldOffsetsProfileRail1 = []
+            worldPoint0ProfileRail1 = worldPointsProfileRail1[0]
+            for iProfile in range(self.resolutionProfile): worldOffsetsProfileRail1.append(worldPointsProfileRail1[iProfile] - worldPoint0ProfileRail1)
+                
+            worldStartPointProfileRail1 = worldPointsRail1[iRail]
+            worldEndPointProfileRail1 = worldStartPointProfileRail1 + worldOffsetsProfileRail1[-1]
+            v3From = worldEndPointProfileRail1 - worldStartPointProfileRail1
+            v3To = worldPointsRail2[iRail] - worldStartPointProfileRail1
+            scaleFactorRail2 = v3To.magnitude / v3From.magnitude
+            rotMatRail2 = Math.CalcRotationMatrix(v3From, v3To)
+            
+            worldOffsetsProfileRail2 = []
+            for iProfile in range(self.resolutionProfile):
+                offsetProfileRail1 = worldOffsetsProfileRail1[iProfile]
+                worldOffsetsProfileRail2.append(rotMatRail2 * (offsetProfileRail1 * scaleFactorRail2))
+            
+            
+            for iProfile in range(self.resolutionProfile):
+                iVert = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
+                currVert = worldPointsRail1[iRail] + worldOffsetsProfileRail2[iProfile]
+                self.bMesh.verts[iVert].co = currVert
+                
+            prevDerivativeRail1 = currDerivativeRail1
+            currWorldMatrixProfile = worldMatrixRail1 * localRotMatRail1 * currLocalProfileToLocalRail1
+
+        
+    def AddFaces(self):
+        bmVerts = self.bMesh.verts
+        
+        for iRail in range(self.resolutionRails - 1):
+            for iProfile in range(self.resolutionProfile - 1):
+                currIndex1 = self.vert0Index + (self.resolutionProfile * iRail) + iProfile
+                currIndex2 = currIndex1 + 1
+                nextIndex1 = self.vert0Index + (self.resolutionProfile * (iRail + 1)) + iProfile
+                nextIndex2 = nextIndex1 + 1
+        
+                self.bMesh.faces.new([bmVerts[currIndex1], bmVerts[currIndex2], bmVerts[nextIndex2], bmVerts[nextIndex1]])
+        
+        
+
+class BirailedSurface:
+    @staticmethod
+    def FromSelection():
+        nrSelectedObjects = bpy.context.scene.curvetools.NrSelectedObjects
+        if nrSelectedObjects != 3: raise Exception("nrSelectedObjects != 3") # shouldn't be possible
+        
+        
+        selectedObjects = bpy.context.scene.curvetools.SelectedObjects
+        selectedObjectValues = selectedObjects.values()
+
+        curveName = selectedObjectValues[0].name
+        rail1BlenderCurve = None
+        try: rail1BlenderCurve = bpy.data.objects[curveName]
+        except: rail1BlenderCurve = None
+        if rail1BlenderCurve is None: raise Exception("rail1BlenderCurve is None")
+
+        curveName = selectedObjectValues[1].name
+        rail2BlenderCurve = None
+        try: rail2BlenderCurve = bpy.data.objects[curveName]
+        except: rail2BlenderCurve = None
+        if rail2BlenderCurve is None: raise Exception("rail2BlenderCurve is None")
+
+        curveName = selectedObjectValues[2].name
+        profileBlenderCurve = None
+        try: profileBlenderCurve = bpy.data.objects[curveName]
+        except: profileBlenderCurve = None
+        if profileBlenderCurve is None: raise Exception("profileBlenderCurve is None")
+        
+        
+        rail1Curve = Curves.Curve(rail1BlenderCurve)
+        rail2Curve = Curves.Curve(rail2BlenderCurve)
+        profileCurve = Curves.Curve(profileBlenderCurve)
+        
+        name = "TODO: autoname"
+        
+        return BirailedSurface(rail1Curve, rail2Curve, profileCurve, name)
+    
+
+    def __init__(self, rail1Curve, rail2Curve, profileCurve, name = "BirailedSurface"):
+        self.rail1Curve = rail1Curve
+        self.rail2Curve = rail2Curve
+        self.profileCurve = profileCurve
+        self.name  = name
+        
+        self.nrSplines = self.rail1Curve.nrSplines
+        if self.rail2Curve.nrSplines < self.nrSplines: self.nrSplines = self.rail2Curve.nrSplines
+        if self.profileCurve.nrSplines < self.nrSplines: self.nrSplines = self.profileCurve.nrSplines
+        
+        self.bMesh = bmesh.new()
+        
+        self.splineSurfaces = self.SetupSplineSurfaces()
+        
+        self.Apply()
+        
+        
+    def SetupSplineSurfaces(self):
+        rvSplineSurfaces = []
+        
+        currV0Index = 0
+        for i in range(self.nrSplines):
+            splineRail1 = self.rail1Curve.splines[i]
+            splineRail2 = self.rail2Curve.splines[i]
+            splineProfile = self.profileCurve.splines[i]
+            
+            resProfile = splineProfile.resolution
+            resRails = splineRail1.resolution
+            if splineRail2.resolution < resRails: resRails = splineRail2.resolution
+            
+            for iv in range(resProfile * resRails): self.bMesh.verts.new()
+            
+            splSurf = BirailedSplineSurface(splineRail1, splineRail2, splineProfile, self.bMesh, currV0Index, resRails, resProfile)
+            splSurf.AddFaces()
+            rvSplineSurfaces.append(splSurf)
+            
+            currV0Index += resProfile * resRails
+        
+        return rvSplineSurfaces
+        
+        
+    def Apply(self):
+        for splineSurface in self.splineSurfaces: splineSurface.Apply(self.rail1Curve.worldMatrix, self.rail2Curve.worldMatrix, self.profileCurve.worldMatrix)
+        
+        
+    def AddToScene(self):
+        mesh = bpy.data.meshes.new("Mesh" + self.name)
+        
+        self.bMesh.to_mesh(mesh)
+        mesh.update()
+        
+        meshObject = bpy.data.objects.new(self.name, mesh)
+        
+        bpy.context.scene.objects.link(meshObject)
+
+    
\ No newline at end of file
diff --git a/curve_tools/Util.py b/curve_tools/Util.py
new file mode 100644 (file)
index 0000000..731460c
--- /dev/null
@@ -0,0 +1,121 @@
+import bpy
+from mathutils import *
+
+
+def GetSelectedCurves():
+    rvList = []
+    
+    for obj in bpy.context.selected_objects:
+        if obj.type == "CURVE": rvList.append(obj)
+    
+    return rvList
+
+
+def Selected1Curve():
+    if len(GetSelectedCurves()) == 1:
+        return (bpy.context.active_object.type == "CURVE")
+    
+    return False
+
+
+def Selected1SingleSplineCurve():
+    if Selected1Curve():
+        return (len(bpy.context.active_object.data.splines) == 1)
+    
+    return False
+
+
+def Selected2Curves():
+    if len(GetSelectedCurves()) == 2:
+        return (bpy.context.active_object.type == "CURVE")
+    
+    return False
+
+
+def Selected3Curves():
+    if len(GetSelectedCurves()) == 3:
+        return (bpy.context.active_object.type == "CURVE")
+    
+    return False
+
+
+def Selected1OrMoreCurves():
+    if len(GetSelectedCurves()) > 0:
+        return (bpy.context.active_object.type == "CURVE")
+    
+    return False
+    
+
+def GetToolsRegion():
+    for area in bpy.context.screen.areas:
+        if area.type == 'VIEW_3D':
+            for region in area.regions:
+                if region.type == 'TOOLS': return region
+            
+    return None
+    
+
+def GetFirstRegionView3D():
+    for area in bpy.context.screen.areas:
+        if area.type == 'VIEW_3D':
+            return area.spaces[0].region_3d
+            
+    return None
+    
+    
+def LogFirstRegionView3D():
+    print("LogFirstRegionView3D()")
+    regionView3D = GetFirstRegionView3D()
+    if regionView3D is None:
+        print("--", "ERROR:", "regionView3D is None")
+        return
+        
+    print("--", "view_matrix:")
+    print("--", "--", regionView3D.view_matrix)
+    print("--", "view_location:")
+    print("--", "--", regionView3D.view_location)
+    
+    
+class Intersection:
+    # listIP: list of BezierSplineIntersectionPoint
+    # return: list of splines
+    @staticmethod
+    def GetBezierSplines(listIP):
+        rvList = []
+        
+        for ip in listIP:
+            if not (ip.spline in rvList): rvList.append(ip.spline)
+        
+        return rvList
+
+        
+    # listIP: list of BezierSplineIntersectionPoint
+    # return: list of segments
+    @staticmethod
+    def GetBezierSegments(listIP, spline):
+        rvList = []
+        
+        for ip in listIP:
+            if not ip.spline is spline: continue
+            
+            segIP = ip.bezierSegmentIntersectionPoint
+            if not (segIP.segment in rvList): rvList.append(segIP.segment)
+        
+        return rvList
+
+        
+    # listIP: list of BezierSplineIntersectionPoint
+    # return: list of floats (not necessarily ordered)
+    @staticmethod
+    def GetBezierSegmentParameters(listIP, segment):
+        rvList = []
+        
+        for ip in listIP:
+            segIP = ip.bezierSegmentIntersectionPoint
+            if not segIP.segment is segment: continue
+            
+            rvList.append(segIP.parameter)
+        
+        return rvList
+
+   
\ No newline at end of file
diff --git a/curve_tools/__init__.py b/curve_tools/__init__.py
new file mode 100644 (file)
index 0000000..97c11c1
--- /dev/null
@@ -0,0 +1,123 @@
+bl_info = {
+    "name": "Curve Tools 2",
+    "description": "Adds some functionality for bezier/nurbs curve/surface modeling",
+    "author": "Mackraken, guy lateur",
+    "version": (0, 2, 0),
+    "blender": (2, 71, 0),
+       "location": "View3D > Tool Shelf > Addons Tab",
+    "warning": "WIP",
+    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
+        "Scripts/Curve/Curve_Tools",
+    "tracker_url": "https://developer.blender.org/T27720",
+    "category": "Add Curve"}
+    
+    
+import bpy
+from bpy.props import *
+
+from . import Properties
+from . import Operators
+from . import Panel
+
+
+    
+def UpdateDummy(object, context):
+    pass
+        
+class CurveTools2Settings(bpy.types.PropertyGroup):
+    # selection
+    SelectedObjects = CollectionProperty(type = Properties.CurveTools2SelectedObject)
+    NrSelectedObjects = IntProperty(name = "NrSelectedObjects", default = 0, description = "Number of selected objects", update = UpdateDummy)
+    # NrSelectedObjects = IntProperty(name = "NrSelectedObjects", default = 0, description = "Number of selected objects")
+
+    # curve
+    CurveLength = FloatProperty(name = "CurveLength", default = 0.0, precision = 6)
+    
+    
+    #splines
+    SplineResolution = IntProperty(name = "SplineResolution", default = 64, min = 2, max = 1024, soft_min = 2, description = "Spline resolution will be set to this value")
+    
+    SplineRemoveLength = FloatProperty(name = "SplineRemoveLength", default = 0.001, precision = 6, description = "Splines shorter than this threshold length will be removed")
+    SplineJoinDistance = FloatProperty(name = "SplineJoinDistance", default = 0.001, precision = 6, description = "Splines with starting/ending points closer to each other than this threshold distance will be joined")
+    SplineJoinStartEnd = BoolProperty(name = "SplineJoinStartEnd", default = False, description = "Only join splines at the starting point of one and the ending point of the other")
+
+    splineJoinModeItems = (('At midpoint', 'At midpoint', 'Join splines at midpoint of neighbouring points'), ('Insert segment', 'Insert segment', 'Insert segment between neighbouring points'))
+    SplineJoinMode = EnumProperty(items = splineJoinModeItems, name = "SplineJoinMode", default = 'At midpoint', description = "Determines how the splines will be joined")
+
+    
+    # curve intersection
+    LimitDistance = FloatProperty(name = "LimitDistance", default = 0.0001, precision = 6, description = "Displays the result of the curve length calculation")
+
+    intAlgorithmItems = (('3D', '3D', 'Detect where curves intersect in 3D'), ('From View', 'From View', 'Detect where curves intersect in the RegionView3D'))
+    IntersectCurvesAlgorithm = EnumProperty(items = intAlgorithmItems, name = "IntersectCurvesAlgorithm", description = "Determines how the intersection points will be detected", default = '3D')
+
+    intModeItems = (('Insert', 'Insert', 'Insert points into the existing spline(s)'), ('Split', 'Split', 'Split the existing spline(s) into 2'), ('Empty', 'Empty', 'Add empty at intersections'))
+    IntersectCurvesMode = EnumProperty(items = intModeItems, name = "IntersectCurvesMode", description = "Determines what happens at the intersection points", default = 'Split')
+
+    intAffectItems = (('Both', 'Both', 'Insert points into both curves'), ('Active', 'Active', 'Insert points into active curve only'), ('Other', 'Other', 'Insert points into other curve only'))
+    IntersectCurvesAffect = EnumProperty(items = intAffectItems, name = "IntersectCurvesAffect", description = "Determines which of the selected curves will be affected by the operation", default = 'Both')
+    
+
+def register():
+    bpy.utils.register_class(Properties.CurveTools2SelectedObject)
+    
+    bpy.utils.register_class(CurveTools2Settings)
+    bpy.types.Scene.curvetools = bpy.props.PointerProperty(type=CurveTools2Settings)
+
+    bpy.utils.register_class(Operators.OperatorSelectionInfo)
+    #bpy.utils.register_class(Properties.CurveTools2SelectedObjectHeader)
+
+    bpy.utils.register_class(Operators.OperatorCurveInfo)
+    bpy.utils.register_class(Operators.OperatorCurveLength)
+    bpy.utils.register_class(Operators.OperatorSplinesInfo)
+    bpy.utils.register_class(Operators.OperatorSegmentsInfo)
+    bpy.utils.register_class(Operators.OperatorOriginToSpline0Start)
+    
+    bpy.utils.register_class(Operators.OperatorIntersectCurves)
+    bpy.utils.register_class(Operators.OperatorLoftCurves)
+    bpy.utils.register_class(Operators.OperatorSweepCurves)
+    
+    bpy.utils.register_class(Operators.OperatorBirail)
+    
+    bpy.utils.register_class(Operators.OperatorSplinesSetResolution)
+    bpy.utils.register_class(Operators.OperatorSplinesRemoveZeroSegment)
+    bpy.utils.register_class(Operators.OperatorSplinesRemoveShort)
+    bpy.utils.register_class(Operators.OperatorSplinesJoinNeighbouring)
+    
+    # bpy.app.handlers.scene_update_pre.append(SceneUpdatePreHandler)
+    
+    bpy.utils.register_class(Panel.Panel)
+
+    
+def unregister():
+    bpy.utils.unregister_class(Panel.Panel)
+    
+    # bpy.app.handlers.scene_update_pre.remove(SceneUpdatePreHandler)
+    
+    bpy.utils.unregister_class(Operators.OperatorSplinesJoinNeighbouring)
+    bpy.utils.unregister_class(Operators.OperatorSplinesRemoveShort)
+    bpy.utils.unregister_class(Operators.OperatorSplinesRemoveZeroSegment)
+    bpy.utils.unregister_class(Operators.OperatorSplinesSetResolution)
+    
+    bpy.utils.unregister_class(Operators.OperatorBirail)
+    
+    bpy.utils.unregister_class(Operators.OperatorSweepCurves)
+    bpy.utils.unregister_class(Operators.OperatorLoftCurves)
+    bpy.utils.unregister_class(Operators.OperatorIntersectCurves)
+    
+    bpy.utils.unregister_class(Operators.OperatorOriginToSpline0Start)
+    bpy.utils.unregister_class(Operators.OperatorSegmentsInfo)
+    bpy.utils.unregister_class(Operators.OperatorSplinesInfo)
+    bpy.utils.unregister_class(Operators.OperatorCurveLength)
+    bpy.utils.unregister_class(Operators.OperatorCurveInfo)
+
+    #bpy.utils.unregister_class(Operators.CurveTools2SelectedObjectHeader)
+    bpy.utils.unregister_class(Operators.OperatorSelectionInfo)
+
+    bpy.utils.unregister_class(CurveTools2Settings)
+
+    bpy.utils.unregister_class(Properties.CurveTools2SelectedObject)
+
+        
+if __name__ == "__main__":
+    register()