Maya Tool: Select two edges of a polygon and make the parallel (python)

Got most of this written and pretty sure I’m on the right track but a couple of things are happening thats not right:

  1. Sometime the selected edge are perfectly perpendicular.
  2. I’m trying to make the second selected edge parallel to the first, but doesn’t seem to matter.
import maya.cmds as cmds

"""
Adding the selected edges to a list. Then, takes the first edge and converts it to vertices and stores in the AB pair variable. same for the CD pair
"""
selected_edge = cmds.ls(selection=True, fl=True)
selection as CD pair.
AB_pair = cmds.polyListComponentConversion(selected_edge[0], toVertex=True)
AB_pair = cmds.ls(AB_pair, fl=True)
CD_pair = cmds.polyListComponentConversion(selected_edge[1], toVertex=True)
CD_pair = cmds.ls(CD_pair, fl=True)

"""
Stripping the pairs into single variables. Each vertX var is now a list 3 coordinates, X,Y, and Z.
I've tried using worldSpace and objectSpace flags.
"""
vertA = cmds.xform(AB_pair[0], query=True, translation=True, objectSpace=True)
vertB = cmds.xform(AB_pair[1], query=True, translation=True, objectSpace=True)
vertC = cmds.xform(CD_pair[0], query=True, translation=True, objectSpace=True)
vertD = cmds.xform(CD_pair[1], query=True, translation=True, objectSpace=True)


#A simple print to make sure I did all above steps correctly.
print(vertA)
print(vertB)
print(vertC)
print(vertD)

"""
This is probably where I'm messing up, as I'm not entirely sure how to do linear algebra lol. I asked a friend of mine to send me the formula.
I'm basically trying to make the edges parallel by simply manipulating vertD
"""
vertD[0] = (vertA[0] - vertB[0]) + vertC[0]
vertD[1] = (vertA[1] - vertB[1]) + vertC[1]
vertD[2] = (vertA[2] - vertB[2]) + vertC[2]
print(vertD)
new_coordsD = (vertD[0],vertD[1],vertD[2])
cmds.xform(CD_pair[1], translation=new_coordsD, objectSpace=True)

So it kinda works.


So first of all, I notice that the order of edge selection isn’t respected, which seems to be a VERY old Maya problem. So you will likely need to define the edges in a different way. Perhaps one at a time. Or perhaps with the OpenMaya API. Or maybe there is a flag to use. But I tried orderedSelection and it didn’t work.

Next, here is my algorithm. I am 100% certain there is a more straight forward way to just find the angle, but I don’t remember off-hand how to rotate a vector around an angle. So I just hacked it like this:

(This isn’t very sophisticated, so it might have bugs depending on order. You might have to find neighbors to detect the proper order of the verts in each edge, so you don’t flip the edge backwards. If you’re measuring angles and dot products, you could choose an angle that is less than 90 degrees to avoid flipping.)

  • vectorAB = vertB - vertA
  • vectorCD = vertD - vertC
  • midCD = Store the mid point for vectorCD to “rotate” around. Use this later.
  • Get the length of vectorCD.
  • Normalize the length of vectorAB, and then scale it to the same length as vectorCD
  • That way you’ll have the same angle as vectorAB, but the original length of vectorCD.
  • Now set vertD position to pointC + vectorAB
  • This will have effectively rotated edge CD around vertC.
  • newMidCD = Now store the NEW mid point for vectorCD again.
  • Relatively move the points back, by the difference of how much the mid point shifted. midCD - newmidCD

Also sorry, I re-wrote this in PyMEL, because it has a lot more convenience for working with vectors. So I hope my list gives you enough inspiration to write in cmds.

import pymel.core as pm
import math

selected_edge = pm.ls(selection=True, fl=True)
#selection as CD pair.
AB_pair = pm.polyListComponentConversion(selected_edge[0], toVertex=True)
CD_pair = pm.polyListComponentConversion(selected_edge[1], toVertex=True)

