v1.0
Overview #

interpBlendShape is a custom MPxDeformerNode that extends standard Maya blendShapes with surface-interpolated deformation. Instead of interpolating deltas linearly in object space, it tracks UV coordinates on a NURBS guide surface, allowing deformation targets to slide on the surface and also supports a Bezier inbetween mode.

Key capabilities include per-vertex UV sliding, quadratic and cubic Bezier arc paths between poses, multi-target inbetween blending, live per-frame target evaluation, and a TBB-parallelised CPU deformation path with optional OpenCL GPU acceleration.

Node ID
The node is registered with Maya type ID 0x00140541. Ensure this ID does not conflict with other plugins in your studio.
Installation #

Copy interpBlendShape.mll (Windows) to your Maya plug-in path and load it via the Plug-in Manager or with the command below.

Python
import maya.cmds as mc
mc.loadPlugin("interpBlendShape")
Node: interpBlendShape #

Create an interpBlendShape node on a mesh or NURBS curve. The node inherits from MPxDeformerNode and follows the standard Maya deformer connection convention (inputGeom / outputGeom).

Python
# Create node on selected mesh
mc.deformer("pSphere1", type="interpBlendShape")
GPU Acceleration #

The node ships with optional OpenCL GPU acceleration support, registered via MPxGPUStandardDeformer. The GPU path handles all deformation modes — non-UV blend, UV blend, and Bezier blend — fully on the GPU, allowing the deformer to benefit from Maya's parallel evaluation graph when GPU override is active.

GPU evaluation uses the same cached data built during the CPU bind pass, so there is no extra bind cost when switching between evaluation modes. The node automatically falls back to CPU evaluation if GPU resources are unavailable or if the Evaluation Manager has GPU override disabled for the node.

Enabling GPU override
GPU acceleration requires Maya's Evaluation Manager to be in Parallel mode with GPU override enabled. Go to Windows → Settings/Preferences → Evaluation Manager, set mode to Parallel, then enable GPU Override.
Performance #

Measured on a 100K vertex base mesh with 10 targets (each deforming ~50K vertices), Maya 2024, Intel Core i7-10700 @ 2.90 GHz, NVIDIA GeForce RTX 2060 SUPER. Results are approximate real-world playback rates under extreme conditions — most production rigs with fewer affected vertices will be significantly faster.

100K vertex base mesh 10 targets · ~50K deformed each Maya 2024 Intel i7-10700 @ 2.90 GHz NVIDIA RTX 2060 SUPER
Peak GPU (Parallel)
350 fps
Non-UV & standard modes
Inbetween GPU speedup
50 fps (DG) → 300 fps (Parallel+GPU)
Maya blendShape inbetween GPU
−19%
80 → 65 fps — GPU does not help
Playback fps by evaluation mode
DG (CPU) Serial + GPU override Parallel + GPU override
Key takeaways

UV Blend and Bézier modes carry a per-vertex surface evaluation cost in DG — clearly visible as low DG fps. Enabling GPU override via Serial or Parallel evaluation eliminates this gap entirely. Bézier mode is compute-heavy and not recommended for meshes above ~100K vertices.

The standout result is inbetween blending: Maya's native blendShape with inbetweens actually slows down on GPU (80 → 65 fps — GPU override provides no benefit). InterpBlendShape's OpenCL kernel handles inbetween evaluation fully on-GPU, delivering ~300 fps in Parallel mode — a 6× speedup over DG and far ahead of Maya's native deformer.

Global Attributes #
Long Name Short Type Default Description
rebind rb bool false Trigger a full rebind of all target data and surface caches. Resets to false automatically on completion.
precision prec double 0.001 Global tolerance for delta culling. Vertices whose delta is smaller than precision × targetPrecision are skipped and not stored. Range: 0–100.
origin or long 1 (local) Coordinate space for deformation. 0 = world space, 1 = local space. Local is slightly faster and recommended for most rigs.
weight wt double[] Per-target blend weight array. Index matches inputTargetGroup logical index. Keyable. Soft range 0–1.
profilingEnabled pe bool false Enables per-section performance profiling. When active, averaged timing data is printed to the Script Editor at the interval set by profilingSampleRate. Intended for performance troubleshooting, not normal use. Non-keyable.
profilingSampleRate psr long 30 Number of frames to accumulate before printing the averaged profiling report. Higher values smooth out per-frame variance. Minimum: 1. Non-keyable.
Target Group Attributes #

Each entry in inputTargetGroup[] defines one blend target and its behaviour. These are children of inputTarget[geomIndex].inputTargetGroup[targetIndex].

