“You just cook them wrong”. ![]()
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()
From OpenMaya:
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)
![]()