vertA, vertB = pm.ls(AB_pair, fl=True)
vertC, vertD = pm.ls(CD_pair, fl=True)

pointA = vertA.getPosition(space='world')
pointB = vertB.getPosition(space='world')
pointC = vertC.getPosition(space='world')
pointD = vertD.getPosition(space='world')

midAB = (pointA + pointB) * 0.5
midCD = (pointC + pointD) * 0.5

vectorAB = pointB - pointA
vectorCD = pointD - pointC
# Normalize AB to length of 1.0
vectorAB.normalize()
# Then multiply by CD length
vectorAB *= vectorCD.length()

vertD.setPosition(pointC + vectorAB)
# 2. Measure the new midCD. Shift it back.
pointC = vertC.getPosition(space='world')
pointD = vertD.getPosition(space='world')
newmidCD = (pointC + pointD) * 0.5
shiftOffset = midCD - newmidCD

pm.move(vertC, shiftOffset[0], shiftOffset[1], shiftOffset[2], relative=True)
pm.move(vertD, shiftOffset[0], shiftOffset[1], shiftOffset[2], relative=True)

parallelEdges

I would just add tiny thing about selection order.
There’s a setting ‘Track selection order’ in Preferences:

If you turn it on, ‘ls’ command with ‘orderedSelection’ flag set to True, would/should return everything in order of selection:

import pymel.core as pmc
sel = pmc.ls(fl=True, orderedSelection=True)
4 Likes

“You just cook them wrong”. :slight_smile:

To use the selection order, you must activate trackSelectionOrder BEFORE SELECTING !!!
If the tracking of the selection order is disabled, then switch it in True/Enable:

With maya.cmds:
maya.cmds.selectPref(trackSelectionOrder = True)

import maya.cmds as cmds
# Activate "trackSelectionOrder".
# cmds.selectPref()
# -trackSelectionOrder(tso):
# When enabled, the order of selected objects and components will be tracked.
# The 'ls' command will be able to return the active list in the order of selection
# which will allow scripts to be written that depend on the order.
if not cmds.selectPref(query = True, trackSelectionOrder = True):
    cmds.selectPref(trackSelectionOrder = True)

# Deselect
# cmds.select()
# -clear(cl):
# Clears the active list. This is more efficient than "select -d;".
# Also "select -d;" will not remove sets from the active list unless the "-ne" flag is also specified.
cmds.select(clear = True)


#    ***  Select something manually...  ***


# After that you will be able to get the selection list according to the order of selection:
# cmds.ls()
# -orderedSelection(os):
# 	List objects and components that are currently selected in their order of selection.
# This flag depends on the value of the -tso/trackSelectionOrder flag of the selectPref command.
# If that flag is not enabled than this flag will return the same thing as the -sl/selection flag would.
# -long(l):
# Return full path names for Dag objects. By default the shortest unique name is returned.
# -flatten(fl):
# Flattens the returned list of objects so that each component is identified individually.
cmds_active_selection_list = cmds.ls(orderedSelection = True, flatten = True, long = True)

Or with OpenMaya (New Python API):
maya.api.OpenMaya.MGlobal.setTrackSelectionOrderEnabled()

import maya.api.OpenMaya as om2
maya_useNewAPI = True

# Activate "trackSelectionOrder".
# OpenMaya.MGlobal.trackSelectionOrderEnabled()
# trackSelectionOrderEnabled() -> bool
# Returns whether the selection order is currerntly being tracked.
if not om2.MGlobal.trackSelectionOrderEnabled():
    # om2.MGlobal.setTrackSelectionOrderEnabled()
    # setTrackSelectionOrderEnabled() -> None
    # Set whether Maya should maintain an active selection list which
    # maintains object and component selection order.
    om2.MGlobal.setTrackSelectionOrderEnabled()

