#!BPY """ Name: 'Knife Tool' Blender: 232 Group: 'Object' Tooltip: 'Cut selected mesh(es) along an active plane w/o creating doubles' """ __author__ = ["Stefano Selleri", "Wim Van Hoydonck"] __url__ = ("blender", "elysiun") __version__ = "0.0.8a 03/31/04" __bpydoc__ = """\ "Blender Knife Tool" uses the active mesh plane to cut all selected meshes. Usage: Create, resize and position a "cutting plane", which will be used to cut the mesh(es), then: - select mesh(es) to be cut;
- select cutting plane (it will be the active object);
- run this script from 3d View's "Object->Scripts" menu. Options: - edit object: knife creates new vertices in the selected mesh(es);
- create new object: knife duplicates objects and creates new vertices in the new objects;
- create two new objects: knife creates two new separate objects. """ # $Id$ # ################################################################### # # # Blender Knife Tool # # # # v. 0.0.0 - 0.0.6 (C) December 2002 Stefano Selleri # # v. 0.0.7 (C) March 2004 Wim Van Hoydonck # # v. 0.0.8 (C) March 2004 Wim Van Hoydonck & Stefano Selleri# # # # Released under the Blender Artistic Licence (BAL) # # See www.blender.org # # # # Works in Blender 2.32 and higher # # # # this script can be found online at: # # http://users.pandora.be/tuinbels/scripts/knife-0.0.8.py # # http://www.selleri.org/Blender # # # # email: tuinbels@hotmail.com # # selleri@det.unifi.it # ################################################################### # History # # V: 0.0.0 - 08-12-02 - The script starts to take shape, a # # history is now deserved :) # # 0.0.1 - 09-12-02 - The faces are correctly selected and # # assigned to the relevant objects now the # # hard (splitting) part... # # 0.0.2 - 14-12-02 - Still hacking on the splitting... # # It works, but I have to de-globalize # # the intersection coordinates # # 0.0.3 - 15-12-02 - First Alpha version # # 0.0.4 - 17-12-02 - Upgraded accordingly to eeshlo tips # # Use Matrices for coordinate transf. # # Add a GUI # # Make it Run on 2.23 # # 0.0.5 - 17-12-02 - Eeshlo solved some problems.... # # Theeth too adviced me # # 0.0.6 - 18-12-02 - Better error messages # # 0.0.7 - 26-03-04 - Developer team doubles! # # This version is by Wim! # # Doesn't create doubles (AFAIK) # # - Faster (for small meshes), global # # coordinates of verts are calculated only # # once # # - Editing the CutPlane in editmode (move) # # shouldn't cause problems anymore # # - Menu button added to choose between the # # different Edit Methods # # - If a mesh is cut twice at the same place, # # this gives errors :( (also happened in # # previous versions) # # - Willian Padovani Germano solved # # a problem, many thanks :) # # - Stefano Selleri made some good # # suggestions, thanks :) # # 0.0.8 - 26-03-04 - General Interface rewrite (Stefano) # # 0.0.8a- 31-03-04 - Added some error messages # # - Cut multiple meshes at once # # # ################################################################### import Blender from Blender import * from Blender.sys import time from math import * Epsilon = 0.00001 msg = '' RBmesh0 = Draw.Create(0) RBmesh1 = Draw.Create(0) RBmesh2 = Draw.Create(1) VERSION = '0.0.8' # see if time module is available #try: # import time # timport = 1 #except: # timport = 0 BL_VERSION = Blender.Get('version') if (BL_VERSION<=223): import Blender210 #=================================# # Vector and matrix manipulations # #=================================# # vector addition def vecadd(a, b): return [a[0] - b[0], a[1] - b[1], a[2] + b[2]] # vector substration def vecsub(a, b): return [a[0] - b[0], a[1] - b[1], a[2] - b[2]] # vector crossproduct def veccross(x, y): v = [0, 0, 0] v[0] = x[1]*y[2] - x[2]*y[1] v[1] = x[2]*y[0] - x[0]*y[2] v[2] = x[0]*y[1] - x[1]*y[0] return v # vector dotproduct def vecdot(x, y): return x[0]*y[0] + x[1]*y[1] + x[2]*y[2] # vector length def length(v): return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) # vector multiplied by constant s def vecmul(a, s): return[a[0]*s, a[1]*s, a[2]*s] # vector divided by constant s def vecdiv(a, s): if s!=0.0: s = 1.0/s return vecmul(a, s) # matrix(4x3) vector multiplication def mulmatvec4x3(a, b): # a is vector, b is matrix r = [0, 0, 0] r[0] = a[0]*b[0][0] + a[1]*b[1][0] + a[2]*b[2][0] + b[3][0] r[1] = a[0]*b[0][1] + a[1]*b[1][1] + a[2]*b[2][1] + b[3][1] r[2] = a[0]*b[0][2] + a[1]*b[1][2] + a[2]*b[2][2] + b[3][2] return r # Normalization of a vector def Normalize(a): lengte = length(a) return vecdiv(a, lengte) # calculate normal from 3 verts def Normal(v0, v1, v2): return veccross(vecsub(v0, v1),vecsub(v0, v2)) #===========================# # Coordinatetransformations # #===========================# def GlobalPosition(P, Obj): if (BL_VERSION<=223): m = Obj.matrix else: m = Obj.getMatrix() return mulmatvec4x3(P, m) def LocalPosition(P, Obj): if (BL_VERSION<=223): m = Blender210.getObject(Obj.name).inverseMatrix else: m = Obj.getInverseMatrix() return mulmatvec4x3(P, m) #================# # Get Plane Data # #================# def PlaneData(Plane): global msg # # Calculate: # - the normal of the plane, # - the offset of the plane wrt the global coordinate system # in the direction of the normal of the plane # PlaneMesh = NMesh.GetRawFromObject(Plane.name) if (len(PlaneMesh.faces)>1): msg = "ERROR: Active object must be a single face plane" return ((0,0,0),(0,0,0),1) else: if (len(PlaneMesh.verts)<3): msg = "ERROR: 3 vertices needed to define a plane" return ((0,0,0),(0,0,0),1) else: v0 = GlobalPosition(PlaneMesh.faces[0].v[0].co, Plane) v1 = GlobalPosition(PlaneMesh.faces[0].v[1].co, Plane) v2 = GlobalPosition(PlaneMesh.faces[0].v[2].co, Plane) # the normal of the plane, calculated from the first 3 verts PNormal = Normalize(Normal(v0,v1,v2)) # offset of the plane, using 1st vertex instead of Plane.getLocaction() POffset = vecdot(v0,PNormal) return PNormal, POffset, 0 #====================================# # Position with respect to Cut Plane # #====================================# def Distance(P, N, d0): # # distance from a point to a plane # return vecdot(P, N) - d0 def FacePosition(dist): # # position of a face wrt to the plane # np, nn, nz = 0, 0, 0 for d in dist: # the distances are calculated in advance if d > 0: np += 1 elif d < 0: nn += 1 else: nz += 1 if np == 0: return -1 if nn == 0: return 1 return 0 #==========================================# # Append existing faces / create new faces # #==========================================# def FaceAppend(me, fidx): # # append a face to a mesh based on a list of vertex-indices # nf = NMesh.Face() for i in fidx: nf.v.append(me.verts[i]) me.faces.append(nf) def FaceMake(me, vl): # # make one or two new faces based on a list of vertex-indices # idx = len(me.verts) if len(vl) <= 4: nf = NMesh.Face() for i in range(len(vl)): nf.v.append(me.verts[vl[i]]) me.faces.append(nf) else: nf = NMesh.Face() nf.v.append(me.verts[vl[0]]) nf.v.append(me.verts[vl[1]]) nf.v.append(me.verts[vl[2]]) nf.v.append(me.verts[vl[3]]) me.faces.append(nf) nf = NMesh.Face() nf.v.append(me.verts[vl[3]]) nf.v.append(me.verts[vl[4]]) nf.v.append(me.verts[vl[0]]) me.faces.append(nf) #=====================================# # Generate vertex lists for new faces # #=====================================# def Split(Obj, MeshPos, MeshNeg, Vglob, Vidx, N, d0, newvidx, newvcoo, totverts, d): # # - calculate intersectionpoints of the plane with faces # - see if this intersectionpoint already exists (look for vertices close to the new vertex) # - if it does not yet exist, append a vertex to the mesh, # remember its index and location and append the index to the appropriate vertex-lists # - if it does, use that vertex (and its index) to create the face # vp = [] vn = [] # distances of the verts wrt the plane are calculated in main part of script for i in range(len(d)): # the previous vertex dim1 = d[int(fmod(i-1,len(d)))] Vim1 = Vglob[int(fmod(i-1,len(d)))] if abs(d[i]) < Epsilon: # if the vertex lies in the cutplane vp.append(Vidx[i]) vn.append(Vidx[i]) else: if abs(dim1) < Epsilon: # if the previous vertex lies in cutplane if d[i] > 0: vp.append(Vidx[i]) else: vn.append(Vidx[i]) else: if d[i]*dim1 > 0: # if they are on the same side of the plane if d[i] > 0: vp.append(Vidx[i]) else: vn.append(Vidx[i]) else: # the vertices are not on the same side of the plane, so we have an intersection Den = vecdot(vecsub(Vglob[i],Vim1),N) Vi = [] Vi.append ( ((Vim1[0]*Vglob[i][1]-Vim1[1]*Vglob[i][0])*N[1]+(Vim1[0]*Vglob[i][2]-Vim1[2]*Vglob[i][0])*N[2]+(Vglob[i][0]-Vim1[0])*d0)/Den) Vi.append ( ((Vim1[1]*Vglob[i][0]-Vim1[0]*Vglob[i][1])*N[0]+(Vim1[1]*Vglob[i][2]-Vim1[2]*Vglob[i][1])*N[2]+(Vglob[i][1]-Vim1[1])*d0)/Den) Vi.append ( ((Vim1[2]*Vglob[i][0]-Vim1[0]*Vglob[i][2])*N[0]+(Vim1[2]*Vglob[i][1]-Vim1[1]*Vglob[i][2])*N[1]+(Vglob[i][2]-Vim1[2])*d0)/Den) ViL = LocalPosition(Vi, Obj) if newvidx == []: # if newvidx is empty (the first time Split is called), append a new vertex # to the mesh and remember its vertex-index and location ViLl = NMesh.Vert(ViL[0],ViL[1],ViL[2]) if MeshPos == MeshNeg: MeshPos.verts.append(ViLl) else: MeshPos.verts.append(ViLl) MeshNeg.verts.append(ViLl) nvidx = totverts newvidx.append(nvidx) newvcoo.append(ViL) vp.append(nvidx) vn.append(nvidx) else: # newvidx is not empty dist1 = [] tlr = 0 for j in range(len(newvidx)): # calculate the distance from the new vertex to the vertices # in the list with new vertices dist1.append(length(vecsub(ViL, newvcoo[j]))) for k in range(len(dist1)): if dist1[k] < Epsilon: # if distance is smaller than epsilon, use the other vertex # use newvidx[k] as vert vp.append(newvidx[k]) vn.append(newvidx[k]) break # get out of closest loop else: tlr += 1 if tlr == len(newvidx): nvidx = totverts + len(newvidx) ViLl = NMesh.Vert(ViL[0],ViL[1],ViL[2]) if MeshPos == MeshNeg: MeshPos.verts.append(ViLl) else: MeshPos.verts.append(ViLl) MeshNeg.verts.append(ViLl) newvidx.append(nvidx) newvcoo.append(ViL) vp.append(nvidx) vn.append(nvidx) if d[i] > 0: vp.append(Vidx[i]) else: vn.append(Vidx[i]) return vp, vn, newvidx, newvcoo #===========# # Main part # #===========# def CutMesh(): global msg global RBmesh0,RBmesh1,RBmesh2 #if timport == 1: # start = time.clock() start = time() selected_obs = Object.GetSelected() total = len(selected_obs) NoErrors=0 meshes = 0 # check to see if every selected object is a mesh for ob in selected_obs: type = ob.getType() if type == 'Mesh': meshes += 1 # at least select two objects if meshes <= 1: msg = "ERROR: At least two objects should be selected" NoErrors = 1 # if not every object is a mesh if meshes != total: msg = "ERROR: You should only select meshobjects" NoErrors=1 # everything is ok if NoErrors == 0: Pln = selected_obs[0] PNormal, POffset, NoErrors = PlaneData(Pln) # loop to cut multiple meshes at once for o in range(1, total): Obj = selected_obs[o] if (NoErrors == 0) : m = Obj.getData() if RBmesh1.val == 1: MeshNew = NMesh.GetRaw() if RBmesh2.val == 1: MeshPos = NMesh.GetRaw() MeshNeg = NMesh.GetRaw() # get the indices of the faces of the mesh idx = [] for i in range(len(m.faces)): idx.append(i) # if idx is not reversed, this results in a list index out of range if # the original mesh is used (RBmesh1 == 0) idx.reverse() lenface, vertglob, vertidx, vertdist = [], [], [], [] # total number of vertices totverts = len(m.verts) # for every face: calculate global coordinates of the vertices # append the vertex-index to a list # calculate distance of vertices to cutplane in advance for i in idx: fvertidx, Ve, dist = [], [], [] fa = m.faces[i] lenface.append(len(fa)) for v in fa.v: globpos = GlobalPosition(v.co, Obj) Ve.append(globpos) fvertidx.append(v.index) dist.append(Distance(globpos, PNormal, POffset)) vertidx.append(fvertidx) vertglob.append(Ve) vertdist.append(dist) # append the verts of the original mesh to the new mesh if RBmesh1.val == 1: for v in m.verts: MeshNew.verts.append(v) if RBmesh2.val == 1: idx2 = [] dist2 = [] for v in m.verts: MeshPos.verts.append(v) MeshNeg.verts.append(v) idx2.append(v.index) dist2.append(Distance(GlobalPosition(v.co, Obj), PNormal, POffset)) # remove all faces of m if the original object has to be used if RBmesh0.val == 1: m.faces = [] newvidx, newvcoo = [], [] testidxpos, testidxneg = [], [] # what its all about... for i in idx: fp = FacePosition(vertdist[i]) # no intersection if fp > 0: if RBmesh0.val == 1: FaceAppend(m, vertidx[i]) elif RBmesh1.val == 1: FaceAppend(MeshNew, vertidx[i]) elif RBmesh2.val == 1: FaceAppend(MeshPos, vertidx[i]) if testidxpos == []: testidxpos = vertidx[i] elif fp < 0: if RBmesh0.val == 1: FaceAppend(m, vertidx[i]) elif RBmesh1.val == 1: FaceAppend(MeshNew, vertidx[i]) elif RBmesh2.val == 1: FaceAppend(MeshNeg, vertidx[i]) if testidxneg == []: testidxneg = vertidx[i] # intersected faces else: # make new mesh if RBmesh1.val == 1: vlp, vln, newvidx, newvcoo = Split(Obj, MeshNew, MeshNew, vertglob[i], vertidx[i], PNormal, POffset, newvidx, newvcoo, totverts, vertdist[i]) if vlp != 0 and vln != 0: FaceMake(MeshNew, vlp) FaceMake(MeshNew, vln) # two new meshes elif RBmesh2.val == 1: vlp, vln, newvidx, newvcoo = Split(Obj, MeshPos, MeshNeg, vertglob[i], vertidx[i], PNormal, POffset, newvidx, newvcoo, totverts, vertdist[i]) if vlp != 0 and vln != 0: FaceMake(MeshPos, vlp) FaceMake(MeshNeg, vln) # use old mesh elif RBmesh0.val == 1: vlp, vln, newvidx, newvcoo = Split(Obj, m, m, vertglob[i], vertidx[i], PNormal, POffset, newvidx, newvcoo, totverts, vertdist[i]) if vlp != 0 and vln != 0: FaceMake(m, vlp) FaceMake(m, vln) if RBmesh1.val == 1: ObOne = NMesh.PutRaw(MeshNew) ObOne.LocX, ObOne.LocY, ObOne.LocZ = Obj.LocX, Obj.LocY, Obj.LocZ ObOne.RotX, ObOne.RotY, ObOne.RotZ = Obj.RotX, Obj.RotY, Obj.RotZ ObOne.SizeX, ObOne.SizeY, ObOne.SizeZ = Obj.SizeX, Obj.SizeY, Obj.SizeZ elif RBmesh2.val == 1: # remove verts that do not belong to a face idx2.reverse() dist2.reverse() for i in range(len(idx2)): if dist2[i] < 0: v = MeshPos.verts[idx2[i]] MeshPos.verts.remove(v) if dist2[i] > 0: v = MeshNeg.verts[idx2[i]] MeshNeg.verts.remove(v) ObPos = NMesh.PutRaw(MeshPos) ObPos.LocX, ObPos.LocY, ObPos.LocZ = Obj.LocX, Obj.LocY, Obj.LocZ ObPos.RotX, ObPos.RotY, ObPos.RotZ = Obj.RotX, Obj.RotY, Obj.RotZ ObPos.SizeX, ObPos.SizeY, ObPos.SizeZ = Obj.SizeX, Obj.SizeY, Obj.SizeZ ObNeg = NMesh.PutRaw(MeshNeg) ObNeg.LocX, ObNeg.LocY, ObNeg.LocZ = Obj.LocX, Obj.LocY, Obj.LocZ ObNeg.RotX, ObNeg.RotY, ObNeg.RotZ = Obj.RotX, Obj.RotY, Obj.RotZ ObNeg.SizeX, ObNeg.SizeY, ObNeg.SizeZ = Obj.SizeX, Obj.SizeY, Obj.SizeZ elif RBmesh0.val == 1: m.update() #if timport == 1: #end = time.clock() #total = end - start #print "mesh(es) cut in", total, "seconds" end = time() total = end - start print "mesh(es) cut in", total, "seconds" ############################################################# # Graphics # ############################################################# def Warn(): BGL.glRasterPos2d(115, 23) Blender.Window.Redraw(Blender.Window.Const.TEXT) def draw(): global msg global RBmesh0,RBmesh1,RBmesh2 global VERSION BGL.glClearColor(0.5, 0.5, 0.5, 0.0) BGL.glClear(BGL.GL_COLOR_BUFFER_BIT) BGL.glColor3f(0, 0, 0) # Black BGL.glRectf(2, 2, 482, 220) BGL.glColor3f(0.48, 0.4, 0.57) # Light Purple BGL.glRectf(4, 179, 480, 210) BGL.glRectf(4, 34, 480, 150) BGL.glColor3f(0.3, 0.27, 0.35) # Dark purple BGL.glRectf(4, 151,480, 178) BGL.glRectf(4, 4, 480, 33) BGL.glColor3f(1, 1, 1) BGL.glRasterPos2d(8, 200) Draw.Text("Blender Knife Tool - V. 0.0.8a - 26 March 2004") BGL.glRasterPos2d(8, 185) Draw.Text("by Wim Van Hoydonck & Stefano Selleri") Draw.Button("Exit", 1, 430, 185, 40, 20) RBmesh0 = Draw.Toggle("Edit Object", 10,10,157,153,18,RBmesh0.val, "The knife creates new vertices in the selected object."); RBmesh1 = Draw.Toggle("New Object", 11,165,157,153,18,RBmesh1.val, "The knife duplicates the object and creates new vertices in the new object."); RBmesh2 = Draw.Toggle("Two New Objects",12,320,157,153,18,RBmesh2.val, "The knife creates two new separate objects."); BGL.glRasterPos2d(8, 128) Draw.Text("1 - Draw a Mesh Plane defining the Cut Plane") BGL.glRasterPos2d(8, 108) Draw.Text("2 - Select the Meshes to be Cut and the Cut Plane") BGL.glRasterPos2d(8, 88) Draw.Text(" (Meshes Dark Purple, Plane Light Purple)") BGL.glRasterPos2d(8, 68) Draw.Text("3 - Choose the Edit Method (Radio Buttons above)") BGL.glRasterPos2d(8, 48) Draw.Text("4 - Push the 'CUT' button (below)") #Create Buttons Draw.Button("CUT", 4, 10, 10, 465, 18, "Cut the selected mesh along the plane") BGL.glRasterPos2d(10, 223) BGL.glColor3f(1,0,0) Draw.Text(msg) msg = '' def event(evt, val): if (evt == Draw.QKEY or evt == Draw.ESCKEY) and not val: Draw.Exit() if evt == Draw.CKEY and not val: CutMesh() Draw.Redraw() def bevent(evt): global RBmesh0,RBmesh1,RBmesh2 if evt == 1: Draw.Exit() elif evt == 4: CutMesh() Draw.Redraw() elif evt == 10: RBmesh0.val = 1 RBmesh1.val = 0 RBmesh2.val = 0 Draw.Redraw() elif evt == 11: RBmesh0.val = 0 RBmesh1.val = 1 RBmesh2.val = 0 Draw.Redraw() elif evt == 12: RBmesh0.val = 0 RBmesh1.val = 0 RBmesh2.val = 1 Draw.Redraw() Draw.Register(draw, event, bevent)