Long NameShortTypeDefaultDescription
targetOffset to double 1.0 Controls how far vertices lift off the guide surface. At 0, all affected vertices snap directly onto the surface and track it as it moves — no stored delta is applied. At 1, the full point delta is applied so vertices sit at their authored offset above the surface. Animating this value between 0 and 1 creates an attach-to-surface / peel-off effect. Range: 0–1.
targetCurvature tc float 1.0 Arc strength multiplier for Bezier blends. Internally computed as curvature × 2.0. Values above 1 exaggerate the arc; 0 produces a straight linear path.
targetBlendUV tbuv bool false Enables UV-space sliding on the guide NURBS surface. Requires a connected inputSurface.
targetBlendBezier tbb bool false Enables Bezier arc interpolation. Inbetween targets become control points. Supports quadratic (1 mid), cubic (2 mids), and arbitrary degree via de Casteljau.
targetBlendLive tbl bool false Recomputes closest-point binding every frame from the live connected geometry. Disables the target data cache for this group.
targetSurfaceId tsi long 0 Index into the inputSurface[] array that this target group uses for UV-blend operations.
targetRebind trb bool false Triggers a partial rebind for this specific target group only, without invalidating the rest of the cache.
targetPrecision tp double 1.0 Per-target precision multiplier. Final threshold = precision × targetPrecision. Range: 0–100.
Surface Attributes #

The inputSurface[] compound array holds one or more guide NURBS surfaces used for UV-blend operations.

Long NameShortTypeDescription
inputTargetSurface is NurbsSurface The guide NURBS surface. Connect the .worldSpace[0] or .local output of the surface shape here.
inputSurfaceOrigin iso long Space for the surface. 0 = world, 1 = local. Overrides the global origin for this surface.
inputRelativePoints idp PointArray Cached base delta vectors from surface-closest-point to base geometry, expressed in the surface tangent frame. Auto-populated on rebind.
inputSurfacePoints isp PointArray Cached base surface closest-point positions. Auto-populated on rebind.
inputSurfaceUV isuv DoubleArray Cached UV parameters of the base geometry on the surface. Stored as interleaved [u0, v0, u1, v1, ...]. Auto-populated on rebind.
Weight Attributes #
Long NameShortTypeDefaultDescription
targetWeights tw double[] 1.0 Per-vertex weight map for this target group. Index = vertex ID. Default 1.0 (full influence). Paintable via paintTargetWeights.
baseWeights bw double[] 1.0 Per-vertex base mask applied across all targets for this geometry. Equivalent to the deformer envelope mask.
targetWeightLock twl bool false Prevents paint normalization from modifying this target's weights. Locked targets are excluded from the redistribution pool.
targetWeightNormalization twn bool false When painting, weights across all normalization-enabled targets in the group sum to 1.0 per vertex. Locked targets are excluded from redistribution.
UV Blend #

UV blend mode projects each vertex onto a guide NURBS surface at bind time, stores UV coordinates and a tangent-space delta, then re-evaluates the surface at the blended UV each frame. This allows deformation targets to slide with the surface normal and tangent frame rather than blending naively in object space.

Periodic surfaces
For periodic-U or periodic-V surfaces (e.g. a cylinder), the deformer automatically detects the form, aligns UV parameters across the seam, and suppresses pole-edge artefacts via a smooth stability weight.
Limitations

Keep the following constraints in mind when setting up UV blend targets:

Pole and seam areas — vertices that bind near a surface pole or UV seam will produce unreliable tangent frames. The tangent vectors at a pole collapse to near-zero length and the seam causes a parameter discontinuity, both of which result in visible popping or incorrect deformation direction. Keep deforming vertices away from these regions, or use a guide surface whose poles and seams fall outside the area of interest.

Surface parameterisation quality — highly non-uniform parameterisation (very dense knot spacing in one region, sparse in another) causes UV deltas to be inconsistent across the surface, making fine-tuning difficult. Prefer surfaces rebuilt with uniform or chord-length parameterisation.

Rebind required after guide surface edits — modifying the guide surface shape after binding does not automatically update the cached UV correspondences. Trigger a rebind (rebind = 1) or enable targetBlendLive on the affected target group after any surface edit.

Performance — UV blend performs a surface evaluation per affected vertex per frame. On meshes with a large number of UV-blend vertices this is the dominant deformation cost. Use the profiling attributes to measure it and consider reducing the affected vertex count via per-vertex weight painting.

