Setting the length of 2 edges equal to each other

Hey there,

So I’m attempting to make some personal tools in Maya that mimic some of functions in the parametric modeling software I use (i.e Shapr3D, SolidWorks). I’m attempting to write a Python script that does the following.

1.Queries the length of a selected edge (or distance between two vertices, whichever works best)
2.Stores the length in a variable
3.Sets the length of another selected edge to that variable.

So I’m basically stuck at step one, as I know how to get the shapeNode’s edge index. But I don’t think the edge length (or vertices distance) is stored that way.

Lastly, I politely ask for a bit of etiquette around my intentions for this. On other forums I’ve had the first response be:
“Why are you attempting to do this?, just do this. . .” or “Maya isn’t like XYZ so why are you attempting to do it this way?”

I find these questions/comments not only to be unhelpful, but also a bit shallow and inconsiderate. So, while the answer to those questions is irrelevant, I’ll answer them now by saying, “just to see if it can be done”. I hope that will suffice. I’m just trying to avoid derailing the thread from its original topic.

Thanks for your help!
//Xarga

Hello Xarga,
first, let me clear something up first, before getting to your question.

The first question there actually is, the correct etiquette, when solving a problem. Especially for TAs. The first question is always. and should be, ‘what are you trying to do? what is the purpose of this?’. And we mean at the root of things.
The main reason is, for the people involved to understand your problem, your needs, and then - for good TAs especially, come up with a solution that may be wildly different from the original requested solution but much better, or more optimized, for your task at hand.
If you think of TAs as doctors, and then imagine a patient walking in and asking for a specific drug, rather than letting the doctor examine them first, moreover claiming that asking for your symptoms is unhelpful, it would most likely lead to the doctor refusing to treat them, right?
TAs don’t mean anything bad by those questions, so please don’t take them personally. We are, in facts, trying to be as helpful as possible, and that means knowing as much as possible about the situation.
A problem can have multiple solutions, and one might be way better than another. Without knowing about the problem, we could recommend something that may be very bad for your use case, because all we have is our imagination then, rather than solid information.
Please give that some thought. Most of us had to learn this the hard way, haha.

As for your request then.
Even if we want to ignore the ‘what is the purpose’ question here, we need more information.

  • when setting the length of the edge, what vertex of the edge should move? should it be uniform? mirrored? (therefore, in your example, the right edge’s vertex would get moved up, to match mirrored position of the left top vertex) if mirrored, what if it can’t find a world-space mirror vert?
  • should it keep the current angle? (i assume yes?)
  • should it work with multiple edges selected?

Depending on the answer to these questions, i may or may not be able to help, but i’ll try ^^

2 Likes

I appreciate the feedback and totally agree, but I guess considering from a user perspective, we’ve always tread lightly as to not sound condescending or abrasive. I guess we consider “Why are you attempting to do this?” a perfectly acceptable question. Its following-up with a " Just do it this way" which we’d like to avoid, as it’s a preconceptive solution without context. But I agree with all of that, 100%.

So for the amplifying info.

  • when setting the length of the edge, what vertex of the edge should move? should it be uniform? mirrored? (therefore, in your example, the right edge’s vertex would get moved up, to match mirrored position of the left top vertex) if mirrored, what if it can’t find a world-space mirror vert?

Either vertices can move in any direction. From what I’ve been able to do so far, it sets the distance from the mid-point of the vertices. This is adequate. Uniformity isn’t needed, but welcome.

  • should it keep the current angle? (i assume yes?)
    No, this isn’t needed either.

  • should it work with multiple edges selected?
    Do you mean setting multiple edges to the single queried length?, if so no.

Thanks a ton for the prompt reply. =)

I will just add, that then it comes to communication skills, haha…

Anywho, with your added information in mind, back to your query…

The overview here is:

  1. get the length of both edges → get the verts associated with each edge, calculate the distance between their positions in space.
  2. calculate the ratio of scale that the target edge needs to adopt.
  3. scale the edge

a simple code would look like this:

import maya.cmds as mc
import math

# if we are in fact going by selection, it's important to turn on the 'trackSelectionOrder' flag of the selectPref command.
# we will be getting the edges using the ls command, with the 'fl/flatten' flag on, and to get clean results, using the 'orderedSelection' flag, which won't work properly unless the 'trackSelectionOrder' is set.
if not mc.selectPref(q = True, tso = True):
    mc.selectPref(tso = True)

