1 # ##### BEGIN GPL LICENSE BLOCK #####
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.
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.
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.
17 # ##### END GPL LICENSE BLOCK #####
22 # http://mediawiki.blender.org/index.php/Scripts/Manual/UV_Calculate/Follow_active_quads
25 from bpy.types import Operator
28 def extend(obj, operator, EXTEND_MODE):
29 from bpy_extras import mesh_utils
32 me_verts = me.vertices
33 # script will fail without UVs
34 if not me.uv_textures:
38 is_editmode = (obj.mode == 'EDIT')
40 bpy.ops.object.mode_set(mode='OBJECT')
43 edge_average_lengths = {}
45 OTHER_INDEX = 2, 3, 0, 1
47 def extend_uvs(face_source, face_target, edge_key):
50 Projects its extends its UV coords onto the face next to it.
51 Both faces must share an edge
56 return [(vi[0], vi[1]), (vi[1], vi[2]), (vi[2], vi[3]), (vi[3], vi[0])]
58 vidx_source = face_source.vertices
59 vidx_target = face_target.vertices
61 faceUVsource = me.uv_textures.active.data[face_source.index]
62 uvs_source = [faceUVsource.uv1, faceUVsource.uv2, faceUVsource.uv3, faceUVsource.uv4]
64 faceUVtarget = me.uv_textures.active.data[face_target.index]
65 uvs_target = [faceUVtarget.uv1, faceUVtarget.uv2, faceUVtarget.uv3, faceUVtarget.uv4]
67 # vertex index is the key, uv is the value
69 uvs_vhash_source = {vindex: uvs_source[i] for i, vindex in enumerate(vidx_source)}
71 uvs_vhash_target = {vindex: uvs_target[i] for i, vindex in enumerate(vidx_target)}
73 edge_idxs_source = face_edge_vs(vidx_source)
74 edge_idxs_target = face_edge_vs(vidx_target)
76 source_matching_edge = -1
77 target_matching_edge = -1
79 edge_key_swap = edge_key[1], edge_key[0]
82 source_matching_edge = edge_idxs_source.index(edge_key)
84 source_matching_edge = edge_idxs_source.index(edge_key_swap)
86 target_matching_edge = edge_idxs_target.index(edge_key)
88 target_matching_edge = edge_idxs_target.index(edge_key_swap)
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]]
95 if edge_idxs_source[source_matching_edge] == edge_idxs_target[target_matching_edge]:
96 iA = 0 # Flipped, most common
98 else: # The normals of these faces must be different
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]]
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':
110 try: # divide by zero is possible
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
115 i1a = edgepair_outer_target[iB]
116 i2a = edgepair_inner_target[iA]
120 i1b = edgepair_outer_source[iB]
121 i2b = edgepair_inner_source[iA]
124 # print edge_average_lengths
125 factor = edge_average_lengths[i1a, i2a][0] / edge_average_lengths[i1b, i2b][0]
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]])
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]])
138 if not me.uv_textures:
141 face_act = me.faces.active
143 operator.report({'ERROR'}, "No active face.")
146 face_sel = [f for f in me.faces if len(f.vertices) == 4 and f.select]
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
154 if face_act_local_index == -1:
155 operator.report({'ERROR'}, "Active face not selected.")
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.
167 for i, f in enumerate(face_sel):
168 for edkey in f.edge_keys:
170 edge_faces[edkey].append(i)
172 edge_faces[edkey] = [i]
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:
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)
184 # remove seams, so we dont map accross seams.
187 # remove the edge pair if we can
189 del edge_faces[ed.key]
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))]
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))
203 # Now we know what face is connected to what other face, map them by connectivity
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
216 face_modes[i] = 2 # dont search again
219 bpy.ops.object.mode_set(mode='EDIT')
224 def main(context, operator):
225 obj = context.active_object
227 extend(obj, operator, operator.properties.mode)
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'}
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",
242 def poll(cls, context):
243 obj = context.active_object
244 return (obj is not None and obj.type == 'MESH')
246 def execute(self, context):
250 def invoke(self, context, event):
251 wm = context.window_manager
252 return wm.invoke_props_dialog(self)