Prefer open-form surfaces — use a NURBS surface with open form in both U and V wherever possible. Open surfaces have well-defined, non-degenerate parameter domains across their entire extent, which gives the most stable closest-point results and tangent frames. Periodic (closed) form is supported but introduces a seam and pole constraints described above. If your rig geometry requires a closed shape, consider splitting it into multiple open-form guide surfaces covering different regions instead.

Python
# Enable UV blend on target group 0
mc.setAttr("interpBlendShape1.inputTarget[0].inputTargetGroup[0].targetBlendUV", 1)
mc.setAttr("interpBlendShape1.inputTarget[0].inputTargetGroup[0].targetSurfaceId", 0)

# Connect guide surface
mc.connectAttr(
    "nurbsSurface1.worldSpace[0]",
    "interpBlendShape1.inputSurface[0].inputTargetSurface"
)
Bezier Blend #

When targetBlendBezier is enabled, inbetween targets act as Bezier control points. With one inbetween the path is quadratic; with two it is cubic; additional inbetweens use de Casteljau evaluation.

The targetCurvature attribute scales how far the middle control points pull away from the chord. A value of 0 collapses all arcs to straight lines; 1 (default) uses the authored control-point positions; higher values amplify the arc.

Performance limit
Bezier mode with more than 10 inbetween targets is not supported. Use standard inbetween blending for dense pose libraries.
Live Blend #

Live blend (targetBlendLive) bypasses the target data cache and recomputes closest-point evaluation per frame directly from the connected target geometry. This is useful for corrective shapes driven by animated meshes, but carries a per-frame closest-point cost. For static targets, always leave live blend off.

Inbetween Targets #

Multiple entries in inputTargetItem[] create an inbetween sequence. Item logical index encodes weight: index 5000 = weight 0, index 6000 = weight 1.0, index 5500 = weight 0.5. The deformer linearly interpolates between adjacent items based on the current weight[] value.

Python — Inbetween index formula
# Index = 5000 + (weight * 1000)
# weight 0.0 → index 5000
# weight 0.5 → index 5500
# weight 1.0 → index 6000
inbetween_index = 5000 + int(weight * 1000)
Negative and greater-than-1 values
Inbetween targets technically support negative weight values and values greater than 1.0, but these ranges are locked in the UI as they have not been fully tested. If you need to drive weights outside the 0–1 range, set them directly via Python rather than through the UI editor.
Weight Painting #

The deformer exposes two weight layers — base weights (per-vertex envelope applied to all targets) and target weights (per-vertex weights per target). Both are paintable via Maya's Paint Skin Weights or interpBlendShape Edit UI for the custom painting UI or the component editor.

Target weights support two additional per-target controls:

ControlDescription
Lockable Lock a target's weights to prevent them from being affected when other targets in the same normalize group are painted.
Normalizable Targets marked with the colour icon are part of the normalize group. Painting one target's weights will redistribute the remainder across the other non-locked targets in the same group to keep the total at 1.0.
Recommended workflow

Group a small set of related targets into a normalize group — typically two or three that represent competing influences on the same region. Lock all targets outside the group before painting.

Always paint weights between two or three targets at a time with the rest locked. Painting across too many active targets at once makes normalization behaviour harder to predict and control.

Python Examples #
Python — Full setup
import maya.cmds as mc

# 1. Create deformer
node = mc.deformer("pSphere1", type="interpBlendShape")[0]

# 2. Connect guide surface
mc.connectAttr("nurbsSurface1.worldSpace[0]",
               node + ".inputSurface[0].inputTargetSurface")

# 3. Connect target mesh
mc.connectAttr("pSphereTarget.worldMesh[0]",
               node + ".inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].inputGeomTarget")

# 4. Enable UV blend
mc.setAttr(node + ".inputTarget[0].inputTargetGroup[0].targetBlendUV", 1)

# 5. Bind
mc.setAttr(node + ".rebind", 1)

# 6. Animate weight
mc.setAttr(node + ".weight[0]", 0.75)
MEL Examples #
MEL
// Create and bind
string $node[] = deformer("pSphere1" -type "interpBlendShape");
connectAttr "nurbsSurface1.worldSpace[0]"
    ($node[0] + ".inputSurface[0].inputTargetSurface");
setAttr ($node[0] + ".rebind") 1;
Command: interpBlendShape #

The interpBlendShape command is used to add and remove blend targets on an existing deformer node. It operates in edit mode only and is fully undoable. Targets can be plain full-weight shapes or inbetween shapes at fractional weights.