# setup some basic functions
def get_edge_length(edge):
    
    mesh_name = edge.split('.')[0]
    
    # get the related verts, and format our return to something manageable
    related_verts = mc.polyInfo(edge, ev = True)[0].split(':')[-1].split(' ')
    related_verts = [x for x in related_verts if x.isdigit()]
    
    # get the length or each of the edges
    edge_length = get_distance('{0}.vtx[{1}]'.format(mesh_name, related_verts[0]), '{0}.vtx[{1}]'.format(mesh_name, related_verts[1]))
    
    return edge_length
    

def get_distance(point_a, point_b):
    
    point_a_position = mc.xform(point_a, q = True, ws = True, t = True)
    point_b_position = mc.xform(point_b, q = True, ws = True, t = True)
    
    vector_result = [point_a_position[0] - point_b_position[0], point_a_position[1] - point_b_position[1], point_a_position[2] - point_b_position[2]]

    squared_result = [x ** 2 for x in vector_result]

    distance = math.sqrt(squared_result[0] + squared_result[1] + squared_result[2])

    return distance
    
    
### the actual code:

# let's say the first item in the selection should be the reference edge, and the second the target edge.
edges = mc.ls(fl = True, os = True)

if edges:
    edge_reference = edges[0]
    edge_target = edges[1]
    
    # get edge lengths
    edge_reference_length = get_edge_length(edge_reference)
    edge_target_length = get_edge_length(edge_target)
    
    # scale correctly
    scale_factor = edge_reference_length / edge_target_length
    mc.scale(scale_factor, scale_factor, scale_factor, edge_target, a = True, cs = True)    
1 Like

Thanks, that worked perfectly. Here I was attempting to do it using the OpenMaya API. This is about as far as I got but the return values is always one.

import maya.cmds as cmds
import maya.api.OpenMaya as om


"""
Grabing shape node info and varifing the type. Will need to ensure it
accepts a selection of any shapeNode
"""
node_name = "pCube1"
mySelection = cmds.ls(selection = True, fl = True)
print("node_name is:" + str(type(node_name)))

"""
https://help.autodesk.com/view/MAYAUL/2019/ENU/?guid=__py_ref_class_open_maya_1_1_m_selection_list_html

"""
edge = om.MSelectionList().add("pCube1").getDagPath(0)
print("edge length is: " + str(edge.length()))

shape_node = edge.child(0)
print("shape_node is:" + str(type(shape_node)))

length = om.MDistance(edge.length())
print( "Edge length:", length.asUnits(om.MDistance.kCentimeters))

No problem!

By the way, is there a specific reason you were trying to do it using the OpenMaya API?

It has a lot of useful tricks up it’s sleeve, but unless you’re dealing with something that can’t be written with python’s maya.cmds wrapper itself, i myself think that it’s not worth the hassle.

However, if speed is your goal, then more often than not, it merits diving into it though.
I look at it once or twice a year when i find myself needing to utilize something from it, or dealing with a plugin, so I don’t hold it all in my head, but rather google around when i need it, so i might not be 100
% correct, but looking at your example, this is my assumption…

You are adding the cube to the selection list, but nowhere are you getting a particular edge length, i think?
At this line:
print("edge length is: " + str(edge.length()))
the ‘edge’ variable here still holds just a MDagPath object, so it’s .length() property, actually refers to the number of objects in the path. Not the length of any edge in particular.
(you can have a look yourself at Help)

If you want API-level access to an edge, you would have to go through the MItMeshEdge iterator object.
then, the iterated object’s .length() property would in fact refer to the length of the edge it’s curently iterating on.

Here’s an example of a code that would work to get the length of an edge:

import maya.api.OpenMaya as om

# variables
mesh_node = "pCube1"
edge_index = 5

# instantiate our selection list
sel = om.MSelectionList()
sel.add(mesh_node)
dag_path = sel.getDagPath(0)

# get our iterator up and running
it_edges = om.MItMeshEdge(dag_path)

# set the iterator to the specified index
it_edges.setIndex(edge_index)

print ("The length of {0}.e[{1}] is {2}".format(mesh_node, edge_index, it_edges.length()))

So, let’s formulate the minimum requirements from the information known to us:

  1. Arbitrary two (only two!) edges must be chosen.
    1.1 Edges can belong to different meshes.
    1.2. Edges may share a common vertex.
  2. Perform some manipulations so that the length of the second selected edge becomes equal to the length of the first selected edge.
    2.1. The vertices of the second edge before and after the transformation must lie on the same straight line.

