3
3
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
4
5
5
import bpy
6
- from mathutils import Vector
6
+ from mathutils import Vector , Quaternion
7
7
8
8
import numpy as np
9
9
16
16
from typing import Tuple
17
17
18
18
19
- def export_shapekey_info (mesh : bpy .types .Mesh , shape_key_idx : int , uv_layer_idx : int , resolution : Tuple [int , int ] = (256 , 256 )) -> np .ndarray :
19
+ def _quaternion_between (v1 : Vector , v2 : Vector ) -> Quaternion :
20
+ """
21
+ Computes the Quaternion aligning the direction of v1 to v2
22
+ :param v1:
23
+ :param v2:
24
+ :return: The Quaternion that can rotate v1 to v2
25
+ """
26
+
27
+ # From https://stackoverflow.com/questions/1171849/finding-quaternion-representing-the-rotation-from-one-vector-to-another
28
+ # One can rotate a vector u to vector v with
29
+ #
30
+ # function fromVectors(u, v) {
31
+ #
32
+ # d = dot(u, v)
33
+ # w = cross(u, v)
34
+ #
35
+ # return Quaternion(d + sqrt(d * d + dot(w, w)), w).normalize()
36
+ # }
37
+ # If it is known that the vectors u to vector v are unit vectors, the function reduces to
38
+ #
39
+ # function fromUnitVectors(u, v) {
40
+ # return Quaternion(1 + dot(u, v), cross(u, v)).normalize()
41
+ # }
42
+
43
+ v1n = v1 .normalized ()
44
+ v2n = v2 .normalized ()
45
+
46
+ d = v1n .dot (v2n )
47
+ w = v1n .cross (v2n )
48
+
49
+ out = Quaternion ((1 + d , * w )).normalized ()
50
+
51
+ return out
52
+
53
+
54
+ def export_shapekey_info (mesh : bpy .types .Mesh , shape_key_idx : int , uv_layer_idx : int ,
55
+ resolution : Tuple [int , int ] = (256 , 256 ), use_normals : bool = False ) -> np .ndarray :
20
56
"""
21
57
Given a mesh, a ShapeKey index and a UV layer, uses the UV vertex coordinates to produce a 3D map storing the offset of each vertex with respect to the base ShapeKey.
22
58
The function returns a "ShapeKey Info" 4D array storing the ShapeKey offset and a counter info.
@@ -75,6 +111,13 @@ def export_shapekey_info(mesh: bpy.types.Mesh, shape_key_idx: int, uv_layer_idx:
75
111
delta = sk_vertex_coords - sk_base_vertex_coords
76
112
# print(sk_vertex_coords, sk_base_vertex_coords, delta)
77
113
114
+ # Convert the delta into a vector relative to the vertex normal
115
+ if use_normals :
116
+ v_normal = mesh .vertices [v_idx ].normal
117
+ canonical_normal = Vector ((0 , 1 , 0 ))
118
+ rot_to_canonical = _quaternion_between (v_normal , canonical_normal )
119
+ delta = rot_to_canonical @ delta
120
+
78
121
# Accumulate the delta into the output picture
79
122
if out [out_y , out_x , 3 ] == 0.0 :
80
123
out [out_y , out_x , :] = delta .x , delta .y , delta .z , 1
@@ -88,7 +131,10 @@ def export_shapekey_info(mesh: bpy.types.Mesh, shape_key_idx: int, uv_layer_idx:
88
131
return out
89
132
90
133
91
- def create_shapekey (obj : bpy .types .Object , uv_layer_idx : int , xyz_map : np .ndarray , src_sk : str ) -> None :
134
+ def create_shapekey (obj : bpy .types .Object , uv_layer_idx : int , xyz_map : np .ndarray , src_sk : bpy .types .ShapeKey ,
135
+ use_normals : bool = False ) -> None :
136
+ """Analyses the xyz delta map and creates a new ShapeKey onto the specified object using the specified
137
+ UV layer. The reference to the source ShapeKey is used to copy name and other parameters."""
92
138
93
139
if obj .type != 'MESH' :
94
140
raise Exception ("obj must be of type MESH" )
@@ -133,6 +179,13 @@ def create_shapekey(obj: bpy.types.Object, uv_layer_idx: int, xyz_map: np.ndarra
133
179
delta = Vector (xyz_map [uv_y , uv_x ])
134
180
# print(f"For poly {poly.index} coords {uv_x},{uv_y} --> delta {delta}")
135
181
182
+ # Convert the delta into a vector relative to the vertex normal
183
+ if use_normals :
184
+ v_normal = mesh .vertices [v_idx ].normal
185
+ canonical_normal = Vector ((0 , 1 , 0 ))
186
+ rot_to_canonical = _quaternion_between (v_normal , canonical_normal )
187
+ delta = rot_to_canonical .inverted () @ delta
188
+
136
189
# Adds the delta to the vertex position in the reference ShapeKey
137
190
reference_coords : Vector = reference_shape_key .data [v_idx ].co
138
191
absolute_coords = reference_coords + delta
@@ -180,21 +233,23 @@ def _save_buffer_as_image(buffer: np.ndarray, save_name: str) -> None:
180
233
def transfer_shapekey_via_uv (src_obj : bpy .types .Object , src_sk_idx : int , src_uv_idx : int ,
181
234
dst_obj : bpy .types .Object , dst_uv_idx : int ,
182
235
resolution : Tuple [int , int ],
236
+ use_normals : bool = False ,
183
237
save_debug_images : bool = False ) -> None :
184
238
"""
185
239
The main function that: i) retrieves the ShapeKey deltas from the source mesh,
186
240
ii) triangulates the delta points and fills the gaps between the points, and
187
241
iii) creates the ShapeKey on the destination object.
188
242
189
- :param src_obj:
190
- :param src_sk_idx:
191
- :param src_uv_idx:
192
- :param dst_obj:
193
- :param dst_uv_idx:
194
- :param resolution:
195
- :param save_debug_images: If True, the intermediate buffers (counts, deltas and triangulates deltas)
243
+ :param src_obj: The source object, containing ShapeKeys and UV layers
244
+ :param src_sk_idx: The index of the ShapeKey to copy
245
+ :param src_uv_idx: The index of the UV layer to be used for creating the delta map
246
+ :param dst_obj: The destination object on which a new ShapeKey will be created
247
+ :param dst_uv_idx: The UV layer of the destination object that should be used to find locations on the delta map
248
+ :param resolution: The resolution of the intermediate delta map
249
+ :param use_normals: If True, the vertex deltas will be saved and loaded as relative to the vertex normal
250
+ :param save_debug_images: If True, the intermediate buffers (counts, deltas, and triangulates deltas)
196
251
will be saved as PNG images for visual debugging.
197
- :return:
252
+ :return: Nothing
198
253
"""
199
254
200
255
map_width , map_height = resolution
@@ -205,6 +260,7 @@ def transfer_shapekey_via_uv(src_obj: bpy.types.Object, src_sk_idx: int, src_uv_
205
260
#
206
261
# Extract ShapeKey info
207
262
sk_info = export_shapekey_info (mesh = src_mesh , shape_key_idx = src_sk_idx , uv_layer_idx = src_uv_idx ,
263
+ use_normals = use_normals ,
208
264
resolution = resolution )
209
265
210
266
# Convert counts ShapeKey Info into a grey image and save
@@ -263,4 +319,5 @@ def transfer_shapekey_via_uv(src_obj: bpy.types.Object, src_sk_idx: int, src_uv_
263
319
#
264
320
# Rebuild the ShapeKey on the destination object
265
321
print (f"Creating ShapeKey '{ src_active_sk .name } ' on object '{ dst_obj .name } ' using UV layer { dst_uv_idx } ..." )
266
- create_shapekey (obj = dst_obj , uv_layer_idx = dst_uv_idx , xyz_map = filled_xyz_map , src_sk = src_active_sk )
322
+ create_shapekey (obj = dst_obj , uv_layer_idx = dst_uv_idx , xyz_map = filled_xyz_map , src_sk = src_active_sk ,
323
+ use_normals = use_normals )
0 commit comments