#!BPY """ Name: 'Solid Wireframe' Blender: 243 Group: 'Mesh' Tooltip: 'Make a solid wireframe copy of this mesh' """ # -------------------------------------------------------------------------- # Solid Wireframe1.0 by Campbell Barton (AKA Ideasman42) # -------------------------------------------------------------------------- # ***** 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ***** END GPL LICENCE BLOCK ***** # -------------------------------------------------------------------------- import Blender from Blender import Scene, Mesh, Window, sys from Blender.Mathutils import AngleBetweenVecs, TriangleNormal from BPyMesh import faceAngles # get angles for face cornders #import BPyMesh #reload(BPyMesh) #faceAngles = BPyMesh.faceAngles # works out the distanbce to inset the corners based on angles from BPyMathutils import angleToLength #import BPyMathutils #reload(BPyMathutils) #angleToLength = BPyMathutils.angleToLength import mesh_solidify import BPyMessages reload(BPyMessages) import bpy def solid_wire(ob_orig, me_orig, sce, PREF_THICKNESS, PREF_SOLID, PREF_SHARP, PREF_XSHARP): if not PREF_SHARP and PREF_XSHARP: PREF_XSHARP = False # This function runs out of editmode with a mesh # error cases are alredy checked for inset_half = PREF_THICKNESS / 2 del PREF_THICKNESS ob = ob_orig.copy() me = me_orig.copy() ob.link(me) sce.objects.selected = [] sce.objects.link(ob) ob.sel = True sce.objects.active = ob # Modify the object, should be a set FGON= Mesh.EdgeFlags.FGON edges_fgon = dict([(ed.key,None) for ed in me.edges if ed.flag & FGON]) # edges_fgon.fromkeys([ed.key for ed in me.edges if ed.flag & FGON]) del FGON # each face needs its own verts # orig_vert_count =len(me.verts) new_vert_count = len(me.faces) * 4 for f in me.faces: if len(f) == 3: new_vert_count -= 1 if PREF_SHARP == 0: new_faces_edge= {} def add_edge(i1,i2, ni1, ni2): if i1>i2: i1,i2 = i2,i1 flip = True else: flip = False new_faces_edge.setdefault((i1,i2), []).append((ni1, ni2, flip)) new_verts = [] new_faces = [] vert_index = len(me.verts) for f in me.faces: f_v_co = [v.co for v in f] angles = faceAngles(f_v_co) f_v_idx = [v.index for v in f] def new_vert(fi): co = f_v_co[fi] a = angles[fi] if a > 180: vert_inset = 1 * inset_half else: vert_inset = inset_half * angleToLength( abs((180-a) / 2) ) # Calculate the inset direction co1 = f_v_co[fi-1] co2 = fi+1 # Wrap this index back to the start if co2 == len(f_v_co): co2 = 0 co2 = f_v_co[co2] co1 = co1 - co co2 = co2 - co co1.normalize() co2.normalize() d = co1+co2 # Done with inset direction d.length = vert_inset return co+d new_verts.extend([new_vert(i) for i in xrange(len(f_v_co))]) if len(f_v_idx) == 4: faces = [\ (f_v_idx[1], f_v_idx[0], vert_index, vert_index+1),\ (f_v_idx[2], f_v_idx[1], vert_index+1, vert_index+2),\ (f_v_idx[3], f_v_idx[2], vert_index+2, vert_index+3),\ (f_v_idx[0], f_v_idx[3], vert_index+3, vert_index),\ ] else: faces = [\ (f_v_idx[1], f_v_idx[0], vert_index, vert_index+1),\ (f_v_idx[2], f_v_idx[1], vert_index+1, vert_index+2),\ (f_v_idx[0], f_v_idx[2], vert_index+2, vert_index),\ ] if PREF_SHARP == 1: if not edges_fgon: new_faces.extend(faces) else: for nf in faces: i1,i2 = nf[0], nf[1] if i1>i2: i1,i2 = i2,i1 if edges_fgon and (i1,i2) not in edges_fgon: new_faces.append(nf) elif PREF_SHARP == 0: for nf in faces: add_edge(*nf) vert_index += len(f_v_co) me.verts.extend(new_verts) if PREF_SHARP == 0: def add_tri_flipped(i1,i2,i3): try: if AngleBetweenVecs(me.verts[i1].no, TriangleNormal(me.verts[i1].co, me.verts[i2].co, me.verts[i3].co)) < 90: return i3,i2,i1 else: return i1,i2,i3 except: return i1,i2,i3 # This stores new verts that use this vert # used for re-averaging this verts location # based on surrounding verts. looks better but not needed. vert_users = [set() for i in xrange(vert_index)] for (i1,i2), nf in new_faces_edge.iteritems(): if len(nf) == 2: # Add the main face if edges_fgon and (i1,i2) not in edges_fgon: new_faces.append((nf[0][0], nf[0][1], nf[1][0], nf[1][1])) if nf[0][2]: key1 = nf[0][1],nf[0][0] else: key1 = nf[0][0],nf[0][1] if nf[1][2]: key2 = nf[1][1],nf[1][0] else: key2 = nf[1][0],nf[1][1] # CRAP, cont work out which way to flip so make it oppisite the verts normal. ###new_faces.append((i2, key1[0], key2[0])) # NO FLIPPING, WORKS THOUGH ###new_faces.append((i1, key1[1], key2[1])) new_faces.append(add_tri_flipped(i2, key1[0], key2[0])) new_faces.append(add_tri_flipped(i1, key1[1], key2[1])) # Average vert loction so its not tooo pointy # not realy needed but looks better vert_users[i2].update((key1[0], key2[0])) vert_users[i1].update((key1[1], key2[1])) if len(nf) == 1: if nf[0][2]: new_faces.append((nf[0][0], nf[0][1], i2, i1)) # flipped else: new_faces.append((i1,i2, nf[0][0], nf[0][1])) # average points now. for i, vusers in enumerate(vert_users): if vusers: co = me.verts[i].co co.zero() for ii in vusers: co += me.verts[ii].co co /= len(vusers) me.faces.delete(1, range(len(me.faces))) me.faces.extend(new_faces) # External function, solidify me.sel = True if PREF_SOLID: mesh_solidify.solidify(me, -inset_half*2, True, False, PREF_XSHARP) def main(): # Gets the current scene, there can be many scenes in 1 blend file. sce = bpy.data.scenes.active # Get the active object, there can only ever be 1 # and the active object is always the editmode object. ob_act = sce.objects.active if not ob_act or ob_act.type != 'Mesh': BPyMessages.Error_NoMeshActive() return # Saves the editmode state and go's out of # editmode if its enabled, we cant make # changes to the mesh data while in editmode. is_editmode = Window.EditMode() Window.EditMode(0) me = ob_act.getData(mesh=1) # old NMesh api is default if len(me.faces)==0: BPyMessages.Error_NoMeshFaces() if is_editmode: Window.EditMode(1) return # Create the variables. PREF_THICK = Blender.Draw.Create(0.005) PREF_SOLID = Blender.Draw.Create(1) PREF_SHARP = Blender.Draw.Create(1) PREF_XSHARP = Blender.Draw.Create(0) pup_block = [\ ('Thick:', PREF_THICK, 0.0001, 2.0, 'Skin thickness in mesh space.'),\ ('Solid Wire', PREF_SOLID, 'If Disabled, will use 6 sided wire segments'),\ ('Sharp Wire', PREF_SHARP, 'Use the original mesh topology for more accurate sharp wire.'),\ ('Extra Sharp', PREF_XSHARP, 'Use less geometry to create a sharper looking wire'),\ ] if not Blender.Draw.PupBlock('Solid Wireframe', pup_block): if is_editmode: Window.EditMode(1) return Window.WaitCursor(1) t = sys.time() # Run the mesh editing function solid_wire(ob_act, me, sce, PREF_THICK.val, PREF_SOLID.val, PREF_SHARP.val, PREF_XSHARP.val) # Timing the script is a good way to be aware on any speed hits when scripting print 'Solid Wireframe finished in %.2f seconds' % (sys.time()-t) Window.WaitCursor(0) if is_editmode: Window.EditMode(1) # This lets you can import the script without running it if __name__ == '__main__': main()