# Deselect.
# OpenMaya.MGlobal.clearSelectionList()
# clearSelectionList() -> None
# Removes all items from the active selection list.
om2.MGlobal.clearSelectionList()


# Select something manually...


# After that you will be able to get the selection list according to the order of selection.
# OpenMaya.MGlobal.getActiveSelectionList()
# getActiveSelectionList(orderedSelectionIfAvailable=False) -> MSelectionList
# 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.
om_active_selection_list = om2.MGlobal.getActiveSelectionList(orderedSelectionIfAvailable = True)

These commands will greatly facilitate the work with the selections manipulations:

From maya.cmds:
cmds.ls()

cmds.select()

cmds.selectMode()

cmds.selectType()

cmds.selectPref()

cmds.selectPriority()

From OpenMaya:

OpenMaya.MGlobal()

OpenMaya.MSelectionList()

OpenMaya.MSelectionMask()

OpenMaya.MItSelectionList()

UPD:

Additions about parallel edges…

In addition to the order in which components are selected, the following points must be taken care of:

It is necessary to verify the Active Selection List for the correctness of the content.
To make the selection easier, we can put the selection into component mode beforehand and restrict the selection mask to certain components (for example, edges).

We have to provide for the case when the second edge is selected on another mesh.

It is necessary to check whether the selected edges are initially parallel.

In order for the edge being displaced not to “twist”, you need to define the “co-direction” of the edges.
The concept of “Codirectionality” applies to co-linear, that is, parallel vectors.
But we can try to define “twisted edges” by comparing the angles between the vectors formed by the edges.
Something like this:
If the angle between “AB” and “CD” is greater than the angle between “AB” and “DC”, then the fins are twisted.
If so, then we must reverse the translate of the shifted vertex.

It is necessary to provide for cases when the selected edges have a common vertex.
In this case, you cannot rotate the target edge around its center, otherwise it will break the position of the master edge.
For universality, the rotation of the edge around its center is better to add as an optional parameter passed to the function.
By default, only the target vertex needs to be repositioned.

It is not necessary to use pymel for vector operations.
Vector arithmetic is very simple and we don’t have intensive vector calculations.
Therefore, we can write a very simple function to convert vertex coordinates to a vector.
And we can write very simple functions for the operations we need on vectors.
Or we can use Maya Vectors by calling: OpenMaya.MVector() (and optional OpenMaya.MVectorArray()).

Disclaimer: This is just a showcase of ideas and possible paths. Do not use these “dirty crutches” in production!

So, let’s try to “reinvent the wheel” with maya.cmds and module math:

import maya.cmds as cmds
import math

# Setting some selection preferences:
def set_select_pref():
    selection_order = cmds.selectPref(query = True, trackSelectionOrder = True)
    if not selection_order:
        cmds.selectPref(trackSelectionOrder = True)
    # cmds.selectPref()
    # -ignoreSelectionPriority(-isp):
    # If this is set, selection priority will be ignored when performing selection.
    # -affectsActive(-aa):
    # Set affects-active toggle which when on causes the active list to be
    # affected when changing between object and component selection mode.
    # -selectTypeChangeAffectsActive(-stc):
    # If true then the active list will be updated according to the new selection preferences.
    cmds.selectPref(ignoreSelectionPriority = False)
    cmds.selectPref(affectsActive = True)
    cmds.selectPref(selectTypeChangeAffectsActive = True)
    # cmds.selectMode()
    # -component(-co):
    # Set component selection on.
    # Component selection mode allows filtered selection based
    # on the component selection mask.
    # The component selection mask is the set of selection masks related to objects
    # that indicate which components are selectable.
    # -preset(-p):
    # Allow selection of anything with the mask set,
    # independent of it being an object or a component.
    selection_mode = cmds.selectMode(query = True, component = True)
    if not selection_mode:
        cmds.selectMode(preset = False, component = True)
    # cmds.selectType()
    # -allComponents(-alc):
    # Set all component selection masks on/off.
    # -polymeshEdge(-pe):
    # Set poly-mesh edge selection mask on/off. (component flag)
    # *(or: -byName = ['polymeshEdge', True])
    cmds.selectType(allComponents = False)
    cmds.selectType(polymeshEdge = True)
    # cmds.selectPriority()
    # -allComponents(-alc):
    # Set all component selection priority.
    # -allObjects(-alo):
    # Set all object selection priority.
    # -polymeshEdge(-pe):
    # Set poly-mesh edge selection priority.
    # Or: -byName = ['polymeshEdge', True]):
    # Set selection priority for the specified user-defined selection type.
    cmds.selectPriority(allComponents = False, allObjects = False)
    cmds.selectPriority(byName = ['polymeshEdge', True])
    
    cmds.select(clear = True)


