Skip to content

Commit 613e633

Browse files
committed
Update IHost with bone name mapping and multi-skin application support
Added methods to handle bone name namespacing and improved skin data extraction and application in IHost. Now supports applying multiple SkinData objects with correct bone name mapping and namespace handling, as well as improved normalization and influence management. Updated batch and PowerShell scripts to reference the correct solution files and updated Maya SDK paths.
1 parent 9aeea11 commit 613e633

File tree

4 files changed

+243
-32
lines changed

4 files changed

+243
-32
lines changed

src/cpp/.sln-pyenv.ps1

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ Set-Location "$devFolder\ThirdParty"
55
$Env:THIRD_PARTY_EIGEN = (Resolve-Path -Path "./eigen")
66
$Env:THIRD_PARTY_FMT = (Resolve-Path -Path "./fmt")
77

8-
$Env:ADSK_MAYA_SDK_2022 = "C:\Program Files\Autodesk\Maya2022\devkit\devkitBase"
9-
$Env:ADSK_MAYA_SDK_2023 = "C:\Program Files\Autodesk\Maya2023\devkit\devkitBase"
10-
$Env:ADSK_MAYA_SDK_2024 = "C:\Program Files\Autodesk\Maya2024\devkit\devkitBase"
11-
$Env:ADSK_MAYA_SDK_2025 = "C:\Program Files\Autodesk\Maya2025\devkit\devkitBase"
12-
$Env:ADSK_MAYA_SDK_2026 = "C:\Program Files\Autodesk\Maya2026\devkit\devkitBase"
8+
$Env:ADSK_MAYA_SDK_2022 = "D:\Program Files\Autodesk\Maya2022\devkit\devkitBase"
9+
$Env:ADSK_MAYA_SDK_2023 = "D:\Program Files\Autodesk\Maya2023\devkit\devkitBase"
10+
$Env:ADSK_MAYA_SDK_2024 = "D:\Program Files\Autodesk\Maya2024\devkit\devkitBase"
11+
$Env:ADSK_MAYA_SDK_2025 = "D:\Program Files\Autodesk\Maya2025\devkit\devkitBase"
12+
$Env:ADSK_MAYA_SDK_2026 = "D:\Program Files\Autodesk\Maya2026\devkit\devkitBase"
1313

1414
$installedVersions = pyenv versions --bare
1515
foreach ($version in $installedVersions) {
16-
$version = $version.Split("-")[1]
17-
Write-Host $version
16+
Write-Host "-----"
17+
if ($version -like "*-*") {
18+
$version = $version.Split("-")[1]
19+
}
20+
Write-Host "version: $version"
1821
pyenv local $version
1922
$pythonPath = pyenv which python
23+
Write-Host $pythonPath
2024
$pythonRootPath = (Get-Item $pythonPath).Directory.FullName
2125
$majorMinor = $version -replace '(\d+\.\d+).*', '$1' -replace '\.', ''
2226
Set-Item "Env:PYTHON_$majorMinor" $pythonRootPath
@@ -30,3 +34,4 @@ foreach ($version in $installedVersions) {
3034

3135
Set-Location $PSScriptRoot
3236
.\skin_plus_plus.sln
37+
pause

src/cpp/open_sln-pyenv.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
rem This allows the sln to be opened by double clicking, rather than right click -> run with powershell.
22
@echo off
3-
PowerShell .sln.ps1
3+
PowerShell .sln-pyenv.ps1

src/cpp/open_sln-uv.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
rem This allows the sln to be opened by double clicking, rather than right click -> run with powershell.
22
@echo off
3-
PowerShell .sln.ps1
3+
PowerShell .sln-uv.ps1

src/skin_plus_plus/dccs/core.py

Lines changed: 229 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,20 @@
22

33
import abc
44
import importlib
5-
import pathlib
6-
import typing
7-
85
import numpy as np
6+
import pathlib
97
import scipy.spatial as spatial
10-
import typeguard
11-
128
import skin_plus_plus
9+
import typeguard
10+
import typing
1311

12+
from .. import SkinData
1413
from .. import enums
1514

1615
_typing = False
1716
if _typing:
18-
from typing import Sequence
19-
20-
from .. import SkinData
2117
from .. import _types
18+
from typing import Sequence
2219

2320
T_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

Comments
 (0)