Project a mesh to another with Maya API (English Translation)
Par Narann le dimanche, 31 juillet 2011, 20:28 - Script et code - Lien permanent
This is the english translation of a tutorial I've done that will allow you to project a mesh to another.
This is something that can be useful but most is pretty "fun" to do (some would say everything is relative :seSentCon: ) and it's a good way to learn fundamentals of matrice operations having a both simple and concrete example.
Amateur of the API, this tutorial is for you!
In advance, sorry for english. :baffed:
Here is what we want to achieve:
A mesh projected on another.
And the scene from which we start:
Sommaire
- Theory
- Start code
- Attributes preparation
- Prepare the scene
- The compute method
- Matrices explained to CG artists
- Back to the code!
- Go through each vertex and modify it
- Build a mesh
- Source code and conclusion
- Update: Do this without Python
Theory
Once again, we begin with theory.
In facts, you'll see it's pretty simple because the harder part (the intersection) will be handle through an API call:
OpenMaya.MFnMesh.closestIntersection( ... )
This method handle intersection of a ray (point+direction) on a mesh and return some infos (the most important: The position of the projected point).
Basically, we need three things:
- A point of origin (the one we want to project)
- A direction (a vector)
- A destination mesh
The point of origin will be, of course, each vertex of the mesh to project (here, vertex numbers's are in yellow).
Direction will be the normal of the vertex to project (in green on the image. Okay, we do not see too much but put it a little good will damn! :cayMal: ).
And the destination mesh will obviously be the mesh that will receive the plan (in our case, a sphere).
So we recover, each time, a point and a normal (yellow crosses).
Start code
Here is the the base code:
import sys import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx kPluginNodeTypeName = "projectMesh" kPluginNodeId = OpenMaya.MTypeId( 0x80000 ) # Node definition class projectMeshNode( OpenMayaMPx.MPxNode ) : # constructor def __init__( self ) : OpenMayaMPx.MPxNode.__init__( self ) def compute( self, plug, data ) : print "compute" return OpenMaya.MStatus.kSuccess # creator def nodeCreator(): return OpenMayaMPx.asMPxPtr( projectMeshNode() ) # initializer def nodeInitializer() : return # initialize the script plug-in def initializePlugin( mobject ) : mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeTypeName, kPluginNodeId, nodeCreator, nodeInitializer ) except: sys.stderr.write( "Failed to register node: %s\n" % kPluginNodeTypeName ) raise # uninitialize the script plug-in def uninitializePlugin( mobject ): mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( "Failed to unregister node: %s\n" % kPluginNodeTypeName ) raise
If you already had wrote a Maya node in Python, this code shouldn't make you too afraid.
Quick to explain for others :baffed: .
import sys import maya.OpenMaya as OpenMaya import maya.OpenMayaMPx as OpenMayaMPx
Import the main modules.
- The sys module is used to create error message when Maya load/unload the plugin (see below).
- The other two are used to call Maya API methods.
Nothing complicated.
kPluginNodeTypeName = "projectMesh" kPluginNodeId = OpenMaya.MTypeId( 0x80000 )
- kPluginNodeTypeName is a simple variable called below to give a name to you node type.
- kPluginNodeId is a value serving as an id for the node when it is written in binary files (mb). 0x80000 to 0xfffff are used by Maya samples. See (documentation|http://download.autodesk.com/us/maya/2011help/API/class_m_type_id.html] for more informations.
# Node definition class projectMeshNode( OpenMayaMPx.MPxNode ) : # constructor def __init__( self ) : OpenMayaMPx.MPxNode.__init__( self ) def compute( self, plug, data ) : print "compute" return OpenMaya.MStatus.kSuccess
Here, the class is an instance of the MPxNode object which is himself a class done to create custom nodes (it's a fairly complex part I will not go througth in this tutorial as it deserves a full post :jdicajdirien: ).
The __init__ method is the first executed when the class is created. It simply initialise the MPxNode class.
The compute method is the one we will work the most. It's a herited methode from MPxNode. The part of the code "which do something". :sourit:
If you're not familiar with classes and Python, this part of the code may seem complex, but don't worry, the only important part is the compute method.
# creator def nodeCreator(): return OpenMayaMPx.asMPxPtr( projectMeshNode() )
A function, executed when initializing the plugin, which returns a pointer to the created class (and therefore, the node).
Python having no notion of pointer and Maya having need, in particular, to initialize the plugins, Autodesk has developed the OpenMayaMPx.asMPxPtr method (more informations here).
Once again, it's something basic, you put it, you do not think :bete: .
# initializer def nodeInitializer() : return
This method is also called during the node creation and allows (among others) to initialize the node attributes. This will be the first one that we will fulfill. For now, it make nothing at all.
# initialize the script plug-in def initializePlugin( mobject ) : mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.registerNode( kPluginNodeTypeName, kPluginNodeId, nodeCreator, nodeInitializer ) except: sys.stderr.write( "Failed to register node: %s\n" % kPluginNodeTypeName ) raise # uninitialize the script plug-in def uninitializePlugin( mobject ): mplugin = OpenMayaMPx.MFnPlugin( mobject ) try: mplugin.deregisterNode( kPluginNodeId ) except: sys.stderr.write( "Failed to unregister node: %s\n" % kPluginNodeTypeName ) raise
Let me tell you, this is really a "copy-paste" I've done from examples. Basically, these functions are called when loading/unloading plugins. They are used to register/unregister the plugins from Maya sessions.
Their behavior is simple, I invite you to review this code snippet for yourself (it doesn't harm! :gniarkgniark: ).
At this point you should be able to create your python node and load it in Maya
And by creating it as follow:
createNode projectMesh; // Result: projectMesh1 //
To the extent that there is no attribute, this node does absolutely nothing! :sourit:
Attributes preparation
As promised, we will start by the nodeInitializer() method which initialize the node attributes.
We will need three attributes:
- Two inputs: The mesh projecting his vertex and the mesh which recieve them.
- An output: The output, projected mesh.
Here we go! :grenadelauncher:
# initializer def nodeInitializer() : typedAttr = OpenMaya.MFnTypedAttribute() # Setup the input attributes projectMeshNode.inputMeshSrc = typedAttr.create( "inputMeshSrc", "inMeshSrc", OpenMaya.MFnData.kMesh ) typedAttr.setReadable(False) projectMeshNode.inputMeshTarget = typedAttr.create( "inputMeshTarget", "inMeshTrg", OpenMaya.MFnData.kMesh ) typedAttr.setReadable(False) # Setup the output attributes projectMeshNode.outputMesh = typedAttr.create( "outputMesh", "outMesh", OpenMaya.MFnData.kMesh ) typedAttr.setWritable(False) typedAttr.setStorable(False) # Add the attributes to the node projectMeshNode.addAttribute( projectMeshNode.inputMeshSrc ) projectMeshNode.addAttribute( projectMeshNode.inputMeshTarget ) projectMeshNode.addAttribute( projectMeshNode.outputMesh ) # Set the attribute dependencies projectMeshNode.attributeAffects( projectMeshNode.inputMeshSrc, projectMeshNode.outputMesh ) projectMeshNode.attributeAffects( projectMeshNode.inputMeshTarget, projectMeshNode.outputMesh )
The first line:
typedAttr = OpenMaya.MFnTypedAttribute()
Creates an "object" (known in the Maya API as "Function Set") that will help us to manipulate the attributes (especially build them):
# Setup the input attributes projectMeshNode.inputMeshSrc = typedAttr.create( "inputMeshSrc", "inMeshSrc", OpenMaya.MFnData.kMesh ) typedAttr.setReadable(False) projectMeshNode.inputMeshTarget = typedAttr.create( "inputMeshTarget", "inMeshTrg", OpenMaya.MFnData.kMesh ) typedAttr.setReadable(False) # Setup the output attributes projectMeshNode.outputMesh = typedAttr.create( "outputMesh", "outMesh", OpenMaya.MFnData.kMesh ) typedAttr.setWritable(False) typedAttr.setStorable(False)
We create attributes. Nothing complicated (see documentation).
Some details on used arguments:
- The full attribute name (long name).
- The short attribute name (short name).
- The attribute "type" (as API type).
The following methods each attribute declaration (setReadable, setWritable, setStorable) add special things to the latest created attribute (see documentation for more precisions).
# Add the attributes to the node projectMeshNode.addAttribute( projectMeshNode.inputMeshSrc ) projectMeshNode.addAttribute( projectMeshNode.inputMeshTarget ) projectMeshNode.addAttribute( projectMeshNode.outputMesh )
As the comment said, this part add/connect attributes created above to the node.
# Set the attribute dependencies projectMeshNode.attributeAffects( projectMeshNode.inputMeshSrc, projectMeshNode.outputMesh ) projectMeshNode.attributeAffects( projectMeshNode.inputMeshTarget, projectMeshNode.outputMesh )
This part is very important! :papi:
It allows you to define "dependencies" between attributes.
In our case:
- If the inputMeshSrc attribute change, outputMesh attribute also change.
- If the inputMeshTarget attribute change, outputMesh attribute also change.
If these lines are not set, the compute method of the node we are creating will never be launched. The node will never be updated.
You can download the python node in the current state here:
Prepare the scene
Before we actually code the behavior of the node, we need to connect some geometry to our future node.
Create a scene that looks like this:
Also create a third mesh, which will return the geometry of our future node (which will be the projected mesh).
In my case: A pSphere. But it can be anything. As it is a mesh node.
You can even create the mesh node by hand.
Place it to the center of the scene (0,0,0) so there have no offset between the projected mesh and his target.
To avoid having to create the connections each time, here are two small MEL codes that help you to test your node (your nodes must be well named).
To load all:
loadPlugin( "monRepertoire/projectMesh.py" ); createNode projectMesh; connectAttr -f projectMesh1.outputMesh pSphereShape2.inMesh; connectAttr -f pPlaneShape1.worldMesh[0] projectMesh1.inputMeshSrc; connectAttr -f pSphereShape1.worldMesh[0] projectMesh1.inputMeshTarget;
And unload all:
delete projectMesh1; flushUndo; unloadPlugin( "projectMesh" );
And that's it! Now take a deep breath, we jump!
The compute method
The first thing to test is the presence of a connection on your outputMesh attribute. Actually, if your node is connected to anything, it should not be calculated:
def compute( self, plug, data ) : if plug == self.outputMesh: print "compute" else: return OpenMaya.MStatus.kUnknownParameter return OpenMaya.MStatus.kSuccess
Once we are sure connections are made, we get input attributes:
if plug == self.outputMesh: # get the inputMeshTarget (return MDataHandle) inMeshSrcHandle = data.inputValue( self.inputMeshSrc ) inMeshTargetHandle = data.inputValue( self.inputMeshTarget )
This is a "connection" to the block of data attributes. This is the first step to get the value (here it's a kMesh so it will be a little different) of an attribute.
Python being an untyped language (both its main strength but also its main flaw ...), I tend to write in comments the Maya API type of data that I get.
Otherwise, it's quick to not knowing at all what type is what variable (types in the Maya API is no lack :aupoil: ).
However, everyone has their own method! If you have a over-developed cortex, if you want to play the "I dont care types are for noobs", if a code that you are the only person who can read it give you the feeling to be a strong male and if you want to justify your BAC +5 (in this day and age it's certainly not your salary that should do it). Do not hesitate, code as
TDpork: No comments, one letter variables and whatnot like that... Your colleagues will return the favor. :sourit:But if you're more modest and would quickly learn the Maya API, I urge you to write Maya API types directly in comments of your code. Besides being clearer, it still requires to known/search, when writing variables, what type it is.
After that, we check our two connected attributes are meshs:
#we check the API type we've got here (we need kMesh) if inMeshSrcHandle.type() == OpenMaya.MFnData.kMesh and inMeshTargetHandle.type() == OpenMaya.MFnData.kMesh : print "cool" else: return OpenMaya.MStatus.kInvalidParameter
And we get them as such:
#we check the API type we've got here (we need kMesh) if inMeshSrcHandle.type() == OpenMaya.MFnData.kMesh and inMeshTargetHandle.type() == OpenMaya.MFnData.kMesh : # return a MObject meshSrc = inMeshSrcHandle.asMesh() meshTarget = inMeshTargetHandle.asMesh() print "cool" else: return OpenMaya.MStatus.kInvalidParameter
I invite you to look at the MDataHandle documentation just to see what we can get from an attribute.
As mentioned in the comment, we get a MObject. This object type is the "all-purpose-type" of Maya.
This MObject is just a transitionnal object. Actually, it is rarely used directly.
In Maya, to modify/malipulate objects, we often use "Function Set". They follow the pattern: MFn*Type*.
Here, to manipulate meshs, we will get a function set of mesh: MFnMesh.
# get the MFnMesh of the twice attr mFnMeshSrc = OpenMaya.MFnMesh( meshSrc ) mFnMeshTarget = OpenMaya.MFnMesh( meshTarget )
What interests us now is to have a list of all vertices of the "source mesh" (one which will be projected to the "target mesh") to create another array that will contain changed vertex positions:
outMeshMPointArray = OpenMaya.MPointArray() # create an array of vertex wich will contain the outputMesh vertex mFnMeshSrc.getPoints(outMeshMPointArray) # get the point in the space
The first line creates an array of type MPointArray.
The second line fills it with the values of the "source mesh" vertex.
How to write is a bit confusing ("upside down" will say some :reflechi: ) but that's the way getPoint works, as many other functions of the Maya API.
Rather than return the result, it is stored in the variable given as argument.
We now have a MPointArray filled with his vertex positions.
The idea now is to change vertex positions to make them matching the projected position on the "target mesh".
But here... The vertex positions that you get in your MPointArray are in "object space". That mean, relative to the center of the object.
Nous allons nous heurter à un vrai problème. The big one! The ultimate: The matrices! *Voix qui résonne* :enerve: We will face a real problem. The big one! The ultimate: The Matrix! *Voice echoing* :enerve:
Matrices explained to CG artists
When you are CG artists, we hear about it without really knowing what it is :bete: .
Add to that what we find on the net is very academic and "too mathematical" (It shows how to multiply a matrix without explaining why it is necessary to do so).
All this so that we don't necessarily see the connection with our work.
I'll modestly try to explain this from a "CG artist" point of view :mayaProf: .
Create a cube.
You may have noticed, once your cube is created, that it has a "pivot point" with informations (position, rotation, scale, etc. ...). Well this point can be a "mathematical link" between the vertex points of your cube and "the world" (center coordinates of the "world" are 0,0,0).
If this "transform" node didn't exist, your object would be at center of the scene. And to move it, it should move the all vertex positions of the cube.
The transform node acts like a "parent" of the vertex of your cube. In this way, the vertex positions of the cube doesn't move. For example, a vertex of a cube placed at 1,1,1 (relative to the pivot of the cube so) will remain at 1,1,1 regardless of the position of the transform.
But some operations (in our case, get if the vertex is face to a different mesh) requires that the coordinates of all the entities are a common reference, the reference world.
Comic demonstration:
Whereas if we chose, as common reference point, the center of the world.
It's much easier.
Okay, now we know the vertex coordinates in their "object space", we must know how get them in "world space".
The basic principle that immediately comes to mind is: We add vertex positions (in object space) to its pivot point (him, in world space).
Example:
If pVertex the position of a vertex and cubePosition the position, in world space, of the pivot point of the cube:
cubePositionX + pVertexInCubeX = pVertexInWorldX
But surely you can imagine, it's more complicated... :siffle:
Indeed, in the case of rotations and scale, it is not enough of a few additions to solve the problem.
Note: Whether a translation, a rotation, or scaling a mesh, all comes down to vertex moves in the world.
The truth is that all this "parameters" can be put into a single "object" that we call a matrix. This matrix will help us to make calculations (as Maya saves us but if you're interested, here is an example of calculates a rotation matrix) to get vertex positions in world space.
As I said, Maya gives us easely access to this object through inclusiveMatrix (There are several matrix types, we will focus on this one).
So at this stage, we have two things:
- Vertex positions relative to the object (in "object space").
- A matrix of the object (which is world relative, "world space").
We need to "convert" vertex positions from "space object" to "world space". A "change of reference" (sorry guys, don't know the english word for that :pasClasse: ). So you get an absolute position ("world space").
And to get vertex position in the "world space", "simply" multiply vertex position matrix {x, y, z} by inclusion matrix of the object. (I wrote "simply" in quotes because multiplying a matrix is not as simple as 2x2... :redface: )
Note: I would not do demonstration on "how to calculate a matrix", internet is full of examples and explanations.
And here's the formula:
positionGlobale = positionLocale * inclusiveMatrix
It's almost like the Pythagorean theorem. Who cares how it works, until you know when to use it! :baffed:
Voila! I hope this little explanation you will shed some light on why the matrices are. :sourit:
Back to the code!
Get inclusive matrix is simple.
# get MDagPath of the MMesh to get the matrix and multiply vertex to it. If I don't do that, all combined mesh will go to the origin inMeshSrcMDagPath = mFnMeshSrc.dagPath() # return MDagPath object inMeshSrcInclusiveMMatrix = inMeshSrcMDagPath.inclusiveMatrix() # return MMatrix
The first line retrieves the dagPath of the source mesh. We can consider the dagPath as the equivalent of the transform node of an object in Maya. Where all the informations about transformations (positions, rotations, scales, etc ...) are stored.
The second line retrieves the inclusiveMatrix of the source mesh as a MMatrix type.
Now we can go through each vertex:
- Multiply it by the matrix to get his position in world space.
- Get his normal.
- Also multiply it bye the matrix.
- Get the collision point, store it as the current point.
Go through each vertex and modify it
The beginning of the main loop looks like this:
for i in range( outMeshMPointArray.length() ) : inMeshMPointTmp = outMeshMPointArray[i] * inMeshSrcInclusiveMMatrix # the MPoint of the meshSrc in the worldspace
The loop is quite simple: "i" is incremented by 1 each "lap" to browse the vertex array (MPointArray).
The first thing we do is point multiplication (outMeshMPointArray[i]) by the matrix (inMeshSrcInclusiveMMatrix) to obtain a vertex (inMesgPointTmp) in world space ( with coordinates relatives to the world, 0,0,0).
Now we've (finally) the vertex positioned relative to the world, we will "intersect" it with the target mesh.
As chrysl666 told in his comment, Maya give a simple way to get informations without have to extract matrix and multiply it yourself. Just call your points (and others stuffs) with:
getPoints(OpenMaya.MSpace.kWorld).
This will return points position directly in world space. Thanks to him for this information. :bravo:
Well, go look at arguments of the OpenMaya.MFnMesh.closestIntersection() method we seen above. Bon, allez regarder les arguments de la méthode OpenMaya.MFnMesh.closestIntersection() que nous avont vu plus haut.
You see there are quite a few. :sourit:
Don't worry, we can ignore many of them. What interests us is the original vertex, his direction (in our case: normal) and the collision point (the hitPoints).
But more subtle, look at the type of the first argument (raySource) expected by the method.
It's a MFloatPoint!
But how convert inMeshMPointTmp (a MPoint) to a MFloatPoint? It's pretty easy in C++, you have to use doubles. After much research, I found a solution. I give it to you:
raySource = OpenMaya.MFloatPoint( inMeshMPointTmp.x, inMeshMPointTmp.y, inMeshMPointTmp.z )
It's not as complicated but if you don't know...
So we have our raySource.
Now the normal:
rayDirection = OpenMaya.MVector() mFnMeshSrc.getVertexNormal( i, False, rayDirection) rayDirection *= inMeshSrcInclusiveMMatrix rayDirection = OpenMaya.MFloatVector(rayDirection.x, rayDirection.y, rayDirection.z)
At this point, you should understand:
- We create the MVector.
- We store the current vertex (i) normal of the source mesh in it.
- We multiply by the matrix ot get this vector in space world.
- We "convert" it to MFloatVector.
The hitPoint is a simple MFloatPoint:
hitPoint = OpenMaya.MFloatPoint()
And the remaining arguments are:
# rest of the args hitFacePtr = OpenMaya.MScriptUtil().asIntPtr() idsSorted = False testBothDirections = False faceIds = None triIds = None accelParams = None hitRayParam = None hitTriangle = None hitBary1 = None hitBary2 = None maxParamPtr = 99999999 # http://zoomy.net/2009/07/31/fastidious-python-shrub/ hit = mFnMeshTarget.closestIntersection(raySource, rayDirection, faceIds, triIds, idsSorted, OpenMaya.MSpace.kWorld, maxParamPtr, testBothDirections, accelParams, hitPoint, hitRayParam, hitFacePtr, hitTriangle, hitBary1, hitBary2)
Great thanks to Peter J. Richardson! Sans son billet, I would never have succeeded to code this stuff. This is why we need to "share what you know" on the internet! ;)
Once called closestIntersection, you get a hitPoint in MFloatPoint that you convert into MPoint:
if hit : inMeshMPointTmp = OpenMaya.MPoint( hitPoint.x, hitPoint.y, hitPoint.z)
We replaces the current point by our new one:
outMeshMPointArray.set( inMeshMPointTmp, i )
And this is the end of the loop! :D
At this point you've got a MPointArray outMeshMPointArray with values of projected vertices on the target mesh.
So now we need to rebuild the mesh.
Build a mesh
I will not go througt details on "how to create and arrange the variables in the case of the creation of a mesh".
Basically we need:
- The number of vertices.
- Le number of ploygons (polygons + triangles if there is).
- A array of points (All vertices one behind others with they XYZ coordinates).
- An array listing the number of vertices by polygons (Example: 4,4,4,4,3,4,3,4,3,4,4,etc...).
- An array of vertex indices (Example: 1,2,3,4,3,4,5,6,5,6,7,8, etc...).
You will have understood, we already have the points array (the third point). Otherwise, we silly gets values from the original mesh.
Let's start! :hehe:
At first, we must create a MFnMeshData function set.
# create a mesh that we will feed! newDataCreator = OpenMaya.MFnMeshData()
This function set will allow us to create a MObject that we can "fill" with data of the future mesh.
newOutputData = newDataCreator.create() # Return MObject
As I said above: We need to get all informations (except the vertex array) of the source mesh:
outMeshNumVtx = mFnMeshSrc.numVertices() # outputMesh will have the same number of vtx and polygons outMeshNumPolygons = mFnMeshSrc.numPolygons() # create two array and feed them outMeshPolygonCountArray = OpenMaya.MIntArray() outMeshVtxArray = OpenMaya.MIntArray() mFnMeshSrc.getVertices(outMeshPolygonCountArray, outMeshVtxArray)
mFnMeshSrc.getVertices() filled two arrays with informations needed to create the mesh. See above.
Once we have that, we create the mesh:
meshFS = OpenMaya.MFnMesh() meshFS.create(outMeshNumVtx, outMeshNumPolygons, outMeshMPointArray, outMeshPolygonCountArray, outMeshVtxArray, newOutputData)
The principle is quite simple:
- We create a MFnMesh function set.
- We create the mesh giving all arguments re le mesh en donnant tout les arguments (collected above) via meshFS.create().
Once the mesh is created, we get the MDataHandle from the outputMesh connection to put the MObject we just fill in it: The mesh!
# Store them on the output plugs outputMeshHandle = data.outputValue( self.outputMesh ) outputMeshHandle.setMObject( newOutputData )
Once this is done, we say, via MDataBlock.setClean(), to the dependency graph that the connection has been updated.
# tell to the dependency graph the connection is clean data.setClean( plug )
It's over! :youplaBoum:
If you well followed the tutorial (and if I did not make errors ( :baffed: ), you should have a node that works correctly (place the plan so that it "target" the sphere):
Of course, if vertices aren't projected on the sphere, it return to their original positions:
Source code and conclusion
At this point, you should have understood the principle of matrices (If this is not the case, don't hesitate to go deeply, you will see 3D in a different way! :sourit: ), be able to recover components of a mesh and create a mesh from scratch.
There is the source code: projectMesh.7z
And voila! I just finished another big tutorial. I hope it has been informative and that you can begin to do interesting things with Maya API. :banaeyouhou:
Such informations is lacking on the Internet on CG web site.
I encourage all seniors who would pass by and who would learn something interesting reading this post to do not hesitate to "give back": If you are competent in a field, share! I did. :hihi:
If I'm wrong somewhere, if a point does not seem clear or if there is an error, please tell me. :pasClasse:
See you soon!
Note: As you could read, my english is far from being perfect. Don't hesitate to leave a comment if you found a better way to explain some stuff.
Update: Do this without Python
Well, it's not really the goal but since Kel Solar talk about in comments I also give you a way to do this using the built in Maya. :seSentCon:
Select the target mesh:
Select the source mesh:
Open transfert attributes options:
Set options as follow:
Basically, we only transfer the vertex positions in world space by projecting them along their normal.
And that's it!
You can rotate the source mesh and it's updated! :laClasse:
Voila! That way, people who came to find a quick solution will not be frustrated! :sourit:
Commentaires
So funny, great work!
J'ai failli lacher au moment des 'vertexs copains', mais bon. Cool, merci pour le step by step =D
@Bernie
Il existe une version fr (l'originale d’ailleurs):
http://www.fevrierdorian.com/blog/p...
:)
I have notice that if you extract a dagPath from mFnMeshSrc you don`t need matrix multiplication anymore .. just pass MSpace::kWorld in getPoints and getVertexNormal.... I have done a c++ translation of your code if you don`t mind .. tnx again for sharing your code . I have learning a lot with it !
http://paste.ideaslabs.com/show/krp...
Hi and thanks for your comment! :)
Yes, you're completely right! This is something I'd discover later:
In almost all Maya API calls to get position stuffs, you can give the space and Maya give you informations in the space you want.
It was something I wasn't aware when I'd start to work on this tutorial. But actually this is how this I've learn to work with matrix multiplications to get/change origin space.
Thanks again for this comment, I 've done a link to your comment it in my tutorial. :)
Wow!
This tutorial is amazing and easy to read. Don't worry about your English, it's fine. ;)
Now the curtain between me and writing Maya-API-Plugins has opened a bit to see the logic behind it.
Cheers,
Chris
Glad to see this tutorial help you. :)
If you are a CG dev you could maybe be interested by coral: http://coral-app.com/ :D
Thanks for your comment! :)
Absolutely fantastic tutorial. Thanks for your efforts.
Hey man, its been a while since you posted it and I want to tell you that it is awesome!! I am just curious about one thing. The hitFacePtr we create, don't we have to delete it after we are done? As it will be overwritten in every loop, in the end, we are still left with one pointer?