22
33import abc
44import importlib
5- import pathlib
6- import typing
7-
85import numpy as np
6+ import pathlib
97import scipy .spatial as spatial
10- import typeguard
11-
128import skin_plus_plus
9+ import typeguard
10+ import typing
1311
12+ from .. import SkinData
1413from .. import enums
1514
1615_typing = False
1716if _typing :
18- from typing import Sequence
19-
20- from .. import SkinData
2117 from .. import _types
18+ from typing import Sequence
2219
2320T_HostNode = typing .TypeVar ("T_HostNode" )
2421
@@ -47,6 +44,25 @@ def api_name(self) -> str:
4744 i.e. pymxs or pymaya
4845 """
4946
47+ def _get_correct_bone_names (self , skin_data_bone_names : list [str ], namespace : str ) -> list [str ]:
48+ namespace = namespace if namespace .endswith (":" ) else f"{ namespace } :"
49+ clean_names = (name .split (":" )[- 1 ] for name in skin_data_bone_names )
50+ namespaced_names = [f"{ namespace } { name } " for name in clean_names ]
51+ return namespaced_names
52+ # namespace_map = {
53+ # full_name.replace(namespace, ""): full_name
54+ # for full_name in skin_data_bone_names
55+ # }
56+ # bone_names = [name.split(":")[-1] for name in skin_data_bone_names]
57+ # name_map = {name: index for index, name in enumerate(bone_names)}
58+ # for short_name, full_name in namespace_map.items():
59+ # if short_name not in name_map:
60+ # continue
61+
62+ # bone_names[name_map[short_name]] = full_name
63+
64+ # return bone_names
65+
5066 def _get_dcc_backend (self ):
5167 version = self ._get_version_number ()
5268 current_directory = pathlib .Path (__file__ ).parent
@@ -55,8 +71,9 @@ def _get_dcc_backend(self):
5571 if not sub_module_path .exists ():
5672 raise FileNotFoundError (f"Unsupported DCC version: { version } " )
5773
58- import_path = f"{ __name__ .rstrip ('core' )} { self .name } .{ version } .skin_plus_plus_{ self .api_name } "
59- print (f"dcc import_path: { import_path } " )
74+ import_path = (
75+ f"{ __name__ .rstrip ('core' )} { self .name } .{ version } .skin_plus_plus_{ self .api_name } "
76+ )
6077 backend = importlib .import_module (import_path )
6178
6279 self ._extract_skin_data = backend .extract_skin_data
@@ -102,31 +119,95 @@ def get_node_handle(self, node: T_HostNode) -> _types.T_Handle:
102119 """
103120
104121 def extract_skin_data (
105- self , node : T_HostNode , vertex_ids : Sequence [int ] | None = None
122+ self ,
123+ node : T_HostNode ,
124+ bone_names : Sequence [str ] | None = None ,
125+ normalize : bool = False ,
126+ vertex_ids : Sequence [int ] | None = None ,
106127 ) -> SkinData :
128+ if bone_names :
129+ return self ._extract_skin_data_from_bones (
130+ node , bone_names , normalize = normalize , vertex_ids = vertex_ids
131+ )
132+
107133 handle = self .get_node_handle (node )
108- return self ._extract_skin_data (handle , vertex_ids = vertex_ids )
134+ return self ._extract_skin_data (handle , vertex_ids )
135+
136+ def _extract_skin_data_from_bones (
137+ self ,
138+ node : T_HostNode ,
139+ bone_names : Sequence [str ],
140+ normalize : bool = False ,
141+ vertex_ids : Sequence [int ] | None = None ,
142+ ):
143+ skin_data = skin_plus_plus .extract_skin_data (node , vertex_ids = vertex_ids )
144+ bone_names_map = {
145+ index : bone_name
146+ for index , bone_name in enumerate (skin_data .bone_names )
147+ if bone_name in bone_names
148+ }
149+
150+ vertex_count = len (skin_data .weights )
151+ final_weights = np .zeros ((vertex_count , 8 ), dtype = np .float64 )
152+ final_bone_ids = np .full ((vertex_count , 8 ), - 1 , dtype = np .int32 )
153+ final_positions = np .zeros ((vertex_count , 3 ), dtype = np .float64 )
154+ for vertex_index , bone_ids in enumerate (skin_data .bone_ids ):
155+ new_weights = np .zeros (8 , dtype = np .float64 )
156+ new_bone_ids = np .full (8 , - 1 , dtype = np .int32 )
157+ next_influence_index = 0
158+ for new_index , bone_id in enumerate (bone_names_map ):
159+ influence_indexes = np .where (bone_ids == bone_id )[0 ]
160+ if not influence_indexes .size :
161+ continue
162+
163+ for influence_index in influence_indexes :
164+ weights = skin_data .weights [vertex_index ]
165+ new_weights [next_influence_index ] = weights [influence_index ]
166+ new_bone_ids [next_influence_index ] = new_index
167+ next_influence_index += 1
168+
169+ final_weights [vertex_index ] = new_weights
170+ final_bone_ids [vertex_index ] = new_bone_ids
171+ final_positions [vertex_index ] = skin_data .positions [vertex_index ]
172+
173+ if normalize :
174+ for index , data in enumerate (final_weights ):
175+ if not (norm := np .linalg .norm (data , 1 )):
176+ continue
177+
178+ final_weights [index ] = data / norm
179+
180+ return skin_plus_plus .SkinData (
181+ list (bone_names_map .values ()), final_bone_ids , final_weights , final_positions
182+ )
109183
110184 def apply_skin_data (
185+ self ,
186+ node : T_HostNode ,
187+ skin_data : SkinData | Sequence [SkinData ],
188+ namespace : str | None = None ,
189+ application_mode : enums .ApplicationMode = enums .ApplicationMode .order ,
190+ ) -> bool :
191+ if isinstance (skin_data , SkinData ):
192+ return self ._apply_skin_data_ (
193+ node , skin_data , namespace = namespace , application_mode = application_mode
194+ )
195+
196+ return self ._apply_skin_datas_ (node , skin_data , namespace = namespace )
197+
198+ def _apply_skin_data_ (
111199 self ,
112200 node : T_HostNode ,
113201 skin_data : SkinData ,
202+ namespace : str | None = None ,
114203 application_mode : enums .ApplicationMode = enums .ApplicationMode .order ,
115204 ):
116- handle = self .get_node_handle (node )
117-
118205 if application_mode == enums .ApplicationMode .nearest :
119206 current_positions = self .get_vertex_positions (node )
120207 kd_tree = spatial .KDTree (skin_data .positions )
121208 _ , new_indexes = kd_tree .query (current_positions )
122- index_map = {
123- old_index : new_index for old_index , new_index in enumerate (new_indexes )
124- }
125209 index_map = typeguard .check_type (
126- {
127- old_index : new_index
128- for old_index , new_index in enumerate (new_indexes )
129- },
210+ {old_index : new_index for old_index , new_index in enumerate (new_indexes )},
130211 dict [int , np .int64 ],
131212 )
132213 new_weights = np .array (
@@ -135,9 +216,134 @@ def apply_skin_data(
135216 new_bone_ids = np .array (
136217 tuple (skin_data .bone_ids [index_map [index ]] for index in index_map )
137218 )
219+ bone_names = (
220+ self ._get_correct_bone_names (skin_data .bone_names , namespace )
221+ if namespace
222+ else skin_data .bone_names
223+ )
138224 new_skin_data = skin_plus_plus .SkinData (
139- skin_data .bone_names , new_bone_ids , new_weights , skin_data .positions
225+ bone_names ,
226+ new_bone_ids ,
227+ new_weights ,
228+ skin_data .positions ,
140229 )
141230 skin_data = new_skin_data
142- print (handle )
231+
232+ elif namespace :
233+ bone_names = self ._get_correct_bone_names (skin_data .bone_names , namespace )
234+ skin_data = skin_plus_plus .SkinData (
235+ bone_names ,
236+ skin_data .bone_ids ,
237+ skin_data .weights ,
238+ skin_data .positions ,
239+ )
240+
241+ handle = self .get_node_handle (node )
143242 return self ._apply_skin_data (handle , skin_data )
243+
244+ def _apply_skin_datas_ (
245+ self ,
246+ node : T_HostNode ,
247+ skin_datas : Sequence [SkinData ],
248+ namespace : str | None = None ,
249+ ) -> bool :
250+ handle = self .get_node_handle (node )
251+
252+ def _set_weights_shape_to_size (weights : np .ndarray , size : int ):
253+ if weights .shape [1 ] < size :
254+ weights = weights .copy ()
255+ zeros = np .zeros ((weights .shape [0 ], (size - weights .shape [1 ])), dtype = np .float64 )
256+ weights = np .append (weights , zeros , axis = 1 )
257+
258+ return weights
259+
260+ def _set_bone_ids_shape_to_size (bone_ids : np .ndarray , size : int ):
261+ if bone_ids .shape [1 ] < size :
262+ bone_ids = bone_ids .copy ()
263+ zeros = np .full ((bone_ids .shape [0 ], (size - bone_ids .shape [1 ])), - 1 , dtype = np .int64 )
264+ bone_ids = np .append (bone_ids , zeros , axis = 1 )
265+
266+ return bone_ids
267+
268+ max_influence_count = max (skin_data .weights .shape [1 ] for skin_data in skin_datas )
269+ bone_name_maps : list [dict [int , str ]] = []
270+ combined_bone_names = skin_datas [0 ].bone_names .copy ()
271+ for skin_data in skin_datas :
272+ skin_data .weights = _set_weights_shape_to_size (skin_data .weights , max_influence_count )
273+ skin_data .bone_ids = _set_bone_ids_shape_to_size (
274+ skin_data .bone_ids , max_influence_count
275+ )
276+ bone_name_maps .append ({index : name for index , name in enumerate (skin_data .bone_names )})
277+ combined_bone_names .extend (skin_data .bone_names )
278+
279+ assert combined_bone_names , f"combined_bone_names: { combined_bone_names } "
280+ combined_bone_names = list (dict .fromkeys (combined_bone_names ))
281+ combined_bone_name_map = {name : index for index , name in enumerate (combined_bone_names )}
282+
283+ current_positions = self .get_vertex_positions (node )
284+
285+ temp_skin_datas = list (skin_datas )
286+ first_skin_data = temp_skin_datas .pop (0 )
287+ combined_positions = first_skin_data .positions .copy ()
288+ combined_skin_weights = first_skin_data .weights .copy ()
289+ combined_bone_ids = first_skin_data .bone_ids .copy ()
290+ for skin_data in temp_skin_datas :
291+ combined_positions = np .append (combined_positions , skin_data .positions , axis = 0 )
292+ combined_skin_weights = np .append (combined_skin_weights , skin_data .weights , axis = 0 )
293+ combined_bone_ids = np .append (combined_bone_ids , skin_data .bone_ids , axis = 0 )
294+
295+ kd_tree = spatial .KDTree (combined_positions )
296+ _ , combined_vertex_indexes = kd_tree .query (current_positions )
297+ vertex_index_map = typeguard .check_type (
298+ {old_index : new_index for old_index , new_index in enumerate (combined_vertex_indexes )},
299+ dict [int , np .int64 ],
300+ )
301+
302+ sorted_weights = np .array (
303+ tuple (combined_skin_weights [vertex_index_map [index ]] for index in vertex_index_map ),
304+ dtype = np .float64 ,
305+ )
306+ sorted_bone_ids = np .array (
307+ tuple (combined_bone_ids [vertex_index_map [index ]] for index in vertex_index_map ),
308+ dtype = np .int32 ,
309+ )
310+ skin_data_ranges = []
311+ start = 0
312+ end = 0
313+ for skin_data in skin_datas :
314+ vertex_count = len (skin_data .weights )
315+ end += vertex_count
316+ skin_data_ranges .append ((start , end ))
317+ start += vertex_count
318+
319+ for index , sorted_index in vertex_index_map .items ():
320+ skin_data_index = None
321+ for _skin_data_index , skin_data_range in enumerate (skin_data_ranges ):
322+ if skin_data_range [0 ] <= sorted_index < skin_data_range [1 ]:
323+ skin_data_index = _skin_data_index
324+ break
325+
326+ assert skin_data_index is not None , (
327+ f"index: { index } , skin_data_range: { skin_data_ranges } "
328+ )
329+ bone_ids = sorted_bone_ids [index ].copy ()
330+ bone_name_map = bone_name_maps [skin_data_index ]
331+ for bone_id_index , bone_id in enumerate (bone_ids ):
332+ if bone_id == - 1 :
333+ break
334+
335+ bone_name = bone_name_map [bone_id ]
336+ correct_id = combined_bone_name_map [bone_name ]
337+ bone_ids [bone_id_index ] = correct_id
338+
339+ sorted_bone_ids [index ] = bone_ids
340+
341+ combined_bone_names = (
342+ self ._get_correct_bone_names (combined_bone_names , namespace )
343+ if namespace
344+ else combined_bone_names
345+ )
346+ combined_skin_data = skin_plus_plus .SkinData (
347+ combined_bone_names , sorted_bone_ids , sorted_weights , current_positions
348+ )
349+ return self ._apply_skin_data (handle , combined_skin_data )
0 commit comments