# interpretate selected data:

def get_edge_vertices(edge_name):
    vertices = cmds.polyListComponentConversion(edge_name, toVertex = True)
    return cmds.ls(vertices, flatten = True, long = True)

def get_vtx_position(vtx_name):
    return cmds.xform(vtx_name, query = True, absolute = True, translation = True, worldSpace = True)

def set_vtx_position(vtx_name, tr_vector, absolute_transform = True, relative_transform = False, world_space = True):
    cmds.xform(vtx_name, translation = (tr_vector.x, tr_vector.y , tr_vector.z),
               relative = relative_transform, absolute = absolute_transform, worldSpace = world_space)


class Vector(object):
    def __init__(self, poz):
        self.x = poz[0]
        self.y = poz[1]
        self.z = poz[2]

def vec_add(vec_01, vec_02):
    return Vector([vec_01.x + vec_02.x, vec_01.y + vec_02.y, vec_01.z + vec_02.z])

def vec_sub(vec_01, vec_02):
    return Vector([vec_01.x - vec_02.x, vec_01.y - vec_02.y, vec_01.z - vec_02.z])

def vec_mul(vec_01, vec_02):
    return Vector([vec_01.x * vec_02.x, vec_01.y * vec_02.y, vec_01.z * vec_02.z])

def vec_fmul(vec_01, fl):
    return Vector([vec_01.x * fl, vec_01.y * fl, vec_01.z * fl])

def vec_center(vec_01, vec_02):
    return vec_fmul(vec_add(vec_01, vec_02), 0.5)

def vec_normalize(in_vec):
    tmp = vec_length(in_vec)
    if tmp != 0:
        tmp = 1.0 / tmp
    return vec_fmul(in_vec, tmp)

def vec_imul(in_vec):
    return Vector([in_vec.x * in_vec.x, in_vec.y * in_vec.y, in_vec.z * in_vec.z])

def vec_length(in_vec):
    return math.sqrt(in_vec.x * in_vec.x + in_vec.y * in_vec.y + in_vec.z * in_vec.z)

def vec_distance(vec_01, vec_02):
    tmp = vec_sub(vec_02 - vec_01)
    return vec_length(tmp)

def vec_dot(vec_01, vec_02):
    return vec_01.x * vec_02.x + vec_01.y * vec_02.y + vec_01.z * vec_02.z

def vec_angle_radians(vec_01, vec_02):
    return math.acos(vec_dot(vec_01, vec_02)/(vec_length(vec_01)*vec_length(vec_02)))

def vec_angle_degrees(vec_01, vec_02):
    return math.degrees(vec_angle_radians(vec_01, vec_02))


