# #####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, 5, 9), "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://projects.blender.org/tracker/index.php?"\ "func=detail&aid=27720", "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=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 i0: 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)=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()