2.5: Top Menu
[blender.git] / release / scripts / mesh_wire.py
1 #!BPY
2 """
3 Name: 'Solid Wireframe'
4 Blender: 243
5 Group: 'Mesh'
6 Tooltip: 'Make a solid wireframe copy of this mesh'
7 """
8
9 # -------------------------------------------------------------------------- 
10 # Solid Wireframe1.0 by Campbell Barton (AKA Ideasman42) 
11 # -------------------------------------------------------------------------- 
12 # ***** BEGIN GPL LICENSE BLOCK ***** 
13
14 # This program is free software; you can redistribute it and/or 
15 # modify it under the terms of the GNU General Public License 
16 # as published by the Free Software Foundation; either version 2 
17 # of the License, or (at your option) any later version. 
18
19 # This program is distributed in the hope that it will be useful, 
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of 
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
22 # GNU General Public License for more details. 
23
24 # You should have received a copy of the GNU General Public License 
25 # along with this program; if not, write to the Free Software Foundation, 
26 # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
27
28 # ***** END GPL LICENCE BLOCK ***** 
29 # --------------------------------------------------------------------------
30 import Blender
31 from Blender import Scene, Mesh, Window, sys
32 from Blender.Mathutils import AngleBetweenVecs, TriangleNormal
33 from BPyMesh import faceAngles # get angles for face cornders
34 #import BPyMesh
35 #reload(BPyMesh)
36 #faceAngles = BPyMesh.faceAngles
37
38 # works out the distanbce to inset the corners based on angles
39 from BPyMathutils import angleToLength
40 #import BPyMathutils 
41 #reload(BPyMathutils)
42 #angleToLength = BPyMathutils.angleToLength
43
44 import mesh_solidify
45
46 import BPyMessages
47 reload(BPyMessages)
48 import bpy
49
50
51 def solid_wire(ob_orig, me_orig, sce, PREF_THICKNESS, PREF_SOLID, PREF_SHARP, PREF_XSHARP):
52         if not PREF_SHARP and PREF_XSHARP:
53                 PREF_XSHARP = False
54         
55         # This function runs out of editmode with a mesh
56         # error cases are alredy checked for
57         
58         inset_half = PREF_THICKNESS / 2
59         del PREF_THICKNESS
60         
61         ob = ob_orig.copy()
62         me = me_orig.copy()
63         ob.link(me)
64         sce.objects.selected = []
65         sce.objects.link(ob)
66         ob.sel = True
67         sce.objects.active = ob
68         
69         # Modify the object, should be a set
70         FGON= Mesh.EdgeFlags.FGON
71         edges_fgon = dict([(ed.key,None) for ed in me.edges if ed.flag & FGON])
72         # edges_fgon.fromkeys([ed.key for ed in me.edges if ed.flag & FGON])
73         
74         del FGON
75
76         
77         
78         # each face needs its own verts
79         # orig_vert_count =len(me.verts)
80         new_vert_count = len(me.faces) * 4
81         for f in me.faces:
82                 if len(f) == 3:
83                         new_vert_count -= 1
84         
85         if PREF_SHARP == 0:
86                 new_faces_edge= {}
87                 
88                 def add_edge(i1,i2, ni1, ni2):
89                         
90                         if i1>i2:
91                                 i1,i2 = i2,i1
92                                 flip = True
93                         else:
94                                 flip = False
95                         new_faces_edge.setdefault((i1,i2), []).append((ni1, ni2, flip))
96                 
97         
98         new_verts = []
99         new_faces = []
100         vert_index = len(me.verts)
101         
102         for f in me.faces:
103                 f_v_co = [v.co for v in f]
104                 angles = faceAngles(f_v_co)
105                 f_v_idx = [v.index for v in f]
106                 
107                 def new_vert(fi):
108                         co = f_v_co[fi]
109                         a = angles[fi]
110                         if a > 180:
111                                 vert_inset = 1 * inset_half
112                         else:
113                                 vert_inset = inset_half * angleToLength( abs((180-a) / 2) )
114                         
115                         # Calculate the inset direction
116                         co1 = f_v_co[fi-1]
117                         co2 = fi+1 # Wrap this index back to the start
118                         if co2 == len(f_v_co): co2 = 0
119                         co2 = f_v_co[co2]
120                         
121                         co1 = co1 - co
122                         co2 = co2 - co
123                         co1.normalize()
124                         co2.normalize()
125                         d = co1+co2
126                         # Done with inset direction
127                         
128                         d.length = vert_inset
129                         return co+d
130                 
131                 new_verts.extend([new_vert(i) for i in xrange(len(f_v_co))])
132                 
133                 if len(f_v_idx) == 4:
134                         faces = [\
135                         (f_v_idx[1], f_v_idx[0], vert_index, vert_index+1),\
136                         (f_v_idx[2], f_v_idx[1], vert_index+1, vert_index+2),\
137                         (f_v_idx[3], f_v_idx[2], vert_index+2, vert_index+3),\
138                         (f_v_idx[0], f_v_idx[3], vert_index+3, vert_index),\
139                         ]
140                 else:
141                         faces = [\
142                         (f_v_idx[1], f_v_idx[0], vert_index, vert_index+1),\
143                         (f_v_idx[2], f_v_idx[1], vert_index+1, vert_index+2),\
144                         (f_v_idx[0], f_v_idx[2], vert_index+2, vert_index),\
145                         ]
146                 
147                 
148                 if PREF_SHARP == 1:
149                         if not edges_fgon:
150                                 new_faces.extend(faces)
151                         else:
152                                 for nf in faces:
153                                         i1,i2 = nf[0], nf[1]
154                                         if i1>i2: i1,i2 = i2,i1
155                                         
156                                         if edges_fgon and (i1,i2) not in edges_fgon:
157                                                 new_faces.append(nf)
158                         
159                         
160                 
161                 elif PREF_SHARP == 0:
162                         for nf in faces:
163                                 add_edge(*nf)
164                         
165                 vert_index += len(f_v_co)
166         
167         me.verts.extend(new_verts)
168         
169         if PREF_SHARP == 0:
170                 def add_tri_flipped(i1,i2,i3):
171                         try:
172                                 if AngleBetweenVecs(me.verts[i1].no, TriangleNormal(me.verts[i1].co, me.verts[i2].co, me.verts[i3].co)) < 90:
173                                         return i3,i2,i1
174                                 else:
175                                         return i1,i2,i3
176                         except:
177                                 return i1,i2,i3
178                 
179                 # This stores new verts that use this vert
180                 # used for re-averaging this verts location
181                 # based on surrounding verts. looks better but not needed.
182                 vert_users = [set() for i in xrange(vert_index)]
183                 
184                 for (i1,i2), nf in new_faces_edge.iteritems():
185                         
186                         if len(nf) == 2:
187                                 # Add the main face
188                                 if edges_fgon and (i1,i2) not in edges_fgon:
189                                         new_faces.append((nf[0][0], nf[0][1], nf[1][0], nf[1][1]))
190                                 
191                                 
192                                 if nf[0][2]:    key1 = nf[0][1],nf[0][0]
193                                 else:                   key1 = nf[0][0],nf[0][1]
194                                 if nf[1][2]:    key2 = nf[1][1],nf[1][0]
195                                 else:                   key2 = nf[1][0],nf[1][1]
196                                 
197                                 # CRAP, cont work out which way to flip so make it oppisite the verts normal.
198                                 
199                                 ###new_faces.append((i2, key1[0], key2[0])) # NO FLIPPING, WORKS THOUGH
200                                 ###new_faces.append((i1, key1[1], key2[1]))
201                                 new_faces.append(add_tri_flipped(i2, key1[0], key2[0]))
202                                 new_faces.append(add_tri_flipped(i1, key1[1], key2[1]))
203                                 
204                                 # Average vert loction so its not tooo pointy
205                                 # not realy needed but looks better
206                                 vert_users[i2].update((key1[0], key2[0]))
207                                 vert_users[i1].update((key1[1], key2[1]))
208                         
209                         if len(nf) == 1:
210                                 if nf[0][2]:    new_faces.append((nf[0][0], nf[0][1], i2, i1)) # flipped
211                                 else:                   new_faces.append((i1,i2, nf[0][0], nf[0][1]))
212                                 
213                 
214                 # average points now.
215                 for i, vusers in enumerate(vert_users):
216                         if vusers:
217                                 co = me.verts[i].co
218                                 co.zero()
219                                 
220                                 for ii in vusers:
221                                         co += me.verts[ii].co
222                                 co /= len(vusers)
223         
224         me.faces.delete(1, range(len(me.faces)))
225         
226         me.faces.extend(new_faces)
227
228         # External function, solidify
229         me.sel = True
230         if PREF_SOLID:
231                 mesh_solidify.solidify(me, -inset_half*2, True, False, PREF_XSHARP)
232
233
234 def main():
235         
236         # Gets the current scene, there can be many scenes in 1 blend file.
237         sce = bpy.data.scenes.active
238         
239         # Get the active object, there can only ever be 1
240         # and the active object is always the editmode object.
241         ob_act = sce.objects.active
242         
243         if not ob_act or ob_act.type != 'Mesh':
244                 BPyMessages.Error_NoMeshActive()
245                 return 
246         
247         # Saves the editmode state and go's out of 
248         # editmode if its enabled, we cant make
249         # changes to the mesh data while in editmode.
250         is_editmode = Window.EditMode()
251         Window.EditMode(0)
252         
253         me = ob_act.getData(mesh=1) # old NMesh api is default
254         if len(me.faces)==0:
255                 BPyMessages.Error_NoMeshFaces()
256                 if is_editmode: Window.EditMode(1)
257                 return
258         
259         # Create the variables.
260         PREF_THICK = Blender.Draw.Create(0.005)
261         PREF_SOLID = Blender.Draw.Create(1)
262         PREF_SHARP = Blender.Draw.Create(1)
263         PREF_XSHARP = Blender.Draw.Create(0)
264         
265         pup_block = [\
266         ('Thick:', PREF_THICK, 0.0001, 2.0, 'Skin thickness in mesh space.'),\
267         ('Solid Wire', PREF_SOLID, 'If Disabled, will use 6 sided wire segments'),\
268         ('Sharp Wire', PREF_SHARP, 'Use the original mesh topology for more accurate sharp wire.'),\
269         ('Extra Sharp', PREF_XSHARP, 'Use less geometry to create a sharper looking wire'),\
270         ]
271         
272         if not Blender.Draw.PupBlock('Solid Wireframe', pup_block):
273                 if is_editmode: Window.EditMode(1)
274                 return
275         
276         Window.WaitCursor(1)
277         t = sys.time()
278         
279         # Run the mesh editing function
280         solid_wire(ob_act, me, sce, PREF_THICK.val, PREF_SOLID.val, PREF_SHARP.val, PREF_XSHARP.val)
281         
282         # Timing the script is a good way to be aware on any speed hits when scripting
283         print 'Solid Wireframe finished in %.2f seconds' % (sys.time()-t)
284         Window.WaitCursor(0)
285         if is_editmode: Window.EditMode(1)
286         
287         
288 # This lets you can import the script without running it
289 if __name__ == '__main__':
290         main()