# General define.
def parallel_edges(rotate_around_center = False):
    # Get current flatten selection list with order selection:
    selection = cmds.ls(orderedSelection = True, flatten = True, long = True)
    # Check selection (two edge only):
    if cmds.polyEvaluate(selection, edgeComponent = True) == 2:
    # Get str long name selected edges:
        edges_01, edges_02 = selection
    # Else: warning:
    else:
        cmds.warning('Select only two PolyEdges!')
        return 0, 'Select only two PolyEdges!'
    # Get vertices long name for selected edges:
    a_vtx, b_vtx = get_edge_vertices(edges_01)
    c_vtx, d_vtx = get_edge_vertices(edges_02)
    # Check selected edges for a common vertex:
    common_vtx = list({a_vtx, b_vtx} & {c_vtx, d_vtx})
    # If "d_vtx" or/and a_vtx is common vertex for selected edges,
    # then swap the values for "c_vec" and "d_vec" vertex
    # or/and swap the values for "a_vec" and "b_vec" vertex.
    if common_vtx:
        if d_vtx == common_vtx[0]:
            c_vtx, d_vtx = d_vtx, c_vtx
        if a_vtx == common_vtx[0]:
            a_vtx, b_vtx = b_vtx, a_vtx
    # Create vectors from vertices positions (world space):
    a_vec = Vector(get_vtx_position(a_vtx))
    b_vec = Vector(get_vtx_position(b_vtx))
    c_vec = Vector(get_vtx_position(c_vtx))
    d_vec = Vector(get_vtx_position(d_vtx))
    # Let us calculate the normalized vector "ab_vec",
    # and the length of the vector "cd_vec".
    ab_vec = vec_sub(b_vec, a_vec)
    ab_vec_norm = vec_normalize(ab_vec)
    cd_vec = vec_sub(d_vec, c_vec)
    cd_length = vec_length(cd_vec)
    dc_vec = vec_sub(c_vec, d_vec)
    # Check co-directionally edges.
    # If edges not co-directionally,
    # then the normalized vector "ab_vec_norm" must be multipled by minus one.
    # That is, we reverse the "cd_vec" vector (with shifted "d_vtx" vertex):
    if not common_vtx:
        if vec_angle_radians(ab_vec, cd_vec) > vec_angle_radians(ab_vec, dc_vec):
            ab_vec_norm = vec_fmul(ab_vec_norm, -1)
    # Let's calculate a new position for the shifting vertex:
    new_position = vec_add(c_vec, vec_fmul(ab_vec_norm, cd_length))
    # Iw we want to achieve parallel edges by rotating
    # the second edge around its center:
    # Let's calculate a new position for the shifting vertices.
    # If the selected edges do not contain a common vertex:
    if rotate_around_center and not common_vtx:
        cd_center = vec_center(c_vec, d_vec)
        cd_new_center = vec_center(c_vec, new_position)
        shift = vec_sub(cd_center, cd_new_center)
        c_new_position = vec_add(c_vec, shift)
        d_new_position = vec_add(new_position, shift)
        set_vtx_position(c_vtx, c_new_position)
        set_vtx_position(d_vtx, d_new_position)
    else:
        set_vtx_position(d_vtx, new_position)

# Before using our tool, let's set the selection preferences:
set_select_pref()

#  ***  Manualy selected components ...  ***

parallel_edges()

# Or, if we want to rotate the target edge arround its center:
# parallel_edges(rotate_around_center = True)

Or use OpenMaya New Python API:

import maya.cmds as cmds
import maya.api.OpenMaya as om2
maya_useNewAPI = True


# Setting some selection preferences:
def om_set_select_pref():
    if not om2.MGlobal.trackSelectionOrderEnabled():
        om2.MGlobal.setTrackSelectionOrderEnabled()
    om2.MGlobal.clearSelectionList()
    # Set the current selection mode "Select components":
    om2.MGlobal.setSelectionMode(om2.MGlobal.kSelectComponentMode)    # om2.MGlobal.kSelectComponentMode or 1
    # Set Selection Mask
    sel_mask = om2.MSelectionMask()
    # Add the specified selection type  (kSelectEdges) to this mask
    sel_mask.addMask(om2.MSelectionMask.kSelectEdges)
    om2.MGlobal.setComponentSelectionMask(sel_mask)
    return 1


