cleanup for python scripts - unused vars and imports
[blender-staging.git] / release / scripts / startup / bl_operators / uvcalc_follow_active.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 #for full docs see...
22 # http://mediawiki.blender.org/index.php/Scripts/Manual/UV_Calculate/Follow_active_quads
23
24 import bpy
25
26
27 def extend(obj, operator, EXTEND_MODE):
28     from bpy_extras import mesh_utils
29
30     me = obj.data
31     me_verts = me.vertices
32     # script will fail without UVs
33     if not me.uv_textures:
34         me.uv_textures.new()
35
36     # Toggle Edit mode
37     is_editmode = (obj.mode == 'EDIT')
38     if is_editmode:
39         bpy.ops.object.mode_set(mode='OBJECT')
40
41     #t = sys.time()
42     edge_average_lengths = {}
43
44     OTHER_INDEX = 2, 3, 0, 1
45
46     def extend_uvs(face_source, face_target, edge_key):
47         '''
48         Takes 2 faces,
49         Projects its extends its UV coords onto the face next to it.
50         Both faces must share an edge
51         '''
52
53         def face_edge_vs(vi):
54             # assume a quad
55             return [(vi[0], vi[1]), (vi[1], vi[2]), (vi[2], vi[3]), (vi[3], vi[0])]
56
57         vidx_source = face_source.vertices
58         vidx_target = face_target.vertices
59
60         faceUVsource = me.uv_textures.active.data[face_source.index]
61         uvs_source = [faceUVsource.uv1, faceUVsource.uv2, faceUVsource.uv3, faceUVsource.uv4]
62
63         faceUVtarget = me.uv_textures.active.data[face_target.index]
64         uvs_target = [faceUVtarget.uv1, faceUVtarget.uv2, faceUVtarget.uv3, faceUVtarget.uv4]
65
66         # vertex index is the key, uv is the value
67
68         uvs_vhash_source = {vindex: uvs_source[i] for i, vindex in enumerate(vidx_source)}
69
70         uvs_vhash_target = {vindex: uvs_target[i] for i, vindex in enumerate(vidx_target)}
71
72         edge_idxs_source = face_edge_vs(vidx_source)
73         edge_idxs_target = face_edge_vs(vidx_target)
74
75         source_matching_edge = -1
76         target_matching_edge = -1
77
78         edge_key_swap = edge_key[1], edge_key[0]
79
80         try:
81             source_matching_edge = edge_idxs_source.index(edge_key)
82         except:
83             source_matching_edge = edge_idxs_source.index(edge_key_swap)
84         try:
85             target_matching_edge = edge_idxs_target.index(edge_key)
86         except:
87             target_matching_edge = edge_idxs_target.index(edge_key_swap)
88
89         edgepair_inner_source = edge_idxs_source[source_matching_edge]
90         edgepair_inner_target = edge_idxs_target[target_matching_edge]
91         edgepair_outer_source = edge_idxs_source[OTHER_INDEX[source_matching_edge]]
92         edgepair_outer_target = edge_idxs_target[OTHER_INDEX[target_matching_edge]]
93
94         if edge_idxs_source[source_matching_edge] == edge_idxs_target[target_matching_edge]:
95             iA = 0  # Flipped, most common
96             iB = 1
97         else:  # The normals of these faces must be different
98             iA = 1
99             iB = 0
100
101         # Set the target UV's touching source face, no tricky calc needed,
102         uvs_vhash_target[edgepair_inner_target[0]][:] = uvs_vhash_source[edgepair_inner_source[iA]]
103         uvs_vhash_target[edgepair_inner_target[1]][:] = uvs_vhash_source[edgepair_inner_source[iB]]
104
105         # Set the 2 UV's on the target face that are not touching
106         # for this we need to do basic expaning on the source faces UV's
107         if EXTEND_MODE == 'LENGTH':
108
109             try:  # divide by zero is possible
110                 '''
111                 measure the length of each face from the middle of each edge to the opposite
112                 allong the axis we are copying, use this
113                 '''
114                 i1a = edgepair_outer_target[iB]
115                 i2a = edgepair_inner_target[iA]
116                 if i1a > i2a:
117                     i1a, i2a = i2a, i1a
118
119                 i1b = edgepair_outer_source[iB]
120                 i2b = edgepair_inner_source[iA]
121                 if i1b > i2b:
122                     i1b, i2b = i2b, i1b
123                 # print edge_average_lengths
124                 factor = edge_average_lengths[i1a, i2a][0] / edge_average_lengths[i1b, i2b][0]
125             except:
126                 # Div By Zero?
127                 factor = 1.0
128
129             uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + factor * (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
130             uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + factor * (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
131
132         else:
133             # same as above but with no factors
134             uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
135             uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
136
137     if not me.uv_textures:
138         me.uv_textures.new()
139
140     face_act = me.faces.active
141     if face_act == -1:
142         operator.report({'ERROR'}, "No active face.")
143         return
144
145     face_sel = [f for f in me.faces if len(f.vertices) == 4 and f.select]
146
147     face_act_local_index = -1
148     for i, f in enumerate(face_sel):
149         if f.index == face_act:
150             face_act_local_index = i
151             break
152
153     if face_act_local_index == -1:
154         operator.report({'ERROR'}, "Active face not selected.")
155         return
156
157     # Modes
158     # 0 unsearched
159     # 1:mapped, use search from this face. - removed!!
160     # 2:all siblings have been searched. dont search again.
161     face_modes = [0] * len(face_sel)
162     face_modes[face_act_local_index] = 1  # extend UV's from this face.
163
164     # Edge connectivty
165     edge_faces = {}
166     for i, f in enumerate(face_sel):
167         for edkey in f.edge_keys:
168             try:
169                 edge_faces[edkey].append(i)
170             except:
171                 edge_faces[edkey] = [i]
172
173     if EXTEND_MODE == 'LENGTH':
174         edge_loops = mesh_utils.edge_loops_from_faces(me, face_sel, [ed.key for ed in me.edges if ed.use_seam])
175         me_verts = me.vertices
176         for loop in edge_loops:
177             looplen = [0.0]
178             for ed in loop:
179                 edge_average_lengths[ed] = looplen
180                 looplen[0] += (me_verts[ed[0]].co - me_verts[ed[1]].co).length
181             looplen[0] = looplen[0] / len(loop)
182
183     # remove seams, so we dont map accross seams.
184     for ed in me.edges:
185         if ed.use_seam:
186             # remove the edge pair if we can
187             try:
188                 del edge_faces[ed.key]
189             except:
190                 pass
191     # Done finding seams
192
193     # face connectivity - faces around each face
194     # only store a list of indices for each face.
195     face_faces = [[] for i in range(len(face_sel))]
196
197     for edge_key, faces in edge_faces.items():
198         if len(faces) == 2:  # Only do edges with 2 face users for now
199             face_faces[faces[0]].append((faces[1], edge_key))
200             face_faces[faces[1]].append((faces[0], edge_key))
201
202     # Now we know what face is connected to what other face, map them by connectivity
203     ok = True
204     while ok:
205         ok = False
206         for i in range(len(face_sel)):
207             if face_modes[i] == 1:  # searchable
208                 for f_sibling, edge_key in face_faces[i]:
209                     if face_modes[f_sibling] == 0:
210                         face_modes[f_sibling] = 1  # mapped and search from.
211                         extend_uvs(face_sel[i], face_sel[f_sibling], edge_key)
212                         face_modes[i] = 1  # we can map from this one now.
213                         ok = True  # keep searching
214
215                 face_modes[i] = 2  # dont search again
216
217     if is_editmode:
218         bpy.ops.object.mode_set(mode='EDIT')
219     else:
220         me.update_tag()
221
222
223 def main(context, operator):
224     obj = context.active_object
225
226     extend(obj, operator, operator.properties.mode)
227
228
229 class FollowActiveQuads(bpy.types.Operator):
230     '''Follow UVs from active quads along continuous face loops'''
231     bl_idname = "uv.follow_active_quads"
232     bl_label = "Follow Active Quads"
233     bl_options = {'REGISTER', 'UNDO'}
234
235     mode = bpy.props.EnumProperty(items=(("EVEN", "Even", "Space all UVs evently"), ("LENGTH", "Length", "Average space UVs edge length of each loop")),
236                         name="Edge Length Mode",
237                         description="Method to space UV edge loops",
238                         default="LENGTH")
239
240     @classmethod
241     def poll(cls, context):
242         obj = context.active_object
243         return (obj is not None and obj.type == 'MESH')
244
245     def execute(self, context):
246         main(context, self)
247         return {'FINISHED'}
248
249     def invoke(self, context, event):
250         wm = context.window_manager
251         return wm.invoke_props_dialog(self)