The Stitch plugin might not be a tool for everyday use but it’s better to have it and not need it, than need it and not have it.
I’ve spent last few months building car interiors in Maya. Because I mostly made models for initial sketch phase, I never cared about stitches. One day my colleague Alex showed me his model of a car seat. It had split lines and stitches all over it created with his own MEL script and my eyes almost popped out. I’ve immediately decided to take it to the next level and create something a bit more powerfull and elegant – the Stitch plugin. For those who’re looking for something reliable there’s Stitch from Ticket01. For those who want to save 249€ and have some fun there’s this article covering few challenges I had to face during the project.
Introduction
The Stitch plugin is basicaly a bit more advaced tool for duplicating objects along a curve. Although I could simply model a single stitch and duplicate it along a curve I’ve decided to define the stitch parametricaly and use combination of mesh normal and curve tangent to define the stitch orientation. I also added the posibility to use an actual maya geometry instead of my predefined stitch. I’m planing to add more features in the future, so stay tuned.
Project Curve on Mesh
If we want the stitches to follow a curve on a surface, we first have to figure out how to project a nurbs curve on a mesh surface. Maya built-in function Project Curve on Mesh is out of question since it is based on unsmoothed mesh and also not available in API (except through MEL command). Let’s do it the hard way then.
Lets first smooth the base mesh to ensure the projected curve is going to follow it nicely. We’ll get the base mesh from a node’s input attribute and smooth it in-place with the following helper function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// Simple in-place mesh smoothing function MStatus StitchNode::generateSmoothMesh(MObject& mesh, int divisions) { MStatus status(MStatus::kSuccess); MFnMesh meshFn(mesh); MMeshSmoothOptions smoothOptions; smoothOptions.setDivisions(divisions); smoothOptions.setKeepBorderEdge(false); meshFn.generateSmoothMesh(mesh, &smoothOptions, &status); CHECK_MSTATUS_AND_RETURN_IT(status); return status; } |
As for the curve projection, it is actually pretty easy to project a single point on mesh via MFnMesh::getClosestPoint() function. So we’ll simply divide the curve and find the closest point on mesh to each sample point.
But…
Unfortunately this only works when the mesh surface doesn’t have any gaps/holes and is larger than the original curve. To check whether the sampled point actually lies above the surface we first take the surface normal of the closest point and shoot a ray from the original point back to the surface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Find closest point on mesh, returns true if the original point lies over the mesh ////////////// bool StitchNode::closestPointOnMesh(MObject& mesh, MPoint& point, MVector& normal){ MFnMesh meshFn(mesh); // First get the closest point on the surface and mesh normal MPoint closestPoint; meshFn.getClosestPointAndNormal(point, closestPoint, normal, MSpace::kObject); // Shoot a ray from original point and look for a collision MFloatPoint fClosestPoint; MMeshIsectAccelParams accelParams = meshFn.autoUniformGridParams(); int hitFace, hitTriangle; float hitBary1, hitBary2, hitRayParam; bool overMesh = meshFn.closestIntersection(point, normal, NULL, NULL, false, MSpace::kObject, 99999.9f, true, &accelParams, fClosestPoint, &hitRayParam, &hitFace, &hitTriangle, &hitBary1, &hitBary2, (float)1e-6); point = closestPoint; return overMesh; } |
If we ignore all the points that lie outside the surface, we end up with a projected curve that doesn’t start on the boundary of the mesh even though the original curve is crossing it. That’s why we keep the points that come immediately before or after the points lying inside the surface. This solution isn’t completely accurate though – points on the boundary are not exact projection of the curve.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// Project curve on mesh MStatus StitchNode::projectCurveOnMesh(MObject& mesh, MObject& curve, int samples, MObjectArray& projectedCurves) { MStatus status(MStatus::kSuccess); projectedCurves.clear(); // Distribute samples along the curve MFnNurbsCurve curveFn(curve, &status); double domainStart = 0; double domainEnd = curveFn.length(); double distance = (domainEnd - domainStart) / samples; MPointArray curvePoints; bool onSrf = false; for (int i = 0; i < (samples + 1); i++) { MPoint point; MVector normal; status = findPointAtLength(curve, (domainStart + distance*i), point); // Get the closest point and info about its position bool prevOnSrf = onSrf; onSrf = closestPointOnMesh(mesh, point, normal); // If two points in the row lie outside the surface, delete the first one if (!prevOnSrf && !onSrf) curvePoints.clear(); curvePoints.append(point); // If the previous point lies inside the surface and the current one outside, // finish the point list and create curve. Repeat. if ((prevOnSrf && !onSrf) || (onSrf && i == samples)) { if (curvePoints.length() < 2) continue; MObject projectedCurve; status = generateNurbsCurve(curvePoints, projectedCurve); projectedCurves.append(projectedCurve); curvePoints.clear(); } } return status; } |
In the source files I’m also using function MFnNurbsCurve::findLengthFromParam(), which is not available in versions older than Maya 2016 extension 2. If you’re writing your code for older version, you can get away with using parameter instead of length. This might result in uneven spacing of samples, which can be fixed by reparametrizing the input curve.
Stitch placement and orientation
Now the fun part…
For each stitch we need 3 vectors that will define orientation matrix. I learned this from a post from Chad Vernon (yes, THE Chad Vernon) in Aligning an object’s rotation to that of a vector discussion. For the vector T we use MFnNurbsCurve::tangent() or create a vector from two neighboring points. That way the stitch geometry will follow the direction of the curve. The vector N comes from MFnMesh::getClosestPointAndNormal() which will orient the stitch normal to the mesh surface. Finally the last vector is a simple cross product of the two.
Here you can see how it works in the code. First we have to get position of our samples on the projected curve. Then we find the closest point on mesh, surface normal and define the tangent vector. Finally we construct an orientation matrix and call helper function which builds the oriented stitch geometry.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
MStatus StitchNode::distributeStitches(MObject& curve, MObject& mesh, MPointArray& points, MIntArray& counts, MIntArray& indices) { MStatus status(MStatus::kSucces); MFnNurbsCurve curveFn(curve); MFnMesh meshFn(mesh); // Calculate distribution of stitches int count = int(round(curveFn.length() / m_distance)); double distance = curveFn.length() / count; for (int i = 0; i < count; i++) { // Get endpoints of the stitch MPoint pointA, pointB; status = findPointAtLength(curve, (distance*i), pointA); status = findPointAtLength(curve, (distance*(i+1)), pointB); // Get surface normal for each point MVector normal, normalB; if (!closestPointOnMesh(m_mesh, pointA, normal)) continue; else if (!closestPointOnMesh(m_mesh, pointB, normalB)) continue; // Get vector T and crossproduct MVector tangent = pointB - pointA; double length = tangent.length(); MPoint position = pointA + tangent / 2; tangent.normalize(); MVector cross = tangent^normal; // Feed XYZ values of each vector in 4x4 matrix double orientation[4][4] = {{ tangent.x, tangent.y, tangent.z, 0 }, { cross.x, cross.y, cross.z, 0 }, { normal.x, normal.y, normal.z, 0 }, { 0, 0, 0, 1 } }; MMatrix orientationMatrix(orientation); // Generate stitch generateStitch(length, points, counts, indices, position, orientationMatrix); } return status; } |
Generating stitch geometry
This is the function that creates lists of points, vertex counts and vertex indices we need to to feed in MFnMesh::create() function. It looks a bit complicated but only because we’re defining a mesh from scratch. If you want to duplicate model you built in Maya, you simply call MFnMesh::getPoints() and MFnMesh::getVertices() to get these values. Note how we “multiply” the geoPoints with the orientation matrix and move them to the new position.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
MStatus StitchNode::generateStitch(double length, MPointArray& points, MIntArray& counts, MIntArray& indices, MPoint& position, MMatrix& orientation){ MStatus status(MStatus::kSuccess); // Calculate correct dimensions of a stitch // Variables starting with m_ come from node input attributes double radius = m_thickness / 2; double outside = length / 2; double inside = outside - m_thickness; // Mesh points double polyPoints[16][4] = {{ -outside, -radius+m_skew, -m_thickness, 0 }, { outside, -radius-m_skew, -m_thickness, 0 }, { -outside, radius+m_skew, -m_thickness, 0 }, { outside, radius-m_skew, -m_thickness, 0 }, { -inside, radius+m_skew/2, 0, 0 }, { inside, radius-m_skew/2, 0, 0 }, { -inside, -radius+m_skew/2, 0, 0 }, { inside, -radius-m_skew/2, 0, 0 }, { inside, -radius, m_thickness, 0 }, { outside, -radius, m_thickness, 0 }, { inside, radius, m_thickness, 0 }, { outside, radius, m_thickness, 0 }, { -inside, -radius, m_thickness, 0 }, { -outside, -radius, m_thickness, 0 }, { -outside, radius, m_thickness, 0 }, { -inside, radius, m_thickness, 0 } }; // Number of vertices in each polygon int polyCounts[12] = { 4,4,4,4,4,4,4,4,4,4,4,4 }; // Indices of vertices forming each polygon int polyIndices[48] = { 15, 12, 6, 4, 14, 15, 4, 2, 13, 14, 2, 0, 12, 13, 0, 6, 11, 9, 1, 3, 10, 11, 3, 5, 8, 10, 5, 7, 9, 8, 7, 1, 0, 1, 7, 6, 6, 7, 5, 4, 4, 5, 3, 2, 2, 3, 1, 0 }; MPointArray geoPoints(polyPoints, 16); MIntArray geoCounts(polyCounts, 12), geoIndices(polyIndices, 48); unsigned firstIndex = points.length(); // Append points for (unsigned i = 0; i < geoPoints.length(); i++) { geoPoints[i] *= orientation; // Apply orientation matrix geoPoints[i] += position; // Move to correct position points.append(geoPoints[i]); } // Append counts for (unsigned i = 0; i < geoCounts.length(); i++) counts.append(geoCounts[i]); // Append indices for (unsigned i = 0; i < geoIndices.length(); i++) indices.append(firstIndex + geoIndices[i]); return status; } |
Conclusion
The Stitch plugin is a great tool to add detail to your model. Although the projection method works quite nicely, it is somewhat primitive and definitely needs improvements. Let me know if you have any questions or suggestions. As always the displayed code is just a scoup from the plugin. For the complete code check the source files.
37 Comments
MATTHEW STEVENS
06 Nov 2022 - 10:22 amHi stepan, can you shed some light on using custom geom it seems to be greyed out for me. Maya2017, win10, Also it would be amazing if the curve function could be added to seameasy plugin for those more complex stitch arrangements.
MATTHEW STEVENS
21 Oct 2022 - 3:08 pmHi,
Does anyone know how to add custom geom. When i tick the box it replaces the stitch with a elongated cube but the option to map is geom is still greyed out
thanks
matt
MATTHEW STEVENS
21 Oct 2022 - 12:08 pmHi Stepan did you ever get curves to work in seameasy?
thanks
MATTHEW STEVENS
21 Oct 2022 - 12:07 pmHi Stepan
Does stitch node work in maya 2022?
thanks
Matt
Sean
26 Aug 2022 - 12:36 amwhere to put sauce code?:
Jason
03 Jan 2022 - 11:42 pmHello, Stepan,
I’m having trouble with installing the stitch plugin..
I’ve got my .mll in the plugins folder, the mel file in scripts, and the icon file in the prefs\icons folder, but I’m still unable to load the plugins for this (or crossSections).
I’m using Maya 2022—is that part of the problem?
Also thank you so much for all these incredible tools!
Stepan Jirka
10 Jan 2022 - 8:29 amHi Jason,
I’m afraid most of my plug-ins are not compiled for 2022 yet. I’ll see if I can fix that sometimes in the beginning of this year.
Stepan
Jason
18 May 2022 - 8:20 pmThanks for replying! I look forward to the upgrades~~
A quick question, though: is the difference between Stitch Node and the StitchEasy plugin just the custom curve projection element, instead of relying on edge loops as the basis for the stitches?
Jason
10 Aug 2022 - 6:11 pmI was wondering if there’s any way we could just take a load off you; is there a simple process we could use to recompile the codes for each successive version of Maya without having to come to you every time? I’m on 2023 now and as one might expect the plugins aren’t working but I hate bothering you about it….
tet
17 Nov 2022 - 6:11 amHi J, can this plug-in do the cross stitch? like something they do in the steering wheel?
John
30 Aug 2021 - 8:56 amHi,
how do i put it in shelf? i cant figure it out, is there a shelf file or something to copy in the script editor?
Thx
Stepan Jirka
30 Aug 2021 - 9:18 amHi John,
this one doesn’t seem to have a shelf file included but you can add buttons to your shelf by typing the command in the Maya command line and dragging it from there to your shelf with the middle mouse button. The command is simply “stitch” (without apostrophes)
Stepan
Thisara Yasasvi Jayasekara
22 Mar 2021 - 6:02 pmAwesome, I’m a student CGI artist from Sri Lanka. This plugin helps me a lot. I will donate some money soon.
Stepan Jirka
23 Mar 2021 - 6:08 pmThank you!
seungnamyang
30 Apr 2020 - 9:24 amHello Stepan.
I really want to buy stitch node 2020 version.
Do you have a plan to selling this plugin?
Stepan Jirka
11 May 2020 - 8:23 pmHonestly, I’m not planning to update StitchNode for 2020. Sorry
Hamed Mousavi
14 Feb 2020 - 8:48 amHi Stepan
Do you have plans of updating the script for Maya 2020?
matthew stevens
29 Mar 2019 - 11:25 pmHi,
first off thanks for giving away your scripts. A true community spirit. I have been using seameasy for a while now and i love it. Just installed this plugn as i need to do more complex stitch pattern but i have noticed unlike seameasy there are no uvs on the generated stitches. Am i missing something?
thanks in advance
matt
Stepan Jirka
01 Apr 2019 - 8:58 amHi Matthew
this plug-in is an older one and unfortunately I never programmed the UV part of it. If I ever find the time and motivation I’d like to just add possibility to use curves in the StitchEasy.
Regards
Stepan
Matthew Stevens
01 Apr 2019 - 9:29 amHi Stepan,
Thanks for your feedback i managed to get around the problem but transfering uvs from seameasy geo to stitch geo. Thanks for you reply. You really must be rewarded for your efforts! Please let me know how, if you dont want to take money let me know a prefered charity and i will donate to it on your behalf
thanks for you amazing contribution to the community
thanks
matt
Stepan Jirka
01 Apr 2019 - 12:23 pmHi Matt,
I’m glad you found a workaround for the UV issue!
As for the donation – I have always admired volunteers who make 3d printed arm prosthetics, so if you really mean it, a donation to e-NABLE foundation would mean a lot to me.
Stepan
Josh
17 Dec 2018 - 11:39 pmHi Stepan
Awesome work on the script, been using it on a pretty regular basis! Would this script work on linux?
Stepan Jirka
20 Dec 2018 - 10:22 amHi Josh,
I’m glad you find it helpful. You could try to compile it for linux from the source code but I myself have no experience compiling for linux.
Stepan
GooDroN
29 May 2018 - 1:34 pmGood day!!! \~/
I’m wildly sorry!
StitchNode does not work!!! (Maya 2018.3)
C:\Users\username\Documents\maya\2018\plug-ins\stitchNode.mll
C:\Users\username\Documents\maya\2018\scripts\AEstitchNodeTemplate.mel
C:\Users\username\Documents\maya\2018\prefs\icons\stitchNode.png
https://yadi.sk/i/C7jIq6A93WfLTy
SEAMS EASY works fine!!! (Maya 2018.3)
Will you advise something?
Thank you!!!
Stepan Jirka
29 May 2018 - 5:54 pmHi,
I’m glad the SeamsEasy works. As for the StitchNode, it unfortunately doesn’t work in 2018 at the moment. I’ll see if I can at some point transfer the functionality of StitchNode in the SeamsEasy plugin.
Stepan
GooDroN
29 May 2018 - 8:21 pmThat would be wonderful !!!
And thank you very much for the tool !!! \ ~ /
Simon
01 Mar 2018 - 2:29 pmHi Stepan,
I have a beginner question. Which part of the Shelfbutton.mel has to be copied into the script editor ?
The content of the mel is:
// The stitch command can be called with following flags
// -d or -distance, takes double
// -c or -count, takes int
// -l or -length, takes double
// -th or -thickness, takes double
// -sk or -skew, takes double
// -t or -translate, takes double double double
// -r or -rotate, takes double double double
// -s or -scale, takes double double double
if(
pluginInfo -q -l "stitchNode.mll"
==0)loadPlugin “stitchNode”;
stitch -d 5 -th 0.5 -sk 0.2;
source AEstitchNodeTemplate;
refreshEditorTemplates;
Sorry for another noob question 😉
Stepan Jirka
01 Mar 2018 - 2:33 pmHi Simon,
You can copy the whole thing. But only the line with “stitch” is actually important.
Best regards
Stepan
Simon
01 Mar 2018 - 2:57 pmThanks very much for your super quick answer 😉 Work perfectly. Tool is amazing.
Seb James
24 Feb 2017 - 2:53 pmHi again,
Any chance you could just say where to install the parts of the download? (Noob question, sorry!)
Thanks,
Seb
Stepan Jirka
24 Feb 2017 - 3:48 pmHi Seb,
here’s example:
C:\Users\username\Documents\maya\2017\plug-ins\stitchNode.mll
C:\Users\username\Documents\maya\2017\scripts\AEstitchNodeTemplate.mel
C:\Users\username\Documents\maya\2017\prefs\icons\stitchNode.png
Load the plugin through Plug-in manager, simply copy content of shelfButton.mel in script editor and drag on your shelf and assign icon if you want. shelfButton.mel also contains short list of flags u can use with “stitch” command.
Seb James
24 Feb 2017 - 3:50 pmPerfect, thanks very much!
Sascha
21 Feb 2017 - 10:31 amThis looks great man, thanks for sharing 🙂
Is it only for Maya2017?
Stepan Jirka
21 Feb 2017 - 10:37 amHi Sascha,
I can compile it for 2016 EXT 2 if that helps, but as I’m using some functions from the latest API I’d have to rewrite the code to make it work in 2016 or lower. In few days I expect to release another plugin with stitches distributed along edges rather that projected curve, that one will be for 2016, 2016.5, 2017
Seb James
16 Feb 2017 - 1:38 pmMany thanks for giving this away for free, it looks great! I’m looking forward to checking out your seam plug-in; I do quite a lot of modelling of seats for aircraft, and this will save SO much time.
Stepan Jirka
16 Feb 2017 - 6:29 pmHi Seb. I’m currently working on a car seat project and its already a huge time and ass saver. I still want to optimize few things, but it shoudn’t take long.
Fan
15 Feb 2017 - 7:02 pmGreat work! Thank you for sharing all your excellent scripts.