def remove_external_axis(*args): """ Removes external axis from the robot it's attached to by deleting all of its attributes. The axis controller and models are preserved. This function just breaks the connection between the robot and the axis :param *args: required by Maya UI :return: """ # Get the selected item from the Mimic UI selection = pm.textScrollList('tsl_externalAxes', selectItem=True, query=True)[0] # Split the selection into the robot's name and the external axis name robot, axis_name = selection.split(': ') target_CTRL = '{}|robot_GRP|target_CTRL'.format(robot) parent_attribute = '{}.externalAxis_{}'.format(target_CTRL, axis_name) # Remove connections between the axis controller and the robot external_axis_attribute_path = target_CTRL + '.' + axis_name external_axis_CTRL, driving_attribute = _get_external_axis_connections( external_axis_attribute_path) driving_axis = driving_attribute[-1] # Prep transformation string for Maya's limit attributes if 'translate' in driving_attribute: driving_attribute_trunc = 'Trans' else: driving_attribute_trunc = 'Rot' _enable_external_axis_limits(external_axis_CTRL, driving_attribute_trunc, driving_axis, enable=False) # Delete External Axis attribute on the robot controller pm.deleteAttr(parent_attribute) # Clear the axis from the Mimic UI selection and reset the UI pm.textScrollList('tsl_externalAxes', edit=True, removeItem=selection) if not pm.textScrollList('tsl_externalAxes', query=True, numberOfItems=True): reset_external_axis_UI() ''' # NEEDS Attention. This deletes parent constraint even if the axis # being removed isn't the one the robot is attached to if _check_if_robot_is_attached_to_external_axis(robot): pm.delete('{}|robot_GRP|local_CTRL|' \ 'localCTRL_externalAxisCTRL_parentConstraint' .format(robot)) pm.setAttr('{}|robot_GRP|local_CTRL.visibility'.format(robot), 1) ''' pm.headsUpMessage('External Axis \'{}\' removed successfully from {}' .format(axis_name, robot))
def matchIkToFk(ikControl, ikPole, offset=100.0, msgAttr='fkjoints', autoKey=True): """ Matches ikControl and ikPole Vector control to match the current pose of underlying fk duplicate joint chains Finds the fk joints using a previously created message connection to the attribute msgAttr """ fkJoints = pmc.listConnections('{0}.{1}'.format(ikControl, msgAttr), destination=False, source=True) attr = pmc.listConnections('{0}.ikfk'.format(ikControl), destination=False, source=True, plugs=True, scn=True)[0] switchControl = attr.node() switchAttr = attr.name(includeNode=False) frameBeforeCurrent = pmc.currentTime(q=True) - 1 if autoKey: pmc.setKeyframe(switchControl, attribute=switchAttr, time=frameBeforeCurrent, value=1, outTangentType='step') pmc.setKeyframe([ikControl, ikPole], time=frameBeforeCurrent, outTangentType='step') pmc.setKeyframe(switchControl, attribute=switchAttr, value=0, outTangentType='step') alignObjects(ikControl, fkJoints[-1]) loc = getPoleVectorPosition(fkJoints, offset, curveGuide=False) alignObjects(ikPole, loc, position=True, rotation=False) pmc.delete(loc) if autoKey: pmc.setKeyframe([ikControl, ikPole], time=frameBeforeCurrent) pmc.headsUpMessage('BAMF!')
def ec_zero(attr="all"): selection = mc.ls(sl=True, type='transform') try: if attr == "sx" or attr == "sy" or attr == "sz": for i in range(0, len(selection)): mc.setAttr(selection[i]+"."+attr, 1.0) elif attr == "s": for i in range(0, len(selection)): mc.setAttr(selection[i]+"."+attr, 1, 1, 1, type="double3") elif attr == "all": for i in range(0, len(selection)): listat = mc.listAttr(selection[i], v=True, k=True) print "listat :" if listat is None: print "ERROR: listat is None : %s" % selection[i] else: for k in range(0, len(listat)): if listat[k] == "scaleX" or listat[k] == "scaleY" or listat[k] == "scaleZ" or listat[k] == "scale" or\ listat[k] == "visibility": mc.setAttr(selection[i]+"."+listat[k], 1.0) else: mc.setAttr(selection[i]+"."+listat[k], 0.0) else: if attr == "t" or attr == "r": for i in range(0, len(selection)): mc.setAttr(selection[i]+"."+attr, 0.0, 0.0, 0.0, type="double3") else: for i in range(0, len(selection)): mc.setAttr(selection[i]+"."+attr, 0.0) py.headsUpMessage("Reset: %s attribute of object %s" % (attr, selection)) except: py.warning("ERROR: %s attribute of %s is locked or otherwise unavailable to reset" % (attr, selection[i]))
def toggleEnvelope(): ssn = pm.optionMenu("selectedSSNode_menu", q=True, v=True) attr = ssn + ".envelope" if pm.getAttr(attr) == True: pm.setAttr(attr, False) pm.headsUpMessage("Shot Sculpt Disabled") elif pm.getAttr(attr) == False: pm.setAttr(attr, True) pm.headsUpMessage("Shot Sculpt Enabled")
def create_wall(self): selection = pm.selected() if not selection: msg = "Select the root of the base flap hierarchy" pm.headsUpMessage(msg) raise Exception(msg) with undo_chunk(auto_undo=True): SplitFlapWall.create( SplitFlap(selection[0]), padding=(self.padding.value(), -self.padding.value() * 0.5) ).make_dynamic()
def bake(self): minFrame = int(pm.playbackOptions(q=1,min=1)) maxFrame = int(pm.playbackOptions(q=1,max=1)) selectSet = pm.ls('*:bake_set*',typ='objectSet') if selectSet == []: pm.headsUpMessage( '???' ) else: bakeList = [] for x in selectSet: for y in x.flattened(): bakeList.append(y) pm.bakeResults(bakeList, sm=1, t=(minFrame, maxFrame), sb=1, pok=1, sac=0, bol=0, at=['tx','ty','tz','rx','ry','rz','sx','sy','sz'])
def list_axes(*args): """ :param args: :return: """ # Clear previus UI list clear_external_axis_list() # Check viewport robot selection # If robots are selected, list all external axes on selected robots selected_robots = mimic_utils.get_robot_roots() if selected_robots: robots = selected_robots # If no robots are selected, list all axes in the scene else: robots_in_scene = mimic_utils.get_robot_roots(all_robots=True) # If there are no robots in the scene, raise an exception if not robots_in_scene: raise MimicError('No robots in scene') else: robots = robots_in_scene # Keep track of selected robots without axes for a heads-up message selected_robots_without_axes = [] # For each robots, get a list of all its external axes for robot in robots: robots_external_axes = get_external_axis_names(robot) # If we're only looking at selected robots, check if each selected # robot has external axes. If not, add it to a list to display in # a heads up message if selected_robots: if not robots_external_axes: selected_robots_without_axes.append(robot) # Update Mimic UI with list of external axes for axis in robots_external_axes: append_string = robot + ': ' + axis pm.textScrollList('tsl_externalAxes', edit=True, append=append_string) if selected_robots_without_axes: robot_list_str = '' for each in selected_robots_without_axes: robot_list_str += each + ', ' pm.headsUpMessage('{} has no External Axes' .format(robot_list_str))
def SelectBlueprintJoint_callBack(self): selection = pm.ls(selection = True) self.blueprintJoints = self.FindBlueprintJoints(selection) if self.blueprintJoints == None: pm.confirmDialog(title = "Attach Geometry to Blueprint", message = "Blueprint joint selection invalid. \nTerminating tool.", button = ["Accept"], defaultButton = "Accept") elif self.geometry == None: pm.headsUpMessage("Please select the geometry you wish to attach to the specified blueprint joint(s).") pm.scriptJob(event = ["SelectionChanged", self.SelectGeometry_callBack], runOnce = True) else: self.AttachGeometryToBlueprint_attachment()
def matchFkToIk(fkControls, msgAttr='ikjoints', autoKey=True): """ Matches fkControls to match the current pose of the underlying ik duplicate joint chains Finds the ik joints using a previously created message connection to the attribute msgAttr """ ikJoints = None switchControl = None switchAttr = None for ctl in fkControls: if pmc.hasAttr(ctl, msgAttr): ikJoints = pmc.listConnections('{0}.{1}'.format(ctl, msgAttr), destination=False, source=True) attr = pmc.listConnections('{0}.ikfk'.format(ctl), destination=False, source=True, plugs=True, scn=True)[0] switchControl = attr.node() switchAttr = attr.name(includeNode=False) break if autoKey: frameBeforeCurrent = pmc.currentTime(q=True) - 1 pmc.setKeyframe(switchControl, attribute=switchAttr, time=frameBeforeCurrent, value=0, outTangentType='step') pmc.setKeyframe(fkControls, attribute='rotate', time=frameBeforeCurrent, outTangentType='step') pmc.setKeyframe(switchControl, attribute=switchAttr, value=1, outTangentType='step') for ikj, ctl in izip(ikJoints, fkControls): alignObjects(ctl, ikj, position=False, rotation=True) if autoKey: pmc.setKeyframe(fkControls, attribute='rotate') pmc.headsUpMessage('BAMF!')
def create_base_flaps(self): selection = pm.selected() if not selection: msg = "Select a base mesh to use for the flaps" pm.headsUpMessage(msg) raise Exception("Select a base mesh to use for the flaps") with undo_chunk(auto_undo=True): SplitFlap.create( base_flaps=selection, num_images=self.num_images.value(), rows=self.rows.value(), columns=self.columns.value(), radius=self.radius.value(), )
def _add_mFIZ_attrs_as_outputs(robot): """ """ # First, we get the IO numbers currently assigned to the robot and increment # ours by 1 to avoid conflicts io_numbers = [] robots_ios = get_io_names(robot) target_ctrl_path = mimic_utils.get_target_ctrl_path(robot) for io_name in robots_ios: io_numbers.append( pm.getAttr('{}.{}_ioNumber'.format(target_ctrl_path, io_name))) if io_numbers: io_number = max(io_numbers) + 1 else: io_number = 1 # For each FIZ Attribute, create and add IO to the robot postproc_id = 0 # Begin postproc_ID attr increment fiz_attrs = FIZ_ATTRS for attr in fiz_attrs: io_param_dict = {} io_param_dict['IO Name'] = attr io_param_dict['Postproc ID'] = str(postproc_id) io_param_dict['IO Number'] = io_number io_param_dict['Type'] = 'digital' io_param_dict['Resolution'] = '16-bit' io_param_dict['Ignore'] = False add_io(io_params=io_param_dict) io_number += 1 postproc_id += 16 pm.headsUpMessage('FIZ outputs added successfully to {}'.format(robot))
def ProcessInitialSelection(self): selection = pm.ls(selection = True) self.blueprintJoints = [] self.geometry = [] self.blueprintJoints = self.FindBlueprintJoints(selection) self.geometry = self.FindGeometry(selection) if self.blueprintJoints == None: pm.headsUpMessage("Please select the blueprint joint(s) you wish to attach geometry to.") pm.scriptJob(event = ["SelectionChanged", self.SelectBlueprintJoint_callBack], runOnce = True) elif self.geometry == None: pm.headsUpMessage("Please select the geometry you wish to attach to the specified blueprint joint.") pm.scriptJob(event = ["SelectionChanged", self.SelectGeometry_callBack], runOnce = True) else: self.AttachGeometryToBlueprint_attachment()
def mirror_locators(**kwargs): file_name = kwargs.setdefault('filename') unmirror = kwargs.setdefault('unmirror', False) file_list = ['up_eye', 'low_eye', 'cheek', 'up_mouth', 'low_mouth'] if unmirror is False: if file_name == 'all': for i in range(0, len(file_list)): file_path = get_file_path('expressions/%s' % file_list[i]) with open(file_path) as myfile: expressionData = myfile.read() mc.expression(string=expressionData, name="%s_Expression" % file_list[i], ae=1, uc='all') py.headsUpMessage('Mirroring for all enabled. Must unmirror to reset locations') else: file_path = get_file_path('expressions/%s' % file_name) with open(file_path) as myfile: expressionData = myfile.read() mc.expression(string=expressionData, name="%s_Expression" % file_name, ae=1, uc='all') py.headsUpMessage('Mirroring for %s enabled. Must unmirror to reset locations' % file_name) else: for i in range(0, len(file_list)): try: mc.delete('%s_Expression' % file_list[i]) except: pass py.headsUpMessage('Mirroring disabled')
def replaceController2Cube(): for origin in pm.ls(sl=1, v=1, ni=1): origin_shape = origin.getShape(type="nurbsCurve") if not origin_shape: pm.warning(u"找不到曲线 %s" % origin) pm.headsUpMessage(u"找不到曲线 %s" % origin) continue jnt_list = [] for constraint in set(origin.listConnections(type="constraint")): if constraint.type() in [ "aimConstraint", "tangentConstraint", "normalConstraint" ]: continue for jnt in constraint.listConnections(type='joint'): if jnt not in jnt_list: jnt_list.append(jnt) if not jnt_list: pm.warning(u"找不到骨骼 %s" % origin) pm.headsUpMessage(u"找不到骨骼 %s" % origin) continue start_jnt = jnt_list[0] child_list = pm.ls(start_jnt, dag=1, type="joint") if len(child_list) >= 2: # NOTE 获取层级之下的骨骼(0是自己) end_jnt = child_list[1] start_pt = dt.Point(*pm.xform(start_jnt, q=1, ws=1, t=1)) end_pt = dt.Point(*pm.xform(end_jnt, q=1, ws=1, t=1)) crv = generateCubeFromVector(start_pt, end_pt, origin) shape = crv.getShape() # NOTE 设置颜色 if origin_shape.overrideEnabled.get(): shape.overrideEnabled.set(1) if origin_shape.overrideRGBColors.get(): color = origin_shape.overrideColorRGB.get() shape.overrideColorRGB.set(color) else: color = origin_shape.overrideColor.get() shape.overrideColor.set(color) pm.parent(shape, origin, r=1, s=1) pm.delete(crv) name = origin_shape.name() pm.delete(origin_shape) shape.rename(name) else: # NOTE 说明层之下没有骨骼了 pm.warning(u"找不到下层级骨骼 %s" % origin) pm.headsUpMessage(u"找不到下层级骨骼 %s" % origin)
def create_facial_panel(offset=6.5): rm.create_bs_ctl() pm.headsUpMessage("Create Facial BlendShape Control", time=0.2) pm.refresh() sleep(0.1) facial_panelGp = ul.get_node('facial_panelGp') panel_pos = facial_panelGp.getTranslation('world') headBone = ul.get_node('CH_Head') headBone_pos = headBone.getTranslation('world') facial_panelGp.setTranslation([panel_pos[0], headBone_pos[1]+offset , panel_pos[2]], 'world') pm.headsUpMessage("Snap Control to Head", time=0.2) pm.refresh() sleep(0.1) import_facialtarget() pm.headsUpMessage("Import BlendShape and connect to control", time=0.2) pm.refresh() sleep(0.1) pm.parentConstraint('CH_Head', 'facial_panelGp', mo=True) pm.headsUpMessage("Parent Constraint facial_panelGp to head bone", time=0.2) pm.refresh() sleep(0.1)
def detect_topology(self): curr = time.time() sellist = [mesh for mesh in pm.ls(pm.pickWalk(d="down"), type="mesh") ] # pm.ls 返回场景中被选中的对象 # pickWalk命令允许您相对于当前选定的节点快速更改选择列表 if len(sellist) != 2: pm.headsUpMessage(u"请选中两个对象") return 0 self.lineEdit_pointNum.clear() self.lineEdit_edgeNum.clear() self.lineEdit_faceNum.clear() self.textEdit_transInfor1.clear() self.textEdit_transInfor2.clear() W = True # 拓扑是否一致 numlist = [(sel.numVertices(), sel.numEdges(), sel.numFaces()) for sel in sellist] # num_list是2*3的列表 # 点、线、面数量不一至,拓扑肯定不一样 if numlist[0][0] != numlist[1][0]: W = False x = numlist[0][0] - numlist[1][0] self.lineEdit_pointNum.setText(str(x)) if numlist[0][1] != numlist[1][1]: W = False x = numlist[0][0] - numlist[1][0] self.lineEdit_edgeNum.setText(str(x)) if numlist[0][2] != numlist[1][2]: W = False x = numlist[0][0] - numlist[1][0] self.lineEdit_faceNum.setText(str(x)) if numlist[0][1] > numlist[1][1]: sellist.reverse() edgenum = numlist[1][1] else: edgenum = numlist[0][1] # 把数量小的换到前面 res_list = [] dif_list1 = [] dif_list2 = [] for i, sel in enumerate(sellist): # enumerate() 列出数据和数据下标 (0, mesh1) edgeLoop_list = [] edge_list = set(range(edgenum)) while len(edge_list) > 1: idx = next(iter(edge_list), None) # iter() 函数用来生成迭代器 next()返回迭代器的下一个项目 if idx is None: break edge_loop_1 = pm.polySelect(sel, edgeLoop=idx, ns=1) # edgeLoop_list.append(edge_loop_1) # 遍历过的边的列表添加进循环边的表 edge_list -= set(edge_loop_1) # 遍历过的边删去 # NOTE 这里先判断一下同一个边序号获取的循环边数组是否一致 if i != 0: continue edge_loop_2 = pm.polySelect(sellist[1].e[edge_loop_1[0]], edgeLoop=idx, ns=1) # 第二个对象的第idx条循环边 if edge_loop_2 != edge_loop_1: dif_list1.append(edge_loop_1) dif_list2.append(edge_loop_2) res_list.append(edgeLoop_list) W = (res_list[0] == res_list[1]) if not W: for edgeloops in dif_list1: self.textEdit_transInfor1.append(str(edgeloops)) self.textEdit_transInfor1.append(u" ") for edgeloops in dif_list2: self.textEdit_transInfor2.append(str(edgeloops)) self.textEdit_transInfor2.append(u" ") pm.select([ "%s.e[%s]" % (sellist[0], e) for sel in dif_list1 for e in sel ], r=1) pm.select([ "%s.e[%s]" % (sellist[1], e) for sel in dif_list2 for e in sel ], add=1) self.lineEdit_result.setText(u"拓扑不一致") else: self.lineEdit_result.setText(u"拓扑一致") self.lineEdit_time.setText(str(time.time() - curr))
def update_external_axis(*args): """ Update external axis in Mimic. :param args: :return: """ # Get the selected item from the Mimic UI selection = pm.textScrollList('tsl_externalAxes', selectItem=True, query=True)[0] # Split the selection into the robot's name and the external axis name robot, axis_name = selection.split(': ') external_axis_params = _get_external_axis_params() axis_number = external_axis_params['Axis Number'] driving_attribute = external_axis_params['Driving Attribute'] position_limit_min = external_axis_params['Position Limit Min'] position_limit_max = external_axis_params['Position Limit Max'] velocity_limit = external_axis_params['Velocity Limit'] attach_robot_to_external = external_axis_params['Attach'] ingnore_in_postproc = external_axis_params['Ignore'] target_CTRL = '{}|robot_GRP|target_CTRL'.format(robot) # Set all External Axis attributes accordingly axis_parent_attribute = target_CTRL + '.' + axis_name # Check that the external axis number is unique if axis_number == pm.getAttr(axis_parent_attribute + '_axisNumber'): pass else: _check_external_axis_number(robot, axis_number) # If attach to robot is true, parent the robot's local control to the # external axis controller if attach_robot_to_external: # Check if the robot is already attached to an external axis controller if _check_if_robot_is_attached_to_external_axis(robot): raise MimicError('{} is already attached to an external ' \ 'axis controller'.format(robot)) # Get and check the proper controllers from viewport selection _, external_axis_CTRL = _get_selection_input() _attach_robot_to_external_axis(robot, external_axis_CTRL) # Check if our driving attribute needs to be updated. If not, do nothing # If so, update the connection axis_attribute_name = '{}|robot_GRP|target_CTRL.{}' \ .format(robot, axis_name) external_axis_CTRL, old_driving_attribute = _get_external_axis_connections(axis_attribute_name) if old_driving_attribute == driving_attribute: pass else: # Connect position attribute to driving attribute old_driving_attribute_path = external_axis_CTRL + '.' + old_driving_attribute new_driving_attribute_path = external_axis_CTRL + '.' + driving_attribute destination_attribute_name = axis_parent_attribute + '_position' pm.disconnectAttr(old_driving_attribute_path, destination_attribute_name) pm.connectAttr(new_driving_attribute_path, destination_attribute_name) # Update the external axis' position/rotation limits # Find the original driving attribute and disable it's axis limits _clear_external_axis_CTRL_limits(robot, external_axis_CTRL, old_driving_attribute, axis_name) _set_external_axis_CTRL_limits(robot, external_axis_CTRL, external_axis_params) # If the driving attribute is a translate attribute, we convert the user # input from millimeters to Maya's default unit of centimeters if 'translate' in driving_attribute: position_limit_min = position_limit_min / 10 position_limit_max = position_limit_max / 10 # Set all appropriate attributes on the robot pm.setAttr(axis_parent_attribute + '_axisNumber', lock=False) pm.setAttr(axis_parent_attribute + '_axisNumber', axis_number, lock=True) pm.setAttr(axis_parent_attribute + '_axisMin', position_limit_min) pm.setAttr(axis_parent_attribute + '_axisMax', position_limit_max) pm.setAttr(axis_parent_attribute + '_maxVelocity', velocity_limit) pm.setAttr(axis_parent_attribute + '_ignore', ingnore_in_postproc) # Select the external axis pm.select(external_axis_CTRL) pm.headsUpMessage('{}: Axis \'{}\' successfully updated' .format(robot, axis_name))
def add_external_axis(*args): """ Add an external axis to Mimic. :param args: :return: """ # Get the External Axis' parameters from the Mimic UI external_axis_params = _get_external_axis_params() axis_name = external_axis_params['Axis Name'] axis_number = external_axis_params['Axis Number'] driving_attribute = external_axis_params['Driving Attribute'] position_limit_min = external_axis_params['Position Limit Min'] position_limit_max = external_axis_params['Position Limit Max'] velocity_limit = external_axis_params['Velocity Limit'] attach_robot_to_external = external_axis_params['Attach'] ingnore_in_postproc = external_axis_params['Ignore'] # Get and check the proper controllers from viewport selection robot, external_axis_CTRL = _get_selection_input() target_CTRL = '{}|robot_GRP|target_CTRL'.format(robot) # Ensure axis name is unique _check_external_axis_name(robot, axis_name) # Check that the external axis number is unique _check_external_axis_number(robot, axis_number) # If attach to robot is true, parent the robot's local control to the # external axis controller if attach_robot_to_external: if _check_if_robot_is_attached_to_external_axis(robot): raise MimicError('{} is already attached to an external ' \ 'axis controller'.format(robot)) _attach_robot_to_external_axis(robot, external_axis_CTRL) # Add attributes to robots # Parent Attrubute parent_attribute = 'externalAxis_{}'.format(axis_name) pm.addAttr(target_CTRL, longName=parent_attribute, niceName='External Axis: {}'.format(axis_name), numberOfChildren=6, category='externalAxis', attributeType='compound') # Define 6 children of the External Axis parent attribute pm.addAttr(target_CTRL, longName=axis_name + '_position', niceName='Position', keyable=False, attributeType='float', parent=parent_attribute) pm.addAttr(target_CTRL, longName=axis_name + '_axisNumber', niceName='Axis Number', keyable=False, attributeType='long', minValue=1, maxValue=6, defaultValue=1, parent=parent_attribute) pm.addAttr(target_CTRL, longName=axis_name + '_axisMin', niceName='Min', keyable=False, attributeType='float', parent=parent_attribute) pm.addAttr(target_CTRL, longName=axis_name + '_axisMax', niceName='Max', keyable=False, attributeType='float', parent=parent_attribute) pm.addAttr(target_CTRL, longName=axis_name + '_maxVelocity', niceName='Max Velocity', keyable=False, attributeType='float', parent=parent_attribute) pm.addAttr(target_CTRL, longName=axis_name + '_ignore', niceName='Ignore', keyable=False, attributeType='bool', parent=parent_attribute) # If the driving attribute is a translate attribute, we convert the user # input from millimeters to Maya's default unit of centimeters if 'translate' in driving_attribute: position_limit_min = position_limit_min / 10 position_limit_max = position_limit_max / 10 # Set all External Axis attributes accordingly axis_parent_attribute = target_CTRL + '.' + axis_name pm.setAttr(axis_parent_attribute + '_axisNumber', axis_number, lock=True) pm.setAttr(axis_parent_attribute + '_axisMin', position_limit_min) pm.setAttr(axis_parent_attribute + '_axisMax', position_limit_max) pm.setAttr(axis_parent_attribute + '_maxVelocity', velocity_limit) pm.setAttr(axis_parent_attribute + '_ignore', ingnore_in_postproc) # Connect position attribute to driving attribute driving_attribute_name = external_axis_CTRL + '.' + driving_attribute destination_attribute_name = axis_parent_attribute + '_position' pm.connectAttr(driving_attribute_name, destination_attribute_name) # Set the External Axis control limits _set_external_axis_CTRL_limits(robot, external_axis_CTRL, external_axis_params) # Select the robot's target/tool controller tool_CTRL = robot + '|robot_GRP|tool_CTRL' if pm.objExists(tool_CTRL): pm.select(tool_CTRL) else: pm.select(target_CTRL) list_axes() pm.headsUpMessage('External axis \'{}\' added successfully to {}' .format(axis_name, robot))