re-commit temp workaround [#35920], this still fails for OSX retina display,
[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 from bpy.types import Operator
26
27
28 def extend(obj, operator, EXTEND_MODE):
29
30     import bmesh
31     me = obj.data
32     # script will fail without UVs
33     if not me.uv_textures:
34         me.uv_textures.new()
35
36     bm = bmesh.from_edit_mesh(me)
37
38     f_act = bm.faces.active
39     uv_act = bm.loops.layers.uv.active
40
41     if f_act is None:
42         operator.report({'ERROR'}, "No active face")
43         return
44     elif len(f_act.verts) != 4:
45         operator.report({'ERROR'}, "Active face must be a quad")
46         return
47
48     faces = [f for f in bm.faces if f.select and len(f.verts) == 4]
49
50     # our own local walker
51     def walk_face_init(faces, f_act):
52         # first tag all faces True (so we dont uvmap them)
53         for f in bm.faces:
54             f.tag = True
55         # then tag faces arg False
56         for f in faces:
57             f.tag = False
58         # tag the active face True since we begin there
59         f_act.tag = True
60
61     def walk_face(f):
62         # all faces in this list must be tagged
63         f.tag = True
64         faces_a = [f]
65         faces_b = []
66
67         while faces_a:
68             for f in faces_a:
69                 for l in f.loops:
70                     l_edge = l.edge
71                     if (l_edge.is_manifold is True) and (l_edge.seam is False):
72                         l_other = l.link_loop_radial_next
73                         f_other = l_other.face
74                         if not f_other.tag:
75                             yield (f, l, f_other)
76                             f_other.tag = True
77                             faces_b.append(f_other)
78             # swap
79             faces_a, faces_b = faces_b, faces_a
80             faces_b.clear()
81
82     def walk_edgeloop(l):
83         """
84         Could make this a generic function
85         """
86         e_first = l.edge
87         e = None
88         while True:
89             e = l.edge
90             yield e
91
92             # don't step past non-manifold edges
93             if e.is_manifold:
94                 # welk around the quad and then onto the next face
95                 l = l.link_loop_radial_next
96                 if len(l.face.verts) == 4:
97                     l = l.link_loop_next.link_loop_next
98                     if l.edge is e_first:
99                         break
100                 else:
101                     break
102             else:
103                 break
104
105     def extrapolate_uv(fac,
106                        l_a_outer, l_a_inner,
107                        l_b_outer, l_b_inner):
108         l_b_inner[:] = l_a_inner
109         l_b_outer[:] = l_a_inner + ((l_a_inner - l_a_outer) * fac)
110
111     def apply_uv(f_prev, l_prev, f_next):
112         l_a = [None, None, None, None]
113         l_b = [None, None, None, None]
114
115         l_a[0] = l_prev
116         l_a[1] = l_a[0].link_loop_next
117         l_a[2] = l_a[1].link_loop_next
118         l_a[3] = l_a[2].link_loop_next
119
120         #  l_b
121         #  +-----------+
122         #  |(3)        |(2)
123         #  |           |
124         #  |l_next(0)  |(1)
125         #  +-----------+
126         #        ^
127         #  l_a   |
128         #  +-----------+
129         #  |l_prev(0)  |(1)
130         #  |    (f)    |
131         #  |(3)        |(2)
132         #  +-----------+
133         #  copy from this face to the one above.
134
135         # get the other loops
136         l_next = l_prev.link_loop_radial_next
137         if l_next.vert != l_prev.vert:
138             l_b[1] = l_next
139             l_b[0] = l_b[1].link_loop_next
140             l_b[3] = l_b[0].link_loop_next
141             l_b[2] = l_b[3].link_loop_next
142         else:
143             l_b[0] = l_next
144             l_b[1] = l_b[0].link_loop_next
145             l_b[2] = l_b[1].link_loop_next
146             l_b[3] = l_b[2].link_loop_next
147
148         l_a_uv = [l[uv_act].uv for l in l_a]
149         l_b_uv = [l[uv_act].uv for l in l_b]
150
151         if EXTEND_MODE == 'LENGTH_AVERAGE':
152             fac = edge_lengths[l_b[2].edge.index][0] / edge_lengths[l_a[1].edge.index][0]
153         elif EXTEND_MODE == 'LENGTH':
154             a0, b0, c0 = l_a[3].vert.co, l_a[0].vert.co, l_b[3].vert.co
155             a1, b1, c1 = l_a[2].vert.co, l_a[1].vert.co, l_b[2].vert.co
156
157             d1 = (a0 - b0).length + (a1 - b1).length
158             d2 = (b0 - c0).length + (b1 - c1).length
159             try:
160                 fac = d2 / d1
161             except ZeroDivisionError:
162                 fac = 1.0
163         else:
164             fac = 1.0
165
166         extrapolate_uv(fac,
167                        l_a_uv[3], l_a_uv[0],
168                        l_b_uv[3], l_b_uv[0])
169
170         extrapolate_uv(fac,
171                        l_a_uv[2], l_a_uv[1],
172                        l_b_uv[2], l_b_uv[1])
173
174     # -------------------------------------------
175     # Calculate average length per loop if needed
176
177     if EXTEND_MODE == 'LENGTH_AVERAGE':
178         bm.edges.index_update()
179         edge_lengths = [None] * len(bm.edges)
180
181         for f in faces:
182             # we know its a quad
183             l_quad = f.loops[:]
184             l_pair_a = (l_quad[0], l_quad[2])
185             l_pair_b = (l_quad[1], l_quad[3])
186
187             for l_pair in (l_pair_a, l_pair_b):
188                 if edge_lengths[l_pair[0].edge.index] is None:
189
190                     edge_length_store = [-1.0]
191                     edge_length_accum = 0.0
192                     edge_length_total = 0
193
194                     for l in l_pair:
195                         if edge_lengths[l.edge.index] is None:
196                             for e in walk_edgeloop(l):
197                                 if edge_lengths[e.index] is None:
198                                     edge_lengths[e.index] = edge_length_store
199                                     edge_length_accum += e.calc_length()
200                                     edge_length_total += 1
201
202                     edge_length_store[0] = edge_length_accum / edge_length_total
203
204     # done with average length
205     # ------------------------
206
207     walk_face_init(faces, f_act)
208     for f_triple in walk_face(f_act):
209         apply_uv(*f_triple)
210
211     bmesh.update_edit_mesh(me, False)
212
213
214 def main(context, operator):
215     obj = context.active_object
216
217     extend(obj, operator, operator.properties.mode)
218
219
220 class FollowActiveQuads(Operator):
221     """Follow UVs from active quads along continuous face loops"""
222     bl_idname = "uv.follow_active_quads"
223     bl_label = "Follow Active Quads"
224     bl_options = {'REGISTER', 'UNDO'}
225
226     mode = bpy.props.EnumProperty(
227             name="Edge Length Mode",
228             description="Method to space UV edge loops",
229             items=(('EVEN', "Even", "Space all UVs evenly"),
230                    ('LENGTH', "Length", "Average space UVs edge length of each loop"),
231                    ('LENGTH_AVERAGE', "Length Average", "Average space UVs edge length of each loop"),
232                    ),
233             default='LENGTH_AVERAGE',
234             )
235
236     @classmethod
237     def poll(cls, context):
238         obj = context.active_object
239         return (obj is not None and obj.type == 'MESH')
240
241     def execute(self, context):
242         main(context, self)
243         return {'FINISHED'}
244
245     def invoke(self, context, event):
246         wm = context.window_manager
247         return wm.invoke_props_dialog(self)