Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
the inspector shows BlendShape names but SkinnedMeshRenderer doesn't let me talk to it using them, makes me irate
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// For working with SkinnedMeshRenderer without failing silently or dealing with age old Unity decisions that were
/// <a href="https://forum.unity.com/threads/removing-clamping-of-blendshapes-to-the-range-0-100.504973/">never fully undone</a>.
/// </summary>
///
public static class SkinnedMeshRendererExtensions {
[Flags]
public enum SafeSkinnedMeshRendererAccessModes {
/// <summary>
/// LogError or return silent bad values like Unity does by default.
/// </summary>
None = 0,
/// <summary>
/// Prevents bad Unity behaviors like untracable LogWarnings or silent failure and allows you to catch errors if anything unexpected occurs.
/// </summary>
Safe = 1,
/// <summary>
/// When Unity imports models, it expects BlendShape values between [0,1] and then scales their value to [0,100] unless you change it on the prefab.
/// Effectively, you have to supply a BlendShape weight of 100f to get similar results to having that BlendShape set to 1f in Blender.
/// NOTE: These methods won't work on animated values, unfortunately.
///
/// When you supply this flag:
/// * All reads from the BlendShape will be divided by 100.
/// * All writes to the BlendShape will be multiplied by 100.
/// * This means you will instead be supplying values between [0f,1f].
/// </summary>
Scale100 = 2,
}
/// <summary>
/// Get BlendShape Weight by name with an exception and scaling.
/// Without the "Safe" flag, Unity will return "0" silently. With it, an IndexOutOfRangeException is thrown.
/// Additionally, a KeyNotFoundException will occur if there is no BlendShape by that name.
/// </summary>
/// <param name="smr"></param>
/// <param name="shapeName"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <returns></returns>
/// <exception cref="KeyNotFoundException"></exception>
public static float GetBlendShapeWeightByName(this SkinnedMeshRenderer smr, string shapeName, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int index = smr.sharedMesh.GetBlendShapeIndex(shapeName);
if (index == -1 && mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
throw new KeyNotFoundException($"BlendShape by name \"{shapeName}\" did not exit on mesh.");
}
return smr.GetBlendShapeWeightSafe(index, mode);
}
/// <summary>
/// Set BlendShape Weight by name with an exception and scaling.
/// Without the "Safe" flag, Unity causes a LogError without a stack trace. With it, an IndexOutOfRangeException is thrown.
/// Additionally, a KeyNotFoundException will occur if there is no BlendShape by that name.
/// </summary>
/// <param name="smr"></param>
/// <param name="shapeName"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <exception cref="KeyNotFoundException"></exception>
public static void SetBlendShapeWeightByName(this SkinnedMeshRenderer smr, string shapeName, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int index = smr.sharedMesh.GetBlendShapeIndex(shapeName);
if (index == -1 && mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
throw new KeyNotFoundException($"BlendShape by name \"{shapeName}\" did not exit on mesh.");
}
smr.SetBlendShapeWeightSafe(index, value, mode);
}
/// <summary>
/// Get BlendShape Weight with an exception and scaling.
/// Without the "Safe" flag, Unity will return "0" silently. With it, an IndexOutOfRangeException is thrown.
/// </summary>
/// <param name="smr"></param>
/// <param name="index"></param>
/// <returns></returns>
/// <exception cref="IndexOutOfRangeException"></exception>
public static float GetBlendShapeWeightSafe(this SkinnedMeshRenderer smr, int index, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int shapeCount = smr.sharedMesh.blendShapeCount;
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
if (shapeCount == 0) {
throw new IndexOutOfRangeException("Cannot get BlendShape weight for mesh with zero BlendShapes.");
}
if (index < 0 || index >= shapeCount) {
throw new IndexOutOfRangeException(
$"Cannot get BlendShape weight index {index} for mesh, valid range is: [0,{shapeCount - 1}].");
}
}
float value = smr.GetBlendShapeWeight(index);
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Scale100)) {
value /= 100f;
}
return value;
}
/// <summary>
/// Set BlendShape Weight with an exception and scaling.
/// Without the "Safe" flag, Unity causes a LogError without a stack trace. With it, an IndexOutOfRangeException is thrown.
/// </summary>
/// <param name="smr"></param>
/// <param name="index"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <exception cref="IndexOutOfRangeException"></exception>
public static void SetBlendShapeWeightSafe(this SkinnedMeshRenderer smr, int index, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe ) {
int shapeCount = smr.sharedMesh.blendShapeCount;
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
if (shapeCount == 0) {
throw new IndexOutOfRangeException("Cannot set BlendShape weight for mesh with zero BlendShapes.");
}
if (index < 0 || index >= shapeCount) {
throw new IndexOutOfRangeException(
$"Cannot set BlendShape weight index {index} for mesh, valid range is: [0,{shapeCount - 1}].");
}
}
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Scale100)) {
value *= 100f;
}
smr.SetBlendShapeWeight(index, value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.