From these requirements it follows that:

  1. We must organize the memorization of the order of selection of components.
  2. We must provide mechanisms for valid selection content.
  3. To change the length of the target edge, we will move edge_vertex_ID(1) along this edge.
    3.1. In case this vertex is shared between the target and source edges:
    To keep the length of the source edge, to change the length of the target edge, we will move edge_vertex_ID(0) along this edge.
  4. In case the source mesh(es) have been transformed, we must ensure that the lengths of the source and target edges match in terms of world space.

*Optional 5. If we want to use this script as a tool, we need to provide a mechanism for interactive selection of components. For the script to work automatically with the correct selection of components.

*Optional 6. It is highly desirable to implement an undo function for “setPoint”!

import maya.api.OpenMaya as om

def maya_useNewAPI():
    pass

maya_useNewAPI = True


# Set whether Maya should maintain an active selection list which
# maintains object and component selection order.
if  not om.MGlobal.trackSelectionOrderEnabled():
    om.MGlobal.setTrackSelectionOrderEnabled()
# Deselect
sel_list = om.MGlobal.getActiveSelectionList()
sel_list.clear()
om.MGlobal.setActiveSelectionList(sel_list)

#####
#... Here you can implement some code for interactive selection of edges.
####

def get_correct_selection():
# Return an MSelectionList containing the nodes, components and
# plugs currently selected in Maya. If orderedSelectionIfAvailable
# is True, and tracking is enabled, will return the selected items
# in the order that they were selected.
    selection = om.MGlobal.getActiveSelectionList(orderedSelectionIfAvailable = True)
# Checking that only two items are selected.
    if selection.isEmpty():
        return [0, 0]
    if selection.length() != 2:
        return [0, 1]
# Get node and component from selection.
    node_01, component_01 = selection.getComponent(0)
    node_02, component_02 = selection.getComponent(1)
# Check yhat both selected components are edges.
    if component_01.apiTypeStr != component_02.apiTypeStr != 'kMeshEdgeComponent':
        return [0, 2]
# For the source edge, we get its length and IDs of its vertices (in world space).
    it = om.MItMeshEdge(node_01, component_01)
    reference_length = it.length(space = 4)           # space = MSpace.kWorld or "4"
    vertex_id_01_01 = it.vertexId(0)
    vertex_id_01_02 = it.vertexId(1)
# For the target edge, we get the coordinates and IDs of its vertices (in world space).
    it = om.MItMeshEdge(node_02, component_02)
    point_02_01 = it.point(0, space = 4)              # space = MSpace.kWorld or "4"
    point_02_02 = it.point(1, space = 4)              # space = MSpace.kWorld or "4"
    vertex_id_02_01 = it.vertexId(0)
    vertex_id_02_02 = it.vertexId(1)
# If vertex(0) of the target edge is common between the target and source edges,
# then we will move vertex(1) on the target edge.
    if vertex_id_02_02 in (vertex_id_01_01, vertex_id_01_02):
        point_a = point_02_02
        point_b = point_02_01
        edge_vertex_id = 0
    else:
        point_a = point_02_01
        point_b = point_02_02
        edge_vertex_id = 1

    return [it,
            point_a,
            point_b,
            edge_vertex_id,
            reference_length]



all_ret = get_correct_selection()


# Processing the selection.
if not all_ret[0]:
    if not all_ret[1]:
        sel_error = 'Nothing selected. Select two edges.'
    elif all_ret[1]:
        sel_error = 'Selection must contain exactly two components. Select two edges.'
    else:
        sel_error = 'Selection is valid only for edges. Select two edges.'
    # ... Here you can implement some code to handle the wrong selection (with "sel_error").
else:
    (it,
     point_a,
     point_b,
     edge_vertex_id,
     reference_length
    ) = all_ret
# Calculate the vertex(1) position of the target edge so that the length
# of the target edge becomes equal to the length of the source edge.
# In addition, we want vertex(0), vertex(1) and vertex(1)(new position)to line on the same line.
vector_a = om.MVector(point_a.x, point_a.y, point_a.z)
vector_b = om.MVector(point_b.x, point_b.y, point_b.z)
new_point = om.MPoint(vector_a + (vector_b - vector_a).normalize()*reference_length)
# Move the vertex to a new position (in world space).
it.setPoint(new_point, edge_vertex_id, space = 4)              # space = MSpace.kWorld or "4"

OpenMaya MSpace Class Reference

Coordinate Space Types:
int kInvalid = 0
int kLast = 5
int kObject = 2
int kPostTransform = 3
int kPreTransform = 2
int kTransform = 1
int kWorld = 4

1 Like

No real reason other than gaining familiarity. I would like to develop plugins as well. I’m in the middle of a online crash course of it right now.

Is there anything that OpenMaya API can do better than Maya.cmds?