interpBlendShape — Flags #
Long NameShortTypeDescription
-name -n string Optional name for the target weight alias.
-inBetween -ib bool When set, the target or removal operation applies to an inbetween item rather than the full target. Required when removing a specific inbetween weight.
-target -t string, uint, string, double Edit-mode only. Arguments: [baseMesh, targetIndex, targetMesh, weight]. Connects targetMesh as a new blend target on the deformer driving baseMesh, at the given logical targetIndex and blend weight. Weight 1.0 creates a full target; fractional values (e.g. 0.5) create inbetween shapes.
-removeTarget -rt string, double Arguments: [targetName, weight]. Pass weight=1.0 to remove the entire target group including all inbetweens. Pass the inbetween weight (e.g. 0.5) with -ib to remove only that specific inbetween item. Inbetween weights are limited to three decimal places.
interpBlendShape — Examples #
Python
import maya.cmds as mc

# Add a full target at weight index 1
mc.interpBlendShape("interpBlendShape1", e=True,
    target=["baseMesh", 1, "targetMesh", 1.0])

# Add an inbetween at weight 0.5 on the same target slot
mc.interpBlendShape("interpBlendShape1", e=True,
    target=["baseMesh", 1, "inbetweenMesh", 0.5])

# Remove only the 0.5 inbetween from target "targetMesh"
mc.interpBlendShape("interpBlendShape1", e=True,
    ib=True, removeTarget=["targetMesh", 0.5])

# Remove the entire target group (all items including inbetweens)
mc.interpBlendShape("interpBlendShape1", e=True,
    removeTarget=["targetMesh", 1.0])
Command: interpBlendShapeEdit #

The interpBlendShapeEdit command mirrors or flips blend target geometry across a chosen symmetry plane. It edits the target mesh points directly and triggers a partial rebind on the affected target group when done. Supports mesh and NURBS curve targets. The command is fully undoable.

Internally it uses a BVH (Bounding Volume Hierarchy) for fast closest-vertex lookup during mirroring, and supports four surface association modes for controlling how mirrored deltas are sampled from the source side.

interpBlendShapeEdit — Flags #
Long NameShortTypeDescription
-destinationDeformer -dd string Name of the destination interpBlendShape deformer node.
-destinationShape -ds string Name of the target shape to write mirrored points into. Must be a shape driven by the destination deformer.
-mirrorMode -mm string Symmetry plane. Accepted values: "YZ" (mirrors across X), "XZ" (mirrors across Y), "XY" (mirrors across Z).
-mirrorInverse -mi bool Reverses the mirror direction — copies from the negative side to the positive side instead of the default positive-to-negative.
-flipTarget -ft bool Flip mode: instead of one-sided mirroring, copies each half of the mesh to the opposite side simultaneously, creating a fully symmetrical result from the existing deltas.
-surfaceAssociation -sa string Controls how mirrored deltas are sampled. Options: "closestPoint" (BVH nearest vertex), "closestComponent", "closestUVGlobal" (UV-space interpolation, mesh only), "closestUVShellCenter" (UV shell centre as mirror axis, mesh only).
interpBlendShapeEdit — Examples #
Python
import maya.cmds as mc

# Mirror target from +X to -X using closest point
mc.interpBlendShapeEdit(
    dd="interpBlendShape1",
    ds="targetMesh",
    mm="YZ",
    sa="closestPoint")

# Mirror from -X to +X (inverse direction)
mc.interpBlendShapeEdit(
    dd="interpBlendShape1",
    ds="targetMesh",
    mm="YZ",
    mi=True,
    sa="closestPoint")

# Flip entire target symmetrically using UV space
mc.interpBlendShapeEdit(
    dd="interpBlendShape1",
    ds="targetMesh",
    mm="YZ",
    ft=True,
    sa="closestUVGlobal")
Command: copyInterpBlendWeights #

The copyInterpBlendWeights command copies, mirrors, or flips per-vertex weight maps between deformer nodes. It works with both standard Maya blendShape nodes and interpBlendShape nodes as source or destination. The command is fully undoable and uses TBB parallelism and a BVH for fast mirrored-vertex lookup on large meshes.

