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