Thanks for this. Comparing the two will be extremely beneficial. I appreciate the help from both of you

You can check that the action of the last command (it.setPoint) that pushes the vertex to a new position does not go onto the undo stack.
In order for actions to be undo/redo, you will have to write handlers for MDagModifier(): doIt(), redoIt(), undoIt(). And yes, you will have to add specific code to use it as a plugin. :slight_smile:
Something like this:
Example implementation based on polyModifierCmd:

Note that this example is not based on Python API version 2 (maya.api.OpenMaya), but on Python API version 1 (maya.OpenMaya).
If you are not yet ready to make such sacrifices, then you can use some suitable maya.cmds command for the last step.
For example: cmds.move.
Here is an example of modified code:

import maya.cmds as cmds
import maya.api.OpenMaya as om2

maya_useNewAPI = True

def maya_useNewAPI():
    pass


def get_new_point_position():
    # Return an MSelectionList containing the nodes, components and
    # plugs currently selected in Maya. If orderedSelectionIfAvailable
    # is True, and tracking is enabled, will return the selected items
    # in the order that they were selected.
    selection = om2.MGlobal.getActiveSelectionList(orderedSelectionIfAvailable = True)
    # Checking that only two items are selected.
    sel_warning = ''
    if selection.isEmpty():
        sel_warning = 'Nothing selected. Select two edges.'
        om2.MGlobal.displayWarning(sel_warning)
        return 0
    if selection.length() != 2:
        sel_warning = 'Selection must contain exactly two components. Select two edges.'
        om2.MGlobal.displayWarning(sel_warning)
        return 0
    # Get node and component for item selection.
    node_01, component_01 = selection.getComponent(0)
    node_02, component_02 = selection.getComponent(1)
    # Check yhat both selected components are edges.
    if component_01.apiTypeStr != component_02.apiTypeStr != 'kMeshEdgeComponent':
        sel_warning = 'Selection is valid only for edges. Select two edges.'
        om2.MGlobal.displayWarning(sel_warning)
        return 0
    # For the source edge, we get its length and IDs of its vertices (in world space).
    it = om2.MItMeshEdge(node_01, component_01)
    reference_length = it.length(space = 4)           # space = MSpace.kWorld or "4"
    vertex_id_01_01 = it.vertexId(0)
    vertex_id_01_02 = it.vertexId(1)
    # For the target edge, we get the coordinates and IDs of its vertices (in world space).
    it = om2.MItMeshEdge(node_02, component_02)
    point_02_01 = it.point(0, space = 4)              # space = MSpace.kWorld or "4"
    point_02_02 = it.point(1, space = 4)              # space = MSpace.kWorld or "4"
    vertex_id_02_01 = it.vertexId(0)
    vertex_id_02_02 = it.vertexId(1)
    # If vertex(0) of the target edge is common between the target and source edges,
    # then we will move vertex(1) on the target edge.
    if vertex_id_02_02 in (vertex_id_01_01, vertex_id_01_02):
        point_a = point_02_02
        point_b = point_02_01
        vertex_id = vertex_id_02_01
    else:
        point_a = point_02_01
        point_b = point_02_02
        vertex_id = vertex_id_02_02
    # Calculate the "vertex_id" position of the target edge so that the length
    # of the target edge becomes equal to the length of the source edge.
    # In addition, we want vertex(0), vertex(1) and vertex(1)(new position)to line on the same line.
    vector_a = om2.MVector(point_a.x, point_a.y, point_a.z)
    vector_b = om2.MVector(point_b.x, point_b.y, point_b.z)
    new_point = om2.MPoint(vector_a + (vector_b - vector_a).normalize()*reference_length)
    vertex_string_name = '{}.vtx[{}]'.format(selection.getDagPath(1).fullPathName(), vertex_id)
    return new_point, vertex_string_name

# Set whether Maya should maintain an active selection list which
# maintains object and component selection order.
if  not om2.MGlobal.trackSelectionOrderEnabled():
    om2.MGlobal.setTrackSelectionOrderEnabled()
# Deselect
sel_list = om2.MGlobal.getActiveSelectionList()
sel_list.clear()
om2.MGlobal.setActiveSelectionList(sel_list)
om2.MGlobal.displayInfo('Select two edges.')

#####
#... Here you can implement some code for interactive selection of edges.
####

# Get new position for target vertex.
all_ret = get_new_point_position()
# Set position for target vertex
if all_ret:
    new_point, vertex_string_name = all_ret
    # Move the vertex to a new position (in world space).
    cmds.move(new_point.x, new_point.y, new_point.z, vertex_string_name, absolute = True)
1 Like