def om_set_vtx_position(node, vtx_id, position_vec, absolute_transform = True, relative_transform = False, world_space = True):
    vtx_str_name = '{}.vtx[{}]'.format(node.fullPathName(), vtx_id)
    cmds.xform(vtx_str_name,
               translation = tuple(position_vec),
               relative = relative_transform,
               absolute = absolute_transform,
               worldSpace = world_space)
    return 1


# General define.
def om_parallel_edges(rotate_around_center = False):
    om_select = om2.MGlobal.getActiveSelectionList(orderedSelectionIfAvailable = True)
    # Check selection:
    om_sel_edges = 0
    if om_select.length() == 2:
        node_01, component_01 = om_select.getComponent(0)
        node_02, component_02 = om_select.getComponent(1)
        if component_01.apiTypeStr == component_02.apiTypeStr == 'kMeshEdgeComponent':
            om_sel_edges = om_select
    if not om_sel_edges:
        om2.MGlobal.displayWarning('Select only two PolyEdges!')
        return 0, 'Select only two PolyEdges!'
    # Interpretate selected data:
    it_01 = om2.MItMeshEdge(node_01, component_01)
    it_02 = om2.MItMeshEdge(node_02, component_02)
    a_vtx = it_01.vertexId(0)
    b_vtx = it_01.vertexId(1)
    c_vtx = it_02.vertexId(0)
    d_vtx = it_02.vertexId(1)
    a_poz = it_01.point(0, space = 4)    # space=4  <=>  MSpace.kWorld  <=>  4
    b_poz = it_01.point(1, space = 4)
    c_poz = it_02.point(0, space = 4)
    d_poz = it_02.point(1, space = 4)
    # Check selected edges for parallelism (collinearity):
    if(om2.MVector(b_poz) - om2.MVector(a_poz)).isParallel(
       om2.MVector(d_poz) - om2.MVector(c_poz)):
        return 1, 'Selected Edges are parallel.'
    # Check selected edges for a common vertex:
    common_vtx = list({a_vtx, b_vtx} & {c_vtx, d_vtx})
    # If "d_vtx" or/and a_vtx is common vertex for selected edges,
    # then swap the values for "c_vec" and "d_vec" vertex
    # or/and swap the values for "a_vec" and "b_vec" vertex.
    if common_vtx:
        if a_vtx == common_vtx[0]:
            a_vtx, b_vtx = b_vtx, a_vtx
            a_poz = it_01.point(1, space = 4)
            b_poz = it_01.point(0, space = 4)
        if d_vtx == common_vtx[0]:
            c_vtx, d_vtx = d_vtx, c_vtx
            c_poz = it_02.point(1, space = 4)
            d_poz = it_02.point(0, space = 4)
    cd_length = it_02.length(space = 4)
    a_vec = om2.MVector(a_poz)
    b_vec = om2.MVector(b_poz)
    c_vec = om2.MVector(c_poz)
    d_vec = om2.MVector(d_poz)
    ab_vec = b_vec - a_vec
    cd_vec = d_vec - c_vec
    dc_vec = c_vec - d_vec
    ab_vec_norm = ab_vec.normal()
    # Check 'co-directionally' edges.
    # If edges not 'co-directionally',
    # then the normalized vector "ab_vec_norm" must be multipled by minus one.
    # That is, we reverse the "cd_vec" vector (with shifted "d_vtx" vertex):
    if not common_vtx:
        if ab_vec.angle(cd_vec) > ab_vec.angle(dc_vec):
            ab_vec_norm = -ab_vec_norm
    # Let's calculate a new position for the shifting vertex:
    new_position = c_vec + (ab_vec_norm * cd_length)
    # Iw we want to achieve parallel edges by rotating
    # the second edge around its center:
    # Let's calculate a new position for the shifting vertices.
    # If the selected edges do not contain a common vertex:
    if rotate_around_center and not common_vtx:
        cd_center = cd_vec * 0.5
        cd_new_center = (new_position - c_vec) * 0.5
        shift = cd_center - cd_new_center
        c_new_position = c_vec + shift
        d_new_position = new_position + shift
        om_set_vtx_position(node_02, c_vtx, c_new_position)
        om_set_vtx_position(node_02, d_vtx, d_new_position)
    else:
        om_set_vtx_position(node_02, d_vtx, new_position)
    return 1, 'Done!'


# Before using our tool, let's set the selection preferences:
om_set_select_pref()

#  ***  Manualy selected components ...  ***

om_parallel_edges()

# Or, if we want to rotate the target edge arround its center:
# om_parallel_edges(rotate_around_center = True)

:slight_smile:

To calculate new coordinates for point “D”:
It is necessary to add the product of the length “BC” by the normalized value of the vector “AB” to the coordinate of the point “C”:
D.new = C + (AB.normalized * CD.length)

1.0. Calculate vector “AB”:
To do this, subtract the coordinates of point “B” from point “A”:
AB = (B.x-A.x, B.y-A.y, B.y-A.y)
* You did the opposite: subtract “B” from “A”! :frowning:

2.0. Normalize the vector “AB”:
If the length of the vector is non-zero:
AB.normalized = AB / AB.length
2.1. AB.length = Square Root(AB.x*AB.x + AB.y*AB.y + AB.z*AB.z)
2.2. AB.normalized = AB / AB.length = (AB.x/AB.length, AB.y/AB.length, AB.z/AB.length)

3.0. Calculate the product of (AB.normalized * CD.length):
3.1. CD.length = Square Root(CD.x*CD.x + CD.y*CD.y + CD.z*CD.z)
3.2. K = AB.normalized * CD.length = (AB.normalized.x*CD.length, AB.normalized.y*CD.length, AB.normalized.z*CD.length)

4.0. D.new = C + K:
D.new = (C.x+K.x, C.y+K.y, C.z+K.z)

5.0. PROFIT !!!

For cases: a common vertex and oppositely directed edges: possible implementations (using maya.cmds and maya.api.OpenMaya) are discussed in my previous message.

Good luck and harmony! :slight_smile:

Thanks for all of the replies. I’ll give them a go today! I’ll report what I find

Edit: While we’re here, from a production standpoint, what benefits or disadvantages would I consider when using cmds vs OpenMayaAPI for a tool like this? I’ve used both, but I’ve gotten by using mostly Maya.cmds.

I’m all about the path of least resistance but I’d also like the solution to be scalable.

Thanks again
//Xarga

As soon as you have tasks with intensive geometry processing, you will have to use the Maya API (Python / C ++) to speed up the processes. When using “maya.cmds”, you are forced to use Python loops with lists or tuples. This is very slow. OpenMaya has built-in iterators for all types of components, which allows you to iterate geometry very, very quickly. High performance arrays are available for specific data types. When using the Maya C++ API, you can get an even more impressive speed boost, at least on long cycles. With intensive value assignment operations, there will be no monstrous slowdown due to Python’s dynamic typing.
(And “icing on the cake” - GIL…)
In your specific example, you do not have intensive calculations and do not process large data.
But then, with the help of OpenMaya, you have the opportunity to use high-performance vector operations out of the box. Otherwise, you have to “reinvent the wheel” or use PyMEL (which is not part of Maya, but is installed as an option). Be aware that PyMEL is just wrappers for “maya.cmds” and some of the Maya API functions. In fact, it greatly slows down the code. But 10-20 years ago this was justified by the fact that Maya Python API-1 was (and remains) not very user friendly and did not follow the Python paradigms very much. But, with the advent of Python API-2, everything has changed a lot for the better (“simplicity and expressiveness”). Therefore, PyMEL is only needed for compatibility with a huge codebase. And PyMEL is slowly moving Autodesk itself into the dusty depths of the scene…
Full-fledged work with the contents of the viewport, with drawing and rendering control, OpenGL control - is impossible without the use of the Maya API… Etk…
From the sad: When using the Maya Python API (outside the plugin) you have to take care of Undo/Redo…
Summary: Maya Python API and Maya Python cmds are not mutually exclusive but complementary tools. The ratio of these tools in the code should be dictated by the tasks being solved and the simplicity / performance ratio.
Therefore, if you have a new class of tasks, such as writing user interfaces, you will have to use an efficient tool, such as the now standard PySide/Qt…
So it goes :slight_smile:
Good luck!