Cross-deformer support
Source and destination can be different deformer types — you can copy weights from a standard blendShape into an interpBlendShape and vice versa.
copyInterpBlendWeights — Flags #
Long NameShortTypeDescription
-destinationDeformer -dd string Name of the deformer to write weights into.
-destinationShape -ds string Target shape node on the destination deformer to write weights onto.
-sourceDeformer -sd string Name of the deformer to read weights from. Omit to mirror within the destination deformer.
-sourceShape -ss string Source shape node to read weights from.
-mirrorMode -mm string Symmetry plane for mirroring. Accepted values: "YZ", "XZ", "XY".
-mirrorInverse -mi bool Reverses the mirror direction (negative to positive side).
-noMirror -nm bool Copies weights directly without any mirroring — straight source-to-destination transfer.
-flipWeights -fw bool Flip mode: symmetrically swaps weights across the mirror plane, affecting both sides simultaneously.
-ignoreNormalize -in bool Skips the weight normalization pass after copying. Useful when you want to set raw values and normalize manually.
-surfaceAssociation -sa string Controls vertex matching during mirror. Options: "closestPoint" (BVH nearest vertex, default), "closestComponent".
copyInterpBlendWeights — Examples #
Python
import maya.cmds as mc

# Mirror weights from +X to -X within the same deformer
mc.copyInterpBlendWeights(
    dd="interpBlendShape1",
    ds="pSphereShape1",
    mm="YZ",
    sa="closestPoint")

# Copy weights from a standard blendShape into interpBlendShape (no mirror)
mc.copyInterpBlendWeights(
    sd="blendShape1",
    ss="pSphereShape1",
    dd="interpBlendShape1",
    ds="pSphereShape1",
    nm=True)

# Flip weights symmetrically using closest component
mc.copyInterpBlendWeights(
    dd="interpBlendShape1",
    ds="pSphereShape1",
    mm="YZ",
    fw=True,
    sa="closestComponent")
Mirror / flip / copy — comparison with Maya built-ins #

Measured on a 250K vertex mesh. The cluster column is included as a reference comparison for weight painting workflows — not a direct equivalent. NA indicates the operation is not supported by that deformer.

Operation interpBlendShape Maya blendShape cluster
mirrorTarget 0.109 s 6.616 s
flipTarget 0.098 s 6.557 s
mirrorWeights 0.743 s 358.62 s
flipWeights 1.112 s 360.14 s
copyWeights 1.274 s 401.00 s
mirrorTarget speedup vs Maya blendShape
~60×
0.109 s vs 6.616 s · 250K verts
mirrorWeights speedup vs cluster
~480×
0.743 s vs 358.62 s · 250K verts
Feature interpBlendShape Maya blendShape
Requires topological symmetry No For best results
Works on asymmetric topology Yes — cleaner vertex matching Yes — may vary at center seam
Cross-mesh weight transfer Yes No
Vertex matching method BVH closest-point Symmetry table
Flip mode Yes (-fw) Yes
Normalize control Yes (-in) No
Profiling #

Enable profilingEnabled to print averaged per-section timing to the Script Editor. Results are averaged over profilingSampleRate frames to smooth per-frame variance before printing. The accumulator resets automatically after each report.

Python — enable profiling
# Enable profiling, print every 60 frames
mc.setAttr("interpBlendShape1.profilingEnabled", 1)
mc.setAttr("interpBlendShape1.profilingSampleRate", 60)
Output
[InterpBlendShape Timing] interpBlendShape1[0] avg(30)
  total=2.14ms (467 fps)
  surfCache=0.00 | targetCache=0.00 | partialCache=0.00
  envW=0.01 | baseW=0.00 | targetLoop=2.12
  uvLoop=1.87 | uvEval=1.54 | nonUvLoop=0.25
Roadmap & Planned Features #

The following features are planned for future releases. Feedback and suggestions are welcome — if there is something you would like to see in the plugin, please get in touch via [email protected].

FeatureStatusNotes
targetOffset weight painting Planned Per-vertex weight map for targetOffset, allowing you to paint which areas of the mesh attach to the surface and which retain their full delta. For example, painting 0 on a region pulls those vertices onto the surface while the rest of the mesh remains unaffected — giving precise control over where the surface-attach behaviour applies.
UV blend seam stability improvements In Progress Further optimisation of the seam and pole transition region to reduce artefacts when deforming vertices close to periodic surface boundaries.
Polygon mesh sliding Planned Barycentric coordinate-based sliding on a polygon mesh guide, as an alternative to the current NURBS surface path. Uses BVH triangle closest-point at bind time (no iterative solving) and barycentric interpolation at deform time — faster bind, no seam or pole artefacts, and no NURBS modelling required. Sliding quality depends on guide mesh resolution.
Suggestions welcome
If there is a feature you would like to see — additional blend modes, new command flags, workflow improvements, or anything else — feel free to reach out. All feedback is appreciated and helps shape the direction of the plugin.