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>"
22 __status__
= "production"
24 __date__
= "24 Feb 2018"
27 from math
import atan2
, cos
, sqrt
, sin
, fabs
31 from mathutils
import Vector
32 from bpy
.props
import BoolProperty
37 def get_vco(verts_orig
, loop
):
39 Get vertex original coordinate from loop
42 if vo
["vidx"] == loop
.vert
.index
and vo
["moved"] is False:
47 def get_link_loops(vert
):
49 Get loop linked to vertex
52 for f
in vert
.link_faces
:
60 for e
in loop
.vert
.link_edges
:
61 if e
.other_vert(loop
.vert
) == vert
:
62 adj_loops
.append(loop
)
63 if len(adj_loops
) < 2:
66 link_loops
.append({"l": l
, "l0": adj_loops
[0], "l1": adj_loops
[1]})
70 def get_ini_geom(link_loop
, uv_layer
, verts_orig
, v_orig
):
73 (Get interior angle of face in vertex/UV space)
75 u
= link_loop
["l"][uv_layer
].uv
76 v0
= get_vco(verts_orig
, link_loop
["l0"])
77 u0
= link_loop
["l0"][uv_layer
].uv
78 v1
= get_vco(verts_orig
, link_loop
["l1"])
79 u1
= link_loop
["l1"][uv_layer
].uv
81 # get interior angle of face in vertex space
83 v0v
= v_orig
["vco"] - v0
84 v1v
= v_orig
["vco"] - v1
85 theta0
= v0v1
.angle(v0v
)
86 theta1
= v0v1
.angle(-v1v
)
87 if (theta0
+ theta1
) > math
.pi
:
88 theta0
= v0v1
.angle(-v0v
)
89 theta1
= v0v1
.angle(v1v
)
91 # get interior angle of face in UV space
95 phi0
= u0u1
.angle(u0u
)
96 phi1
= u0u1
.angle(-u1u
)
97 if (phi0
+ phi1
) > math
.pi
:
98 phi0
= u0u1
.angle(-u0u
)
99 phi1
= u0u1
.angle(u1u
)
101 # get direction of linked UV coordinate
102 # this will be used to judge whether angle is more or less than 180 degree
103 dir0
= u0u1
.cross(u0u
) > 0
104 dir1
= u0u1
.cross(u1u
) > 0
115 def get_target_uv(link_loop
, uv_layer
, verts_orig
, v
, ini_geom
):
117 Get target UV coordinate
119 v0
= get_vco(verts_orig
, link_loop
["l0"])
120 lo0
= link_loop
["l0"]
121 v1
= get_vco(verts_orig
, link_loop
["l1"])
122 lo1
= link_loop
["l1"]
124 # get interior angle of face in vertex space
128 theta0
= v0v1
.angle(v0v
)
129 theta1
= v0v1
.angle(-v1v
)
130 if (theta0
+ theta1
) > math
.pi
:
131 theta0
= v0v1
.angle(-v0v
)
132 theta1
= v0v1
.angle(v1v
)
134 # calculate target interior angle in UV space
135 phi0
= theta0
* ini_geom
["phi0"] / ini_geom
["theta0"]
136 phi1
= theta1
* ini_geom
["phi1"] / ini_geom
["theta1"]
138 uv0
= lo0
[uv_layer
].uv
139 uv1
= lo1
[uv_layer
].uv
141 # calculate target vertex coordinate from target interior angle
142 tuv0
, tuv1
= calc_tri_vert(uv0
, uv1
, phi0
, phi1
)
144 # target UV coordinate depends on direction, so judge using direction of
145 # linked UV coordinate
149 dir0
= u0u1
.cross(u0u
) > 0
150 dir1
= u0u1
.cross(u1u
) > 0
151 if (ini_geom
["dir0"] != dir0
) or (ini_geom
["dir1"] != dir1
):
157 def calc_tri_vert(v0
, v1
, angle0
, angle1
):
159 Calculate rest coordinate from other coordinates and angle of end
161 angle
= math
.pi
- angle0
- angle1
163 alpha
= atan2(v1
.y
- v0
.y
, v1
.x
- v0
.x
)
164 d
= (v1
.x
- v0
.x
) / cos(alpha
)
165 a
= d
* sin(angle0
) / sin(angle
)
166 b
= d
* sin(angle1
) / sin(angle
)
167 s
= (a
+ b
+ d
) / 2.0
168 if fabs(d
) < 0.0000001:
172 xd
= (b
* b
- a
* a
+ d
* d
) / (2 * d
)
173 yd
= 2 * sqrt(s
* (s
- a
) * (s
- b
) * (s
- d
)) / d
174 x1
= xd
* cos(alpha
) - yd
* sin(alpha
) + v0
.x
175 y1
= xd
* sin(alpha
) + yd
* cos(alpha
) + v0
.y
176 x2
= xd
* cos(alpha
) + yd
* sin(alpha
) + v0
.x
177 y2
= xd
* sin(alpha
) - yd
* cos(alpha
) + v0
.y
179 return Vector((x1
, y1
)), Vector((x2
, y2
))
182 class MUV_TexLockStart(bpy
.types
.Operator
):
184 Operation class: Start Texture Lock
187 bl_idname
= "uv.muv_texlock_start"
189 bl_description
= "Start Texture Lock"
190 bl_options
= {'REGISTER', 'UNDO'}
192 def execute(self
, context
):
193 props
= context
.scene
.muv_props
.texlock
194 obj
= bpy
.context
.active_object
195 bm
= bmesh
.from_edit_mesh(obj
.data
)
196 if common
.check_version(2, 73, 0) >= 0:
197 bm
.verts
.ensure_lookup_table()
198 bm
.edges
.ensure_lookup_table()
199 bm
.faces
.ensure_lookup_table()
201 if not bm
.loops
.layers
.uv
:
203 {'WARNING'}, "Object must have more than one UV map")
207 {"vidx": v
.index
, "vco": v
.co
.copy(), "moved": False}
208 for v
in bm
.verts
if v
.select
]
213 class MUV_TexLockStop(bpy
.types
.Operator
):
215 Operation class: Stop Texture Lock
218 bl_idname
= "uv.muv_texlock_stop"
220 bl_description
= "Stop Texture Lock"
221 bl_options
= {'REGISTER', 'UNDO'}
223 connect
= BoolProperty(
228 def execute(self
, context
):
230 props
= sc
.muv_props
.texlock
231 obj
= bpy
.context
.active_object
232 bm
= bmesh
.from_edit_mesh(obj
.data
)
233 if common
.check_version(2, 73, 0) >= 0:
234 bm
.verts
.ensure_lookup_table()
235 bm
.edges
.ensure_lookup_table()
236 bm
.faces
.ensure_lookup_table()
238 if not bm
.loops
.layers
.uv
:
240 {'WARNING'}, "Object must have more than one UV map")
242 uv_layer
= bm
.loops
.layers
.uv
.verify()
244 verts
= [v
.index
for v
in bm
.verts
if v
.select
]
245 verts_orig
= props
.verts_orig
247 # move UV followed by vertex coordinate
248 for vidx
, v_orig
in zip(verts
, verts_orig
):
249 if vidx
!= v_orig
["vidx"]:
250 self
.report({'ERROR'}, "Internal Error")
254 link_loops
= get_link_loops(v
)
258 for ll
in link_loops
:
259 ini_geom
= get_ini_geom(ll
, uv_layer
, verts_orig
, v_orig
)
260 target_uv
= get_target_uv(
261 ll
, uv_layer
, verts_orig
, v
, ini_geom
)
262 result
.append({"l": ll
["l"], "uv": target_uv
})
264 # connect other face's UV
266 ave
= Vector((0.0, 0.0))
269 ave
= ave
/ len(result
)
271 r
["l"][uv_layer
].uv
= ave
274 r
["l"][uv_layer
].uv
= r
["uv"]
275 v_orig
["moved"] = True
276 bmesh
.update_edit_mesh(obj
.data
)
281 class MUV_TexLockUpdater(bpy
.types
.Operator
):
283 Operation class: Texture locking updater
286 bl_idname
= "uv.muv_texlock_updater"
287 bl_label
= "Texture Lock Updater"
288 bl_description
= "Texture Lock Updater"
293 def __update_uv(self
, context
):
295 Update UV when vertex coordinates are changed
297 props
= context
.scene
.muv_props
.texlock
298 obj
= bpy
.context
.active_object
299 bm
= bmesh
.from_edit_mesh(obj
.data
)
300 if common
.check_version(2, 73, 0) >= 0:
301 bm
.verts
.ensure_lookup_table()
302 bm
.edges
.ensure_lookup_table()
303 bm
.faces
.ensure_lookup_table()
305 if not bm
.loops
.layers
.uv
:
306 self
.report({'WARNING'}, "Object must have more than one UV map")
308 uv_layer
= bm
.loops
.layers
.uv
.verify()
310 verts
= [v
.index
for v
in bm
.verts
if v
.select
]
311 verts_orig
= props
.intr_verts_orig
313 for vidx
, v_orig
in zip(verts
, verts_orig
):
314 if vidx
!= v_orig
["vidx"]:
315 self
.report({'ERROR'}, "Internal Error")
319 link_loops
= get_link_loops(v
)
322 for ll
in link_loops
:
323 ini_geom
= get_ini_geom(ll
, uv_layer
, verts_orig
, v_orig
)
324 target_uv
= get_target_uv(
325 ll
, uv_layer
, verts_orig
, v
, ini_geom
)
326 result
.append({"l": ll
["l"], "uv": target_uv
})
328 # UV connect option is always true, because it raises
329 # unexpected behavior
330 ave
= Vector((0.0, 0.0))
333 ave
= ave
/ len(result
)
335 r
["l"][uv_layer
].uv
= ave
336 v_orig
["moved"] = True
337 bmesh
.update_edit_mesh(obj
.data
)
339 common
.redraw_all_areas()
340 props
.intr_verts_orig
= [
341 {"vidx": v
.index
, "vco": v
.co
.copy(), "moved": False}
342 for v
in bm
.verts
if v
.select
]
344 def modal(self
, context
, event
):
345 props
= context
.scene
.muv_props
.texlock
347 context
.area
.tag_redraw()
348 if props
.intr_running
is False:
349 self
.__handle
_remove
(context
)
351 if event
.type == 'TIMER':
352 self
.__update
_uv
(context
)
354 return {'PASS_THROUGH'}
356 def __handle_add(self
, context
):
357 if self
.__timer
is None:
358 self
.__timer
= context
.window_manager
.event_timer_add(
359 0.10, context
.window
)
360 context
.window_manager
.modal_handler_add(self
)
362 def __handle_remove(self
, context
):
363 if self
.__timer
is not None:
364 context
.window_manager
.event_timer_remove(self
.__timer
)
367 def execute(self
, context
):
368 props
= context
.scene
.muv_props
.texlock
369 if props
.intr_running
is False:
370 self
.__handle
_add
(context
)
371 props
.intr_running
= True
372 return {'RUNNING_MODAL'}
374 props
.intr_running
= False
376 context
.area
.tag_redraw()
381 class MUV_TexLockIntrStart(bpy
.types
.Operator
):
383 Operation class: Start texture locking (Interactive mode)
386 bl_idname
= "uv.muv_texlock_intr_start"
387 bl_label
= "Texture Lock Start (Interactive mode)"
388 bl_description
= "Texture Lock Start (Realtime UV update)"
389 bl_options
= {'REGISTER', 'UNDO'}
391 def execute(self
, context
):
392 props
= context
.scene
.muv_props
.texlock
393 if props
.intr_running
is True:
396 obj
= bpy
.context
.active_object
397 bm
= bmesh
.from_edit_mesh(obj
.data
)
398 if common
.check_version(2, 73, 0) >= 0:
399 bm
.verts
.ensure_lookup_table()
400 bm
.edges
.ensure_lookup_table()
401 bm
.faces
.ensure_lookup_table()
403 if not bm
.loops
.layers
.uv
:
404 self
.report({'WARNING'}, "Object must have more than one UV map")
407 props
.intr_verts_orig
= [
408 {"vidx": v
.index
, "vco": v
.co
.copy(), "moved": False}
409 for v
in bm
.verts
if v
.select
]
411 bpy
.ops
.uv
.muv_texlock_updater()
416 # Texture lock (Stop, Interactive mode)
417 class MUV_TexLockIntrStop(bpy
.types
.Operator
):
419 Operation class: Stop texture locking (interactive mode)
422 bl_idname
= "uv.muv_texlock_intr_stop"
423 bl_label
= "Texture Lock Stop (Interactive mode)"
424 bl_description
= "Texture Lock Stop (Realtime UV update)"
425 bl_options
= {'REGISTER', 'UNDO'}
427 def execute(self
, context
):
428 props
= context
.scene
.muv_props
.texlock
429 if props
.intr_running
is False:
432 bpy
.ops
.uv
.muv_texlock_updater()