I wouldn’t necessarily advice changing preferences (not saying that ‘trackSelectionOrder’ should be Off or On - speaking in general) from some script (if script’s purpose actually isn’t changing preferences), altering scene settings, etc., and not reverting to the original preferences/settings after execution.
Especially without, at least, script informing user about that. It could create confusion, break some functionality dependent on current settings, etc.

Think it might be better to put this kind of stuff in context manager, in this manner, or similar:

from contextlib import contextmanager
from maya import cmds


@contextmanager
def track_selection_order():
    """ Restores trackSelectionOrder preference to original state. """

    try:
        track_selection = cmds.selectPref(q=True, trackSelectionOrder=True)  # store original state

        # turn on selection order tracking, if it's off
        if not cmds.selectPref(query=True, trackSelectionOrder=True):
            cmds.selectPref(trackSelectionOrder=True)

        yield

    finally:
        cmds.selectPref(trackSelectionOrder=track_selection)  # revert to original state

And then call function:

with track_selection_order():
    ...  # put logic or call function here - parallel_edges() in this case

Of course, this also could be done by simply querying preferences, executing code logic, and setting preferences back to the original state, without context manager; but all exceptions must be handled, otherwise script might never reach to the end, and ‘trackSelectionOrder’ may never be reverted to what it was previously (stays turned On, even if originally it was maybe Off).
With simple context manager like the one above, it’s guaranteed that the original state will be restored, no matter what happens during script’s execution.

Just my thoughts on it…

2 Likes

Enabling the “Track selection order” option does not affect anything, except for a slight performance drop and an increase in memory consumption. At the same time, you need to understand that this statement is true only for giant selection lists. Therefore, any developer should check the state of global variables that affect the non-deterministic behavior of the functions he needs.
I could not immediately think of a situation where enabling the “Track selection order” option could break something in someone’s code. We have just observed the opposite situation :).

If some code writer relies on the state of something mutable, then this is groundless self-confident stupidity. For some reason, many coders believe that:

  • Some (all!) standard plugins must be loaded.
  • Construction history must be enabled.
  • The “Undo” option must be enabled.
  • The Maya window is located on the first monitor.
  • Does not assume 2-3-4 monitors.
  • Display taskbar (vertical, horizontal, hidden).
  • All Display/s resolution/s is the same (vertical, horizontal layout).
  • Display/s resolutions cannot be less than x.
  • In Maya, enable Docking/Undocking.
  • Up axis - “Y”.
  • Working units - centimetr/degrees.
  • Security preferences ?!
  • Framerate -30 fps!
  • Arnold shaders and Arnold-envlight are always available.
  • Update node added, update selection , add new nodes are enabled in nodeeditor/hypergraph/shadingnetwork.
  • Maya is running as administrator.
  • Menubars are displayed on all panels.
  • Titlebar is always on.
  • The interface is unscaled (scaled with system settings).
  • Always have an internet connection.
  • You can always download and install the missing system components.
  • The Windows language version and the Windows interface language are the same!
  • All windows in Maya are in an unminimized state.
  • The perspective panel is always the same and is named “modelPanel4”.
  • You can always make the “modelPanel4” panel active.
  • Shelf Tabs Panell alwes enable.
  • Display Pref: materialLoadingMode is True! (What the hell is this?! :wink: )

This music will last forever…
:slight_smile: