3 # ##### BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # ##### END GPL LICENSE BLOCK #####
21 __author__
= "Nutti <nutti.metro@gmail.com>, Mifth, MaxRobinot"
22 __status__
= "production"
24 __date__
= "24 Feb 2018"
26 from collections
import OrderedDict
30 from bpy
.props
import BoolProperty
35 class MUV_TransUVCopy(bpy
.types
.Operator
):
37 Operation class: Transfer UV copy
38 Topological based copy
41 bl_idname
= "uv.muv_transuv_copy"
42 bl_label
= "Transfer UV Copy"
43 bl_description
= "Transfer UV Copy (Topological based copy)"
44 bl_options
= {'REGISTER', 'UNDO'}
46 def execute(self
, context
):
47 props
= context
.scene
.muv_props
.transuv
48 active_obj
= context
.scene
.objects
.active
49 bm
= bmesh
.from_edit_mesh(active_obj
.data
)
50 if common
.check_version(2, 73, 0) >= 0:
51 bm
.faces
.ensure_lookup_table()
54 if not bm
.loops
.layers
.uv
:
55 self
.report({'WARNING'}, "Object must have more than one UV map")
57 uv_layer
= bm
.loops
.layers
.uv
.verify()
59 props
.topology_copied
.clear()
62 active_face
= bm
.faces
.active
63 sel_faces
= [face
for face
in bm
.faces
if face
.select
]
64 if len(sel_faces
) != 2:
65 self
.report({'WARNING'}, "Two faces must be selected")
67 if not active_face
or active_face
not in sel_faces
:
68 self
.report({'WARNING'}, "Two faces must be active")
71 # parse all faces according to selection
72 active_face_nor
= active_face
.normal
.copy()
73 all_sorted_faces
= main_parse(
74 self
, uv_layer
, sel_faces
, active_face
,
78 for face_data
in all_sorted_faces
.values():
80 uv_loops
= face_data
[2]
81 uvs
= [l
.uv
.copy() for l
in uv_loops
]
82 pin_uvs
= [l
.pin_uv
for l
in uv_loops
]
83 seams
= [e
.seam
for e
in edges
]
84 props
.topology_copied
.append([uvs
, pin_uvs
, seams
])
86 bmesh
.update_edit_mesh(active_obj
.data
)
91 class MUV_TransUVPaste(bpy
.types
.Operator
):
93 Operation class: Transfer UV paste
94 Topological based paste
97 bl_idname
= "uv.muv_transuv_paste"
98 bl_label
= "Transfer UV Paste"
99 bl_description
= "Transfer UV Paste (Topological based paste)"
100 bl_options
= {'REGISTER', 'UNDO'}
102 invert_normals
= BoolProperty(
103 name
="Invert Normals",
104 description
="Invert Normals",
107 copy_seams
= BoolProperty(
109 description
="Copy Seams",
113 def execute(self
, context
):
114 props
= context
.scene
.muv_props
.transuv
115 active_obj
= context
.scene
.objects
.active
116 bm
= bmesh
.from_edit_mesh(active_obj
.data
)
117 if common
.check_version(2, 73, 0) >= 0:
118 bm
.faces
.ensure_lookup_table()
121 if not bm
.loops
.layers
.uv
:
122 self
.report({'WARNING'}, "Object must have more than one UV map")
124 uv_layer
= bm
.loops
.layers
.uv
.verify()
126 # get selection history
128 e
for e
in bm
.select_history
129 if isinstance(e
, bmesh
.types
.BMFace
) and e
.select
]
130 if len(all_sel_faces
) % 2 != 0:
131 self
.report({'WARNING'}, "Two faces must be selected")
134 # parse selection history
135 for i
, _
in enumerate(all_sel_faces
):
136 if (i
== 0) or (i
% 2 == 0):
138 sel_faces
= [all_sel_faces
[i
- 1], all_sel_faces
[i
]]
139 active_face
= all_sel_faces
[i
]
141 # parse all faces according to selection history
142 active_face_nor
= active_face
.normal
.copy()
143 if self
.invert_normals
:
144 active_face_nor
.negate()
145 all_sorted_faces
= main_parse(
146 self
, uv_layer
, sel_faces
, active_face
,
150 # check amount of copied/pasted faces
151 if len(all_sorted_faces
) != len(props
.topology_copied
):
154 "Mesh has different amount of faces"
158 for j
, face_data
in enumerate(all_sorted_faces
.values()):
159 copied_data
= props
.topology_copied
[j
]
161 # check amount of copied/pasted verts
162 if len(copied_data
[0]) != len(face_data
[2]):
163 bpy
.ops
.mesh
.select_all(action
='DESELECT')
164 # select problematic face
165 list(all_sorted_faces
.keys())[j
].select
= True
168 "Face have different amount of vertices"
172 for k
, (edge
, uvloop
) in enumerate(zip(face_data
[1],
174 uvloop
.uv
= copied_data
[0][k
]
175 uvloop
.pin_uv
= copied_data
[1][k
]
177 edge
.seam
= copied_data
[2][k
]
179 bmesh
.update_edit_mesh(active_obj
.data
)
181 active_obj
.data
.show_edge_seams
= True
187 self
, uv_layer
, sel_faces
,
188 active_face
, active_face_nor
):
189 all_sorted_faces
= OrderedDict() # This is the main stuff
196 # get shared edge of two faces
198 for edge
in active_face
.edges
:
199 if edge
in sel_faces
[0].edges
and edge
in sel_faces
[1].edges
:
200 cross_edges
.append(edge
)
202 # parse two selected faces
203 if cross_edges
and len(cross_edges
) == 1:
204 shared_edge
= cross_edges
[0]
208 dot_n
= active_face_nor
.normalized()
209 edge_vec_1
= (shared_edge
.verts
[1].co
- shared_edge
.verts
[0].co
)
210 edge_vec_len
= edge_vec_1
.length
211 edge_vec_1
= edge_vec_1
.normalized()
213 af_center
= active_face
.calc_center_median()
214 af_vec
= shared_edge
.verts
[0].co
+ (edge_vec_1
* (edge_vec_len
* 0.5))
215 af_vec
= (af_vec
- af_center
).normalized()
217 if af_vec
.cross(edge_vec_1
).dot(dot_n
) > 0:
218 vert1
= shared_edge
.verts
[0]
219 vert2
= shared_edge
.verts
[1]
221 vert1
= shared_edge
.verts
[1]
222 vert2
= shared_edge
.verts
[0]
224 # get active face stuff and uvs
225 face_stuff
= get_other_verts_edges(
226 active_face
, vert1
, vert2
, shared_edge
, uv_layer
)
227 all_sorted_faces
[active_face
] = face_stuff
228 used_verts
.update(active_face
.verts
)
229 used_edges
.update(active_face
.edges
)
231 # get first selected face stuff and uvs as they share shared_edge
232 second_face
= sel_faces
[0]
233 if second_face
is active_face
:
234 second_face
= sel_faces
[1]
235 face_stuff
= get_other_verts_edges(
236 second_face
, vert1
, vert2
, shared_edge
, uv_layer
)
237 all_sorted_faces
[second_face
] = face_stuff
238 used_verts
.update(second_face
.verts
)
239 used_edges
.update(second_face
.edges
)
242 faces_to_parse
.append(active_face
)
243 faces_to_parse
.append(second_face
)
246 self
.report({'WARNING'}, "Two faces should share one edge")
251 new_parsed_faces
= []
252 if not faces_to_parse
:
254 for face
in faces_to_parse
:
255 face_stuff
= all_sorted_faces
.get(face
)
256 new_faces
= parse_faces(
257 face
, face_stuff
, used_verts
, used_edges
, all_sorted_faces
,
259 if new_faces
== 'CANCELLED':
260 self
.report({'WARNING'}, "More than 2 faces share edge")
263 new_parsed_faces
+= new_faces
264 faces_to_parse
= new_parsed_faces
266 return all_sorted_faces
270 check_face
, face_stuff
, used_verts
, used_edges
, all_sorted_faces
,
272 """recurse faces around the new_grow only"""
274 new_shared_faces
= []
275 for sorted_edge
in face_stuff
[1]:
276 shared_faces
= sorted_edge
.link_faces
278 if len(shared_faces
) > 2:
279 bpy
.ops
.mesh
.select_all(action
='DESELECT')
280 for face_sel
in shared_faces
:
281 face_sel
.select
= True
285 clear_shared_faces
= get_new_shared_faces(
286 check_face
, sorted_edge
, shared_faces
, all_sorted_faces
.keys())
287 if clear_shared_faces
:
288 shared_face
= clear_shared_faces
[0]
289 # get vertices of the edge
290 vert1
= sorted_edge
.verts
[0]
291 vert2
= sorted_edge
.verts
[1]
293 common
.debug_print(face_stuff
[0], vert1
, vert2
)
294 if face_stuff
[0].index(vert1
) > face_stuff
[0].index(vert2
):
295 vert1
= sorted_edge
.verts
[1]
296 vert2
= sorted_edge
.verts
[0]
298 common
.debug_print(shared_face
.verts
, vert1
, vert2
)
299 new_face_stuff
= get_other_verts_edges(
300 shared_face
, vert1
, vert2
, sorted_edge
, uv_layer
)
301 all_sorted_faces
[shared_face
] = new_face_stuff
302 used_verts
.update(shared_face
.verts
)
303 used_edges
.update(shared_face
.edges
)
306 shared_face
.select
= True # test which faces are parsed
308 new_shared_faces
.append(shared_face
)
310 return new_shared_faces
313 def get_new_shared_faces(orig_face
, shared_edge
, check_faces
, used_faces
):
316 for face
in check_faces
:
317 is_shared_edge
= shared_edge
in face
.edges
318 not_used
= face
not in used_faces
319 not_orig
= face
is not orig_face
320 not_hide
= face
.hide
is False
321 if is_shared_edge
and not_used
and not_orig
and not_hide
:
322 shared_faces
.append(face
)
327 def get_other_verts_edges(face
, vert1
, vert2
, first_edge
, uv_layer
):
328 face_edges
= [first_edge
]
329 face_verts
= [vert1
, vert2
]
332 other_edges
= [edge
for edge
in face
.edges
if edge
not in face_edges
]
334 for _
in range(len(other_edges
)):
336 # get sorted verts and edges
337 for edge
in other_edges
:
338 if face_verts
[-1] in edge
.verts
:
339 other_vert
= edge
.other_vert(face_verts
[-1])
341 if other_vert
not in face_verts
:
342 face_verts
.append(other_vert
)
345 if found_edge
not in face_edges
:
346 face_edges
.append(edge
)
349 other_edges
.remove(found_edge
)
352 for vert
in face_verts
:
353 for loop
in face
.loops
:
354 if loop
.vert
is vert
:
355 face_loops
.append(loop
[uv_layer
])
358 return [face_verts
, face_edges
, face_loops
]