def _build_avar_macro(self, cls_ctrl, avar, constraint=False, **kwargs): """ Factory method that create an avar that is not affiliated with any influence and is only used for connections. :param cls_ctrl: The class definition to use for the ctrl. :param avar: The Avar class instance to use. :param constraint: By default, a macro Avar don't affect it's influence (directly). This is False by default. :param kwargs: Any additional keyword arguments will be sent to the avar build method. :return: """ if cls_ctrl: avar._CLS_CTRL = cls_ctrl # Hack, find a more elegant way. self._build_avar( avar, callibrate_doritos=False, # We'll callibrate ourself since we're connecting manually. constraint=constraint, **kwargs ) if libPymel.is_valid_PyNode(avar.grp_anm): if self._grp_anm_avars_macro: avar.grp_anm.setParent(self._grp_anm_avars_macro) else: avar.grp_anm.setParent(self.grp_anm) if libPymel.is_valid_PyNode(avar.grp_rig): if self._grp_rig_avars_macro: avar.grp_rig.setParent(self._grp_rig_avars_macro) else: avar.grp_rig.setParent(self.grp_rig) # todo: raise warning? return avar #
def _build_avar_macro(self, cls_ctrl, avar, constraint=False, **kwargs): """ Factory method that create an avar that is not affiliated with any influence and is only used for connections. :param cls_ctrl: The class definition to use for the ctrl. :param avar: The Avar class instance to use. :param constraint: By default, a macro Avar don't affect it's influence (directly). This is False by default. :param kwargs: Any additional keyword arguments will be sent to the avar build method. :return: """ if cls_ctrl: avar._CLS_CTRL = cls_ctrl # Hack, find a more elegant way. self._build_avar( avar, callibrate_doritos= False, # We'll callibrate ourself since we're connecting manually. constraint=constraint, **kwargs) if libPymel.is_valid_PyNode(avar.grp_anm): if self._grp_anm_avars_macro: avar.grp_anm.setParent(self._grp_anm_avars_macro) else: avar.grp_anm.setParent(self.grp_anm) if libPymel.is_valid_PyNode(avar.grp_rig): if self._grp_rig_avars_macro: avar.grp_rig.setParent(self._grp_rig_avars_macro) else: avar.grp_rig.setParent(self.grp_rig) # todo: raise warning? return avar #
def unbuild(self, disconnect_attr=True): """ Call unbuild on each individual ctrls This allow the rig to save his ctrls appearance (shapes) and animation (animCurves). Note that this happen first so the rig can return to it's bind pose before anything else is done. :param disconnect_attr: Tell the unbuild if we want to disconnect the input translate, rotate, scale """ self.info("Un-building") # Ensure that there's no more connections in the input chain if disconnect_attr: self._disconnect_inputs() # Delete the ctrls in reverse hyerarchy order. ctrls = self.get_ctrls() ctrls = filter(libPymel.is_valid_PyNode, ctrls) ctrls = reversed(sorted(ctrls, key=libPymel.get_num_parents)) for ctrl in ctrls: ctrl.unbuild() if self.grp_anm is not None and libPymel.is_valid_PyNode(self.grp_anm): pymel.delete(self.grp_anm) self.grp_anm = None if self.grp_rig is not None and libPymel.is_valid_PyNode(self.grp_rig): pymel.delete(self.grp_rig) self.grp_rig = None self.globalScale = None # Reset any cached properties # todo: ensure it's the best way if '_cache' in self.__dict__: self.__dict__.pop('_cache')
def parent(self): if not self.chain_jnt: return None first_input = next(iter(self.chain_jnt), None) if libPymel.is_valid_PyNode(first_input): return first_input.getParent() return None
def unbuild(self, keep_shapes=True, *args, **kwargs): """ Delete ctrl setup, but store the animation and the shapes. """ if not libPymel.is_valid_PyNode(self.node): raise Exception("Can't hold ctrl attribute! Some information may be lost... {0}".format(self.node)) else: self.hold_attrs_all() self.hold_shapes() super(BaseCtrl, self).unbuild(*args, **kwargs) # Delete offset node if necessary. # Note that we delete the offset node AFTER deleting the original node. if libPymel.is_valid_PyNode(self.offset): pymel.delete(self.offset) self.offset = None
def prebuild(self): # Ensure we got a root joint # If needed, parent orphan joints to this one all_root_jnts = libPymel.ls_root_jnts() if not libPymel.is_valid_PyNode(self.grp_jnts): self.grp_jnts = pymel.createNode('joint', name='jnts') all_root_jnts.setParent(self.grp_jnts)
def parent(self): # TODO: We might want to search for specifically a joint in case the influence have intermediate objects. if not self.chain_jnt: return None first_input = next(iter(self.chain_jnt), None) if libPymel.is_valid_PyNode(first_input): return first_input.getParent() return None
def _build_avar_micro(self, cls_ctrl, avar, **kwargs): if cls_ctrl: avar._CLS_CTRL = cls_ctrl # Hack, find a more elegant way. self._build_avar(avar, **kwargs) if libPymel.is_valid_PyNode(avar.grp_anm): if self._grp_anm_avars_micro: avar.grp_anm.setParent(self._grp_anm_avars_micro) else: avar.grp_anm.setParent(self.grp_anm) if libPymel.is_valid_PyNode(avar.grp_rig): if self._grp_rig_avars_micro: avar.grp_rig.setParent(self._grp_rig_avars_micro) else: avar.grp_rig.setParent(self.grp_rig) # todo: raise warning?
def unbuild(self, **kwargs): for child in self.children: child.unbuild(**kwargs) # Delete the rig group if it isnt used anymore if libPymel.is_valid_PyNode(self.grp_rigs) and len(self.grp_rigs.getChildren()) == 0: pymel.delete(self.grp_rigs) self.grp_rigs = None super(RigRoot, self).unbuild(**kwargs)
def unbuild(self, keep_shapes=True, *args, **kwargs): """ Delete ctrl setup, but store the animation, shapes and rotate order0. """ if not libPymel.is_valid_PyNode(self.node): raise Exception( "Can't hold ctrl attribute! Some information may be lost... {0}" .format(self.node)) else: self.rotateOrder = self.node.rotateOrder.get() self.hold_attrs_all() self.hold_shapes() super(BaseCtrl, self).unbuild(*args, **kwargs) # Delete offset node if necessary. # Note that we delete the offset node AFTER deleting the original node. if libPymel.is_valid_PyNode(self.offset): pymel.delete(self.offset) self.offset = None
def unbuild(self, disconnect_attr=True): """ Call unbuild on each individual ctrls This allow the rig to save his ctrls appearance (shapes) and animation (animCurves). Note that this happen first so the rig can return to it's bind pose before anything else is done. :param disconnect_attr: Tell the unbuild if we want to disconnect the input translate, rotate, scale """ self.info("Un-building") # Ensure that there's no more connections in the input chain if disconnect_attr: for obj in self.input: if isinstance(obj, pymel.nodetypes.Transform): libAttr.disconnectAttr(obj.tx) libAttr.disconnectAttr(obj.ty) libAttr.disconnectAttr(obj.tz) libAttr.disconnectAttr(obj.rx) libAttr.disconnectAttr(obj.ry) libAttr.disconnectAttr(obj.rz) libAttr.disconnectAttr(obj.sx) libAttr.disconnectAttr(obj.sy) libAttr.disconnectAttr(obj.sz) # Delete the ctrls in reverse hyerarchy order. ctrls = self.get_ctrls() ctrls = filter(libPymel.is_valid_PyNode, ctrls) ctrls = reversed(sorted(ctrls, key=libPymel.get_num_parents)) for ctrl in ctrls: ctrl.unbuild() if self.grp_anm is not None and libPymel.is_valid_PyNode(self.grp_anm): pymel.delete(self.grp_anm) self.grp_anm = None if self.grp_rig is not None and libPymel.is_valid_PyNode(self.grp_rig): pymel.delete(self.grp_rig) self.grp_rig = None self.globalScale = None # Reset any cached properties # todo: ensure it's the best way if '_cache' in self.__dict__: self.__dict__.pop('_cache')
def post_build_module(self, module): # Raise warnings if a module leave junk in the scene. if module.grp_anm and not module.grp_anm.getChildren(): cmds.warning( "Found empty group {0}, please cleanup module {1}.".format( module.grp_anm.longName(), module)) pymel.delete(module.grp_anm) if module.grp_rig and not module.grp_rig.getChildren(): cmds.warning( "Found empty group {0}, please cleanup module {1}.".format( module.grp_rig.longName(), module)) pymel.delete(module.grp_rig) # Prevent animators from accidentaly moving offset nodes # TODO: Lock more? for ctrl in module.get_ctrls(): if libPymel.is_valid_PyNode(ctrl) and hasattr( ctrl, 'offset') and ctrl.offset: ctrl.offset.t.lock() ctrl.offset.r.lock() ctrl.offset.s.lock() # Parent modules grp_anm to main grp_anm if libPymel.is_valid_PyNode( module.grp_anm) and libPymel.is_valid_PyNode(self.grp_anm): module.grp_anm.setParent(self.grp_anm) # Constraint modules grp_rig to main grp_rig if libPymel.is_valid_PyNode( module.grp_rig) and libPymel.is_valid_PyNode(self.grp_rig): module.grp_rig.setParent(self.grp_rig) # Connect globalScale attribute to each modules globalScale. if module.globalScale: pymel.connectAttr(self.grp_anm.globalScale, module.globalScale, force=True) # Apply ctrl color if needed if self._color_ctrl: self.color_module_ctrl(module)
def _clean_invalid_pynodes(self): fnCanDelete = lambda x: (isinstance(x, (pymel.PyNode, pymel.Attribute)) and not libPymel.is_valid_PyNode(x)) for key, val in self.__dict__.iteritems(): if fnCanDelete(val): setattr(self, key, None) elif isinstance(val, (list, set, tuple)): for i in reversed(range(len(val))): if fnCanDelete(val[i]): val.pop(i) if len(val) == 0: setattr(self, key, None)
def _is_potential_influence(self, jnt): """ Take a potential influence and validate that it is not blacklisted. Currently any influence under the rig group is automatically ignored. :param mesh: A pymel.PyNode representing an influence object. :return: True if the object is a good deformable candidate. """ # Ignore any joint in the rig group (like joint used with ikHandles) if libPymel.is_valid_PyNode(self.grp_rig): if libPymel.is_child_of(jnt, self.grp_rig.node): return False return True
def post_build_module(self, module): """ Additional changes on the different module built for the rig :param module: The built module on which we want to do additional changes """ super(RigUnreal, self).post_build_module(module) # Allow animators to change the rotate order if needed for ctrl in module.get_ctrls(): if libPymel.is_valid_PyNode(ctrl): ctrl.node.rotateOrder.setKeyable(True)
def post_build_module(self, module): # Raise warnings if a module leave junk in the scene. if module.grp_anm and not module.grp_anm.getChildren(): cmds.warning("Found empty group {0}, please cleanup module {1}.".format( module.grp_anm.longName(), module )) pymel.delete(module.grp_anm) if module.grp_rig and not module.grp_rig.getChildren(): cmds.warning("Found empty group {0}, please cleanup module {1}.".format( module.grp_rig.longName(), module )) pymel.delete(module.grp_rig) # Prevent animators from accidentaly moving offset nodes # TODO: Lock more? for ctrl in module.get_ctrls(): if libPymel.is_valid_PyNode(ctrl) and hasattr(ctrl, 'offset') and ctrl.offset: ctrl.offset.t.lock() ctrl.offset.r.lock() ctrl.offset.s.lock() # Parent modules grp_anm to main grp_anm if libPymel.is_valid_PyNode(module.grp_anm) and libPymel.is_valid_PyNode(self.grp_anm): module.grp_anm.setParent(self.grp_anm) # Constraint modules grp_rig to main grp_rig if libPymel.is_valid_PyNode(module.grp_rig) and libPymel.is_valid_PyNode(self.grp_rig): module.grp_rig.setParent(self.grp_rig) # Connect globalScale attribute to each modules globalScale. if module.globalScale: pymel.connectAttr(self.grp_anm.globalScale, module.globalScale, force=True) # Apply ctrl color if needed if self._color_ctrl: self.color_module_ctrl(module) # Store the version of omtk used to generate the rig. module.version = api.get_version()
def fetch_avars(self): """ If a previously created network have be created holding avars connection, we'll transfert thoses connections back to the grp_rig node. Note that the avars have to been added to the grp_rig before.. """ if libPymel.is_valid_PyNode(self.avar_network): for attr_name in pymel.listAttr(self.avar_network, userDefined=True): attr_src = self.avar_network.attr(attr_name) attr_dst = self.grp_rig.attr(attr_name) libAttr.transfer_connections(attr_src, attr_dst) pymel.delete(self.avar_network) self.avar_network = None
def build(self, *args, **kwargs): super(RigCtrl, self).build(*args, **kwargs) if self._create_offset: self.offset = self.__createOffset__() self.fetch_attr_all() if libPymel.is_valid_PyNode(self.shape): libRigging.fetch_ctrl_shapes(self.shape, self.node) #pymel.delete(self.shape) self.shape = None #super(RigCtrl, self).build(*args, **kwargs) return self.node
def unbuild(self, **kwargs): """ :param kwargs: Potential parameters to pass recursively to the unbuild method of each module. :return: True if successful. """ # Unbuild all children for child in self.modules: if child.is_built(): child.unbuild(**kwargs) # Delete anm_grp if isinstance(self.grp_anm, CtrlRoot) and self.grp_anm.is_built(): self.grp_anm.unbuild() # Delete the rig group if it isnt used anymore if libPymel.is_valid_PyNode(self.grp_rig) and len(self.grp_rig.getChildren()) == 0: pymel.delete(self.grp_rig) self.grp_rig = None # Delete the displayLayers if libPymel.is_valid_PyNode(self.layer_anm): pymel.delete(self.layer_anm) self.layer_anm = None if libPymel.is_valid_PyNode(self.layer_geo): pymel.delete(self.layer_geo) self.layer_geo = None if libPymel.is_valid_PyNode(self.layer_rig): pymel.delete(self.layer_rig) # Remove any references to missing pynodes #HACK --> Remove clean invalid PyNode self._clean_invalid_pynodes() if self.modules is None: self.modules = [] return True
def get_parent_obj(self): """ :return: The object to act as the parent of the module if applicable. """ if self.parent is None: return None module = self.rig.get_module_by_input(self.parent) if module: desired_parent = module.get_parent(self.parent) if desired_parent: self.debug("Will be parented to {0}, {1}".format(module, desired_parent)) return desired_parent if libPymel.is_valid_PyNode(self.parent): self.debug("Can't recommend a parent. {0} is not in any known module.".format(self.parent)) return self.parent
def fetch_avars(self): """ If a previously created network have be created holding avars connection, we'll transfert thoses connections back to the grp_rig node. Note that the avars have to been added to the grp_rig before.. """ if libPymel.is_valid_PyNode(self.avar_network): for attr_name in pymel.listAttr(self.avar_network, userDefined=True): attr_src = self.avar_network.attr(attr_name) if not self.grp_rig.hasAttr(attr_name): self.warning("Can't fetch stored avar named {0}!".format(attr_name)) continue attr_dst = self.grp_rig.attr(attr_name) libAttr.transfer_connections(attr_src, attr_dst) # Ensure Maya don't delete our networks when removing the backup node... pymel.disconnectAttr(self.avar_network.message) pymel.delete(self.avar_network) self.avar_network = None
def color_module_ctrl(self, module): # # Set ctrls colors # color_by_side = { self.nomenclature.SIDE_L: self.LEFT_CTRL_COLOR, # Red self.nomenclature.SIDE_R: self.RIGHT_CTRL_COLOR # Blue } epsilon = 0.1 if module.grp_anm: nomenclature_anm = module.get_nomenclature_anm() for ctrl in module.get_ctrls(): if libPymel.is_valid_PyNode(ctrl): if not ctrl.drawOverride.overrideEnabled.get(): nomenclature_ctrl = nomenclature_anm.rebuild(ctrl.stripNamespace().nodeName()) side = nomenclature_ctrl.side color = color_by_side.get(side, self.CENTER_CTRL_COLOR) ctrl.drawOverride.overrideEnabled.set(1) ctrl.drawOverride.overrideColor.set(color)
def color_module_ctrl(self, module): # # Set ctrls colors # color_by_side = { self.nomenclature.SIDE_L: self.LEFT_CTRL_COLOR, # Red self.nomenclature.SIDE_R: self.RIGHT_CTRL_COLOR # Blue } epsilon = 0.1 if module.grp_anm: nomenclature_anm = module.get_nomenclature_anm() for ctrl in module.get_ctrls(): if libPymel.is_valid_PyNode(ctrl): if not ctrl.drawOverride.overrideEnabled.get(): nomenclature_ctrl = nomenclature_anm.rebuild( ctrl.name()) side = nomenclature_ctrl.side color = color_by_side.get(side, self.CENTER_CTRL_COLOR) ctrl.drawOverride.overrideEnabled.set(1) ctrl.drawOverride.overrideColor.set(color)
def _is_potential_deformable(self, mesh): """ Take a potential deformable shape and validate that it is not blacklisted. Currently any deformable under the rig group is automatically ignored. :param mesh: A pymel.PyNode representing a deformable object. :return: True if the object is a good deformable candidate. """ # Any intermediate shape is automatically discarded. if isinstance(mesh, pymel.nodetypes.Shape) and mesh.intermediateObject.get(): return False # Ignore any mesh in the rig group (like mesh used for ribbons) if libPymel.is_valid_PyNode(self.grp_rig): if libPymel.is_child_of(mesh, self.grp_rig.node): return False # Any mesh that is used as an input in a module is used for rigging. # if mesh in self._get_all_input_shapes(): # return False return True
def get_parent_obj(self): """ :return: The object to act as the parent of the module if applicable. """ if self.parent is None: return None module = self.rig.get_module_by_input(self.parent) if module: desired_parent = module.get_parent(self.parent) if desired_parent: self.debug("Will be parented to {0}, {1}".format( module, desired_parent)) return desired_parent if libPymel.is_valid_PyNode(self.parent): self.debug( "Can't recommend a parent. {0} is not in any known module.". format(self.parent)) return self.parent
def get_spaceswitch_targets(self, rig, module, jnt, add_world=True, world_name='World'): targets = [] target_names = [] # Resolve modules modules = set() while jnt: module = rig.get_module_by_input(jnt) if module: modules.add(module) #targets.update(module.get_pin_locations()) jnt = jnt.getParent() for module in modules: for target, target_name in module.get_pin_locations(): targets.append(target) target_names.append(target_name) if add_world and libPymel.is_valid_PyNode(rig.grp_jnt): targets.append(rig.grp_jnt) target_names.append(world_name) return targets, target_names
def export_network(_data, **kwargs): log.debug('CreateNetwork {0}'.format(_data)) # We'll deal with two additional attributes, '_network' and '_uid'. # Thoses two attributes allow us to find the network from the value and vice-versa. # Note that since the '_uid' refer to the current python context, it's value could be erroned when calling import_network. # However the change of collisions are extremely improbable so checking the type of the python variable is sufficient. # Please feel free to provide a better design if any if possible. # Optimisation: Use existing network if already present in scene if hasattr(_data, '_network') and libPymel.is_valid_PyNode(_data._network): network = _data._network else: # Automaticly name network whenever possible if hasattr(_data, '__getNetworkName__') and _data.__getNetworkName__ is None: networkName = _data.__class__.__name__ else: networkName = _data.__getNetworkName__() if hasattr(_data, '__getNetworkName__') else _data.__class__.__name__ _data._network = networkName network = pymel.createNode('network', name=networkName) # Ensure the network have the current python id stored if not network.hasAttr('_uid'): pymel.addAttr(network, longName='_uid', niceName='_uid', at='long') # todo: validate attributeType network._uid.set(id(_data)) # Convert _pData to basic data dictionary (recursive for now) dicData = core._export_basicData(_data, _bRecursive=False, **kwargs) assert(isinstance(dicData, dict)) fnNet = network.__apimfn__() for key, val in dicData.items(): if val is not None: if key == '_class' or key[0] != '_': # Attributes starting with '_' are protected or private _addAttr(fnNet, key, val) return network
def pre_build(self, create_master_grp=False, create_grp_jnt=True, create_grp_anm=True, create_grp_rig=True, create_grp_geo=True, create_display_layers=True, create_grp_backup=False): # Hack: Invalidate any cache before building anything. # This ensure we always have fresh data. try: del self._cache except AttributeError: pass # Look for a root joint if create_grp_jnt: # For now, we will determine the root jnt by it's name used in each rig. Not the best solution, # but currently the safer since we want to support multiple deformation layer if not libPymel.is_valid_PyNode(self.grp_jnt): # self.grp_jnt = next(iter(libPymel.ls_root(type='joint')), None) if cmds.objExists(self.nomenclature.root_jnt_name): self.grp_jnt = pymel.PyNode( self.nomenclature.root_jnt_name) else: self.warning( "Could not find any root joint, master ctrl will not drive anything" ) # self.grp_jnt = pymel.createNode('joint', name=self.nomenclature.root_jnt_name) # Ensure all joints have segmentScaleComprensate deactivated. # This allow us to scale adequately and support video game rigs. # If for any mean stretch and squash are necessary, implement # them on a new joint chains parented to the skeletton. # TODO: Move elsewere? all_jnts = libPymel.ls(type='joint') for jnt in all_jnts: jnt.segmentScaleCompensate.set(False) # Create the master grp if create_master_grp: self.grp_master = self.build_grp( RigGrp, self.grp_master, self.name + '_' + self.nomenclature.type_rig) # Create grp_anm if create_grp_anm: grp_anim_size = CtrlRoot._get_recommended_radius(self) self.grp_anm = self.build_grp(CtrlRoot, self.grp_anm, self.nomenclature.root_anm_name, size=grp_anim_size) # Create grp_rig if create_grp_rig: self.grp_rig = self.build_grp(RigGrp, self.grp_rig, self.nomenclature.root_rig_name) # Create grp_geo if create_grp_geo: all_geos = libPymel.ls_root_geos() self.grp_geo = self.build_grp(RigGrp, self.grp_geo, self.nomenclature.root_geo_name) #if all_geos: # all_geos.setParent(self.grp_geo) if create_grp_backup: self.grp_backup = self.build_grp( RigGrp, self.grp_backup, self.nomenclature.root_backup_name) #Parent all grp on the master grp if self.grp_master: if self.grp_jnt: self.grp_jnt.setParent(self.grp_master.node) if self.grp_anm: self.grp_anm.setParent(self.grp_master.node) if self.grp_rig: self.grp_rig.setParent(self.grp_master.node) if self.grp_geo: self.grp_geo.setParent(self.grp_master.node) if self.grp_backup: self.grp_backup.setParent(self.grp_master.node) # Setup displayLayers if create_display_layers: if not pymel.objExists(self.nomenclature.layer_anm_name): self.layer_anm = pymel.createDisplayLayer( name=self.nomenclature.layer_anm_name, number=1, empty=True) self.layer_anm.color.set(17) # Yellow else: self.layer_anm = pymel.PyNode(self.nomenclature.layer_anm_name) pymel.editDisplayLayerMembers(self.layer_anm, self.grp_anm, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_rig_name): self.layer_rig = pymel.createDisplayLayer( name=self.nomenclature.layer_rig_name, number=1, empty=True) self.layer_rig.color.set(13) # Red # self.layer_rig.visibility.set(0) # Hidden self.layer_rig.displayType.set(2) # Frozen else: self.layer_rig = pymel.PyNode(self.nomenclature.layer_rig_name) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_rig, noRecurse=True) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_jnt, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_geo_name): self.layer_geo = pymel.createDisplayLayer( name=self.nomenclature.layer_geo_name, number=1, empty=True) self.layer_geo.color.set(12) # Green? self.layer_geo.displayType.set(2) # Frozen else: self.layer_geo = pymel.PyNode(self.nomenclature.layer_geo_name) pymel.editDisplayLayerMembers(self.layer_geo, self.grp_geo, noRecurse=True)
def build(self, _bOrientIkCtrl=True, *args, **kwargs): super(IK, self).build(*args, **kwargs) # Duplicate input chain (we don't want to move the hierarchy) # Todo: implement a duplicate method in omtk.libs.libPymel.PyNodeChain # Create ikChain and fkChain self._chain_ik = pymel.duplicate(self.input, renameChildren=True, parentOnly=True) for oInput, oIk, in zip(self.input, self._chain_ik): pNameMap = NameMap(oInput, _sType='rig') oIk.rename(pNameMap.Serialize('ik')) self._chain_ik[0].setParent(self._oParent) # Trick the IK system (temporary solution) oChainS = self._chain_ik[0] oChainE = self._chain_ik[self.iCtrlIndex] # Compute chain length self._chain_length = self._chain.length() # Compute swivel position p3SwivelPos = self.calc_swivel_pos() # Create ikChain grp_ikChain = pymel.createNode('transform', name=self._pNameMapRig.Serialize('ikChain'), parent=self.grp_rig) grp_ikChain.setMatrix(oChainS.getMatrix(worldSpace=True), worldSpace=True) oChainS.setParent(grp_ikChain) # Create ikEffector self._oIkHandle, oIkEffector = pymel.ikHandle(startJoint=oChainS, endEffector=oChainE, solver='ikRPsolver') self._oIkHandle.rename(self._pNameMapRig.Serialize('ikHandle')) self._oIkHandle.setParent(grp_ikChain) oIkEffector.rename(self._pNameMapRig.Serialize('ikEffector')) # Create ctrls if not isinstance(self.ctrlIK, CtrlIk): self.ctrlIK = CtrlIk() self.ctrlIK.build() #self.ctrlIK = CtrlIk(_create=True) self.ctrlIK.setParent(self.grp_anm) self.ctrlIK.rename(self._pNameMapAnm.Serialize('ik')) self.ctrlIK.offset.setTranslation(oChainE.getTranslation(space='world'), space='world') if _bOrientIkCtrl is True: self.ctrlIK.offset.setRotation(oChainE.getRotation(space='world'), space='world') if not isinstance(self.ctrl_swivel, CtrlIkSwivel): self.ctrl_swivel = CtrlIkSwivel() self.ctrl_swivel.build() #self.ctrl_swivel = CtrlIkSwivel(_oLineTarget=self.input[1], _create=True) self.ctrl_swivel.setParent(self.grp_anm) self.ctrl_swivel.rename(self._pNameMapAnm.Serialize('ikSwivel')) self.ctrl_swivel.offset.setTranslation(p3SwivelPos, space='world') self.ctrl_swivel.offset.setRotation(self.input[self.iCtrlIndex - 1].getRotation(space='world'), space='world') self.swivelDistance = self._chain_length # Used in ik/fk switch # # Create softIk node and connect user accessible attributes to it. # oAttHolder = self.ctrlIK fnAddAttr = functools.partial(libAttr.addAttr, hasMinValue=True, hasMaxValue=True) attInRatio = fnAddAttr(oAttHolder, longName='SoftIkRatio', niceName='SoftIK', defaultValue=0, minValue=0, maxValue=.5, k=True) attInStretch = fnAddAttr(oAttHolder, longName='Stretch', niceName='Stretch', defaultValue=0, minValue=0, maxValue=1.0, k=True) rig_softIkNetwork = SoftIkNode() rig_softIkNetwork.build() pymel.connectAttr(attInRatio, rig_softIkNetwork.inRatio) pymel.connectAttr(attInStretch, rig_softIkNetwork.inStretch) pymel.connectAttr(grp_ikChain.worldMatrix, rig_softIkNetwork.inMatrixS) pymel.connectAttr(self.ctrlIK.worldMatrix, rig_softIkNetwork.inMatrixE) rig_softIkNetwork.inChainLength.set(self._chain_length) # Constraint effector attOutRatio = rig_softIkNetwork.outRatio attOutRatioInv = libRigging.CreateUtilityNode('reverse', inputX=rig_softIkNetwork.outRatio).outputX pymel.select(clear=True) pymel.select(self.ctrlIK, grp_ikChain, self._oIkHandle) constraint = pymel.pointConstraint() constraint.rename(constraint.name().replace('pointConstraint', 'softIkConstraint')) pymel.select(constraint) weight_inn, weight_out = constraint.getWeightAliasList() pymel.connectAttr(attOutRatio, weight_inn) pymel.connectAttr(attOutRatioInv, weight_out) # Constraint joints stretch attOutStretch = rig_softIkNetwork.outStretch num_jnts = len(self._chain_ik) for i in range(1, num_jnts): obj = self._chain_ik[i] pymel.connectAttr( libRigging.CreateUtilityNode('multiplyDivide', input1X=attOutStretch, input1Y=attOutStretch, input1Z=attOutStretch, input2=obj.t.get()).output, obj.t, force=True) # Connect rig -> anm pymel.orientConstraint(self.ctrlIK, oChainE, maintainOffset=True) pymel.poleVectorConstraint(self.ctrl_swivel, self._oIkHandle) # Connect to parent if libPymel.is_valid_PyNode(self.parent): pymel.parentConstraint(self.parent, grp_ikChain, maintainOffset=True) for source, target in zip(self._chain_ik, self._chain): pymel.parentConstraint(source, target)
def is_built(self): return libPymel.is_valid_PyNode(self.node)
def build(self, module, ref, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, **kwargs): """ Create an Interactive controller that follow a geometry. :param module: ??? :param ref: :param ref_tm: :param grp_rig: :param obj_mesh: :param u_coord: :param v_coord: :param kwargs: :return: """ # HACK: Ensure flipped shapes are correctly restaured... # This is necessary since when holded, the scale of the ctrl is set to identity. # However ctrl from the right side have an inverted scale on the x axis. -_- if flip_lr and libPymel.is_valid_PyNode(self.shapes): self.shapes.sx.set(-1) pymel.makeIdentity(self.shapes, rotate=True, scale=True, apply=True) # todo: Simplify the setup, too many nodes super(InteractiveCtrl, self).build(**kwargs) #nomenclature_anm = self.get_nomenclature_anm(parent) nomenclature_rig = module.rig.nomenclature( suffix=module.rig.nomenclature.type_rig) #nomenclature_rig = self.get_nomenclature_rig(parent) # TODO: Only use position instead of PyNode or Matrix? if ref_tm is None: ref_tm = ref.getMatrix(worldSpace=True) pos_ref = ref_tm.translate # Resolve u and v coordinates # todo: check if we really want to resolve the u and v ourself since it's now connected. if obj_mesh is None: # We'll scan all available geometries and use the one with the shortest distance. meshes = libRigging.get_affected_geometries(ref) meshes = list(set(meshes) & set(module.rig.get_meshes())) obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes( meshes, pos_ref) else: _, out_u, out_v = libRigging.get_closest_point_on_shape( obj_mesh, pos_ref) if u_coord is None: u_coord = out_u if v_coord is None: v_coord = out_v if obj_mesh is None: raise Exception( "Can't find mesh affected by {0}. Skipping doritos ctrl setup." ) if self.jnt: module.debug( 'Creating doritos on {0} using {1} as reference'.format( obj_mesh, self.jnt)) else: module.debug('Creating doritos on {0}'.format(obj_mesh)) # Initialize external stack # Normally this would be hidden from animators. stack_name = nomenclature_rig.resolve('doritosStack') stack = classNode.Node(self) stack.build(name=stack_name) stack.setTranslation(pos_ref) # Add sensibility attributes # The values will be computed when attach_ctrl will be called libAttr.addAttr_separator(module.grp_rig, "ctrlCalibration") self.attr_sensitivity_tx = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TX, defaultValue=1.0) self.attr_sensitivity_ty = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TY, defaultValue=1.0) self.attr_sensitivity_tz = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TZ, defaultValue=1.0) self.attr_sensitivity_tx.set(channelBox=True) self.attr_sensitivity_ty.set(channelBox=True) self.attr_sensitivity_tz.set(channelBox=True) # Note that to only check in the Z axis, we'll do a raycast first. # If we success this will become our reference position. ''' pos = pos_ref pos.z = 999 dir = pymel.datatypes.Point(0,0,-1) result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None) if result: pos_ref = result ctrl_tm.translate = result ''' # Create the layer_fol that will follow the geometry layer_fol_name = nomenclature_rig.resolve('doritosFol') layer_fol = stack.append_layer() layer_fol.rename(layer_fol_name) #layer_fol.setParent(self.grp_rig) # TODO: Validate that we don't need to inverse the rotation separately. fol_mesh = None if follow_mesh: fol_name = nomenclature_rig.resolve('doritosFollicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord) fol_mesh = fol_shape.getParent() self.follicle = fol_mesh fol_mesh.rename(fol_name) pymel.parentConstraint(fol_mesh, layer_fol, maintainOffset=True) fol_mesh.setParent(self.grp_rig) # HACK: Fix rotation issues. # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since # any deformation can affect the normal of the faces. jnt_head = module.rig.get_head_jnt() if jnt_head: pymel.disconnectAttr(layer_fol.rx) pymel.disconnectAttr(layer_fol.ry) pymel.disconnectAttr(layer_fol.rz) pymel.orientConstraint(jnt_head, layer_fol, maintainOffset=True) else: self.follicle = layer_fol pymel.parentConstraint(ref, layer_fol, maintainOffset=True) # # Constraint a specic controller to the avar doritos stack. # Call this method after connecting the ctrl to the necessary avars. # The sensibility of the doritos will be automatically computed in this step if necessary. # # Create inverted attributes for sensibility util_sensitivity_inv = libRigging.create_utility_node( 'multiplyDivide', operation=2, input1X=1.0, input1Y=1.0, input1Z=1.0, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz) attr_sensibility_lr_inv = util_sensitivity_inv.outputX attr_sensibility_ud_inv = util_sensitivity_inv.outputY attr_sensibility_fb_inv = util_sensitivity_inv.outputZ # Add an inverse node that will counter animate the position of the ctrl. # TODO: Rename layer_doritos_name = nomenclature_rig.resolve('doritosInv') layer_doritos = pymel.createNode('transform', name=layer_doritos_name) layer_doritos.setParent(stack.node) # Create inverse attributes for the ctrl attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=self.node.t, input2=[-1, -1, -1]).output attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide', input1=self.node.r, input2=[-1, -1, -1]).output attr_ctrl_inv_t = libRigging.create_utility_node( 'multiplyDivide', input1=attr_ctrl_inv_t, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz).output if flip_lr: attr_doritos_tx = libRigging.create_utility_node( 'multiplyDivide', input1X=attr_ctrl_inv_t.outputX, input2X=-1).outputX else: attr_doritos_tx = attr_ctrl_inv_t.outputX attr_doritos_ty = attr_ctrl_inv_t.outputY attr_doritos_tz = attr_ctrl_inv_t.outputZ pymel.connectAttr(attr_doritos_tx, layer_doritos.tx) pymel.connectAttr(attr_doritos_ty, layer_doritos.ty) pymel.connectAttr(attr_doritos_tz, layer_doritos.tz) pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r) # Apply scaling on the ctrl parent. # This is were the 'black magic' happen. if flip_lr: attr_ctrl_offset_sx_inn = libRigging.create_utility_node( 'multiplyDivide', input1X=self.attr_sensitivity_tx, input2X=-1).outputX else: attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX) pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY) pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ) # Apply sensibility on the ctrl shape ctrl_shape = self.node.getShape() tmp = pymel.duplicate(self.node.getShape())[0] ctrl_shape_orig = tmp.getShape() ctrl_shape_orig.setParent(self.node, relative=True, shape=True) ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name())) pymel.delete(tmp) ctrl_shape_orig.intermediateObject.set(True) for cp in ctrl_shape.cp: cp.set(0, 0, 0) # Counter-scale the shape attr_adjustement_sx_inn = attr_sensibility_lr_inv attr_adjustement_sy_inn = attr_sensibility_ud_inv attr_adjustement_sz_inn = attr_sensibility_fb_inv attr_adjustement_scale = libRigging.create_utility_node( 'composeMatrix', inputScaleX=attr_adjustement_sx_inn, inputScaleY=attr_adjustement_sy_inn, inputScaleZ=attr_adjustement_sz_inn).outputMatrix attr_adjustement_rot = libRigging.create_utility_node( 'composeMatrix', inputRotateX=self.node.rotateX, inputRotateY=self.node.rotateY, inputRotateZ=self.node.rotateZ).outputMatrix attr_adjustement_rot_inv = libRigging.create_utility_node( 'inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix attr_adjustement_tm = libRigging.create_utility_node( 'multMatrix', matrixIn=[ attr_adjustement_rot, attr_adjustement_scale, attr_adjustement_rot_inv ]).matrixSum attr_transform_geometry = libRigging.create_utility_node( 'transformGeometry', transform=attr_adjustement_tm, inputGeometry=ctrl_shape_orig.local).outputGeometry pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True) # Constraint ctrl pymel.parentConstraint(layer_doritos, self.offset, maintainOffset=False, skipRotate=['x', 'y', 'z']) pymel.orientConstraint(layer_doritos.getParent(), self.offset, maintainOffset=True) # Clean dag junk if grp_rig: stack.setParent(grp_rig) if fol_mesh: fol_mesh.setParent(grp_rig)
def get_spaceswitch_targets(self, module, jnt, add_world=True, add_root=True, add_local=True, root_name='Root', world_name='World', **kwargs): """ Return the list of target used by the space switch of a controller. It will try get all module pin location it can find from it's jnt parameter :param module: The module on which we want to process space switch targets :param jnt: A list of joint that will be used to find associated modules to find space objects :param add_world: Is the world will be added as a space switch target of the ctrl :param add_root: Is the root will be added as a space switch target of the ctrl :param add_local: Is the local option will be used. Local will be the same than the first module target :param root_name: The name in the list of targets the root will take :param world_name: The name in the list of targets the world will take :param kwargs: Additional parameters :return: The targets obj, name and index of the found space switch target """ targets = [] targets.extend(self.targets) # The target # Initialize the target name list with the same number of item than the targets keeped before target_names = [] for i in range(0, len(targets)): target_names.append(None) indexes = [] indexes.extend(self.targets_indexes) # Use the grp_rip node as the world target. It will always be the first target in the list if add_world and libPymel.is_valid_PyNode(module.rig.grp_rig): if module.rig.grp_rig not in targets: targets.append(module.rig.grp_rig) # World will always be -1 indexes.append( self.get_bestmatch_index( module.rig.grp_rig, constants.SpaceSwitchReservedIndex.world)) target_names.append(world_name) else: idx = targets.index(module.rig.grp_rig) target_names[idx] = world_name # Add the master ctrl as a spaceswitch target if libPymel.is_valid_PyNode(module.rig.grp_anm): if module.rig.grp_anm not in targets: targets.append(module.rig.grp_anm) target_names.append(root_name) # The root will always be index 1, because we want to let local to be 0 indexes.append( self.get_bestmatch_index( module.rig.grp_anm, constants.SpaceSwitchReservedIndex.root)) else: idx = targets.index(module.rig.grp_anm) target_names[idx] = root_name # Resolve modules targets first_module = True while jnt: m = module.rig.get_module_by_input(jnt) # We will not add as a target the first modules target found if we add the local space # The local space is an equivalent to not having any space activated so as if it follow it's parent which # would be the first module found if m and ((add_local and not first_module) or not add_local): target, target_name = m.get_pin_locations(jnt) if target: if target not in targets: targets.append(target) target_names.append(target_name) indexes.append(self.get_bestmatch_index(target)) else: idx = targets.index(target) target_names[idx] = target_name else: first_module = False jnt = jnt.getParent() # Final check to ensure that not target is None. If one None target is found, we need to remove it and let the # index in the space attribute to be free to fix manually for i, t in reversed(list(enumerate(targets))): if t is None: log.warning("Space switch index {0} target is None on {1}, " "maybe a manual connection will be needed".format( indexes[i], self.name)) targets.pop(i) target_names.pop(i) indexes.pop(i) return targets, target_names, indexes
def pre_build(self, create_master_grp=False, create_grp_jnt=True, create_grp_anm=True, create_grp_rig=True, create_grp_geo=True, create_display_layers=True, create_grp_backup=False, create_layer_jnt=False): # Hack: Invalidate any cache before building anything. # This ensure we always have fresh data. self._clear_cache() # Look for a root joint if create_grp_jnt: # For now, we will determine the root jnt by it's name used in each rig. Not the best solution, # but currently the safer since we want to support multiple deformation layer if not libPymel.is_valid_PyNode(self.grp_jnt): # self.grp_jnt = next(iter(libPymel.ls_root(type='joint')), None) if cmds.objExists(self.nomenclature.root_jnt_name): self.grp_jnt = pymel.PyNode(self.nomenclature.root_jnt_name) else: self.warning("Could not find any root joint, master ctrl will not drive anything") # self.grp_jnt = pymel.createNode('joint', name=self.nomenclature.root_jnt_name) # Create the master grp if create_master_grp: self.grp_master = self.build_grp(RigGrp, self.grp_master, self.name + '_' + self.nomenclature.type_rig) # Create grp_anm if create_grp_anm: grp_anim_size = CtrlRoot._get_recommended_radius(self) self.grp_anm = self.build_grp(CtrlRoot, self.grp_anm, self.nomenclature.root_anm_name, size=grp_anim_size) # Create grp_rig if create_grp_rig: self.grp_rig = self.build_grp(RigGrp, self.grp_rig, self.nomenclature.root_rig_name) # Create grp_geo if create_grp_geo: all_geos = libPymel.ls_root_geos() self.grp_geo = self.build_grp(RigGrp, self.grp_geo, self.nomenclature.root_geo_name) # if all_geos: # all_geos.setParent(self.grp_geo) if create_grp_backup: self.grp_backup = self.build_grp(RigGrp, self.grp_backup, self.nomenclature.root_backup_name) # Parent all grp on the master grp if self.grp_master: if self.grp_jnt: self.grp_jnt.setParent(self.grp_master.node) if self.grp_anm: self.grp_anm.setParent(self.grp_master.node) if self.grp_rig: self.grp_rig.setParent(self.grp_master.node) if self.grp_geo: self.grp_geo.setParent(self.grp_master.node) if self.grp_backup: self.grp_backup.setParent(self.grp_master.node) # Setup displayLayers if create_display_layers: if not pymel.objExists(self.nomenclature.layer_anm_name): self.layer_anm = pymel.createDisplayLayer(name=self.nomenclature.layer_anm_name, number=1, empty=True) self.layer_anm.color.set(17) # Yellow else: self.layer_anm = pymel.PyNode(self.nomenclature.layer_anm_name) pymel.editDisplayLayerMembers(self.layer_anm, self.grp_anm, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_rig_name): self.layer_rig = pymel.createDisplayLayer(name=self.nomenclature.layer_rig_name, number=1, empty=True) self.layer_rig.color.set(13) # Red # self.layer_rig.visibility.set(0) # Hidden self.layer_rig.displayType.set(2) # Frozen else: self.layer_rig = pymel.PyNode(self.nomenclature.layer_rig_name) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_rig, noRecurse=True) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_jnt, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_geo_name): self.layer_geo = pymel.createDisplayLayer(name=self.nomenclature.layer_geo_name, number=1, empty=True) self.layer_geo.color.set(12) # Green? self.layer_geo.displayType.set(2) # Frozen else: self.layer_geo = pymel.PyNode(self.nomenclature.layer_geo_name) pymel.editDisplayLayerMembers(self.layer_geo, self.grp_geo, noRecurse=True) if create_layer_jnt: if not pymel.objExists(self.nomenclature.layer_jnt_name): self.layer_jnt = pymel.createDisplayLayer(name=self.nomenclature.layer_jnt_name, number=1, empty=True) self.layer_jnt.color.set(1) # Black? self.layer_jnt.visibility.set(0) # Hidden self.layer_jnt.displayType.set(2) # Frozen else: self.layer_jnt = pymel.PyNode(self.nomenclature.layer_jnt_name) pymel.editDisplayLayerMembers(self.layer_jnt, self.grp_jnt, noRecurse=True)
def pre_build(self): super(RigSqueeze, self).pre_build(create_grp_jnt=False) # # Create specific group related to squeeze rig convention # if not libPymel.is_valid_PyNode(self.grp_all): if cmds.objExists(self.nomenclature.root_all_name): self.grp_all = pymel.PyNode(self.nomenclature.root_all_name) else: self.grp_all = pymel.createNode('transform', name=self.nomenclature.root_all_name) if not libPymel.is_valid_PyNode(self.grp_model): if cmds.objExists(self.nomenclature.root_model_name): self.grp_model = pymel.PyNode(self.nomenclature.root_model_name) else: self.grp_model = pymel.createNode('transform', name=self.nomenclature.root_model_name) if not libPymel.is_valid_PyNode(self.grp_proxy): if cmds.objExists(self.nomenclature.root_proxy_name): self.grp_proxy = pymel.PyNode(self.nomenclature.root_proxy_name) else: self.grp_proxy = pymel.createNode('transform', name=self.nomenclature.root_proxy_name) if not libPymel.is_valid_PyNode(self.grp_fx): if cmds.objExists(self.nomenclature.root_fx_name): self.grp_fx = pymel.PyNode(self.nomenclature.root_fx_name) else: self.grp_fx = pymel.createNode('transform', name=self.nomenclature.root_fx_name) #Parent all groups in the main grp_all pymel.parent(self.grp_anm, self.grp_all) #grp_anm is not a Node, but a Ctrl self.grp_rig.setParent(self.grp_all) self.grp_fx.setParent(self.grp_all) self.grp_model.setParent(self.grp_all) self.grp_proxy.setParent(self.grp_all) self.grp_geo.setParent(self.grp_all) ''' if self.grp_jnt.getParent() is None: self.grp_jnt.setParent(self.grp_all) ''' #Lock and hide all attributes we don't want the animator to play with libAttr.lock_hide_trs(self.grp_all) libAttr.lock_hide_trs(self.grp_rig) libAttr.lock_hide_trs(self.grp_fx) libAttr.lock_hide_trs(self.grp_model) libAttr.lock_hide_trs(self.grp_proxy) libAttr.lock_hide_trs(self.grp_geo) libAttr.hide_scale(self.grp_anm) #Hide some group #self.grp_jnt.visibility.set(False) self.grp_rig.visibility.set(False) self.grp_fx.visibility.set(False) self.grp_model.visibility.set(False) # # Add root ctrl attributes specific to squeeze # if not self.grp_anm.hasAttr(self.GROUP_NAME_DISPLAY, checkShape=False): libAttr.addAttr_separator(self.grp_anm, self.GROUP_NAME_DISPLAY) #Display Mesh if not self.grp_anm.hasAttr(self.ATTR_NAME_DISPLAY_MESH, checkShape=False): attr_displayMesh = libAttr.addAttr(self.grp_anm, longName=self.ATTR_NAME_DISPLAY_MESH, at='short', k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1) else: attr_displayMesh = self.grp_anm.attr(self.ATTR_NAME_DISPLAY_MESH) #Display Ctrl if not self.grp_anm.hasAttr(self.ATTR_NAME_DISPLAY_CTRL, checkShape=False): attr_displayCtrl = libAttr.addAttr(self.grp_anm, longName=self.ATTR_NAME_DISPLAY_CTRL, at='short', k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=1) else: attr_displayCtrl = self.grp_anm.attr(self.ATTR_NAME_DISPLAY_CTRL) #Display Proxy if not self.grp_anm.hasAttr(self.ATTR_NAME_DISPLAY_PROXY, checkShape=False): attr_displayProxy = libAttr.addAttr(self.grp_anm, longName=self.ATTR_NAME_DISPLAY_PROXY, at='short', k=True, hasMinValue=True, hasMaxValue=True, minValue=0, maxValue=1, defaultValue=0) else: attr_displayProxy = self.grp_anm.attr(self.ATTR_NAME_DISPLAY_PROXY) pymel.connectAttr(attr_displayMesh, self.grp_geo.visibility, force=True) pymel.connectAttr(attr_displayProxy, self.grp_proxy.visibility, force=True) for child in self.grp_anm.getChildren(): pymel.connectAttr(attr_displayCtrl, child.visibility, force=True)
def _is_influence(self, jnt): # Ignore any joint in the rig group (like joint used with ikHandles) if libPymel.is_valid_PyNode(self.grp_rig): if libPymel.is_child_of(jnt, self.grp_rig.node): return False return True
def _createAttribute(_name, _val): if isinstance(_val, basestring): fn = OpenMaya.MFnTypedAttribute() fn.create(_name, _name, OpenMaya. MFnData.kString) return fn kType = type(_val) if issubclass(kType, bool): fn = OpenMaya.MFnNumericAttribute() fn.create(_name, _name, OpenMaya.MFnNumericData.kBoolean) return fn if issubclass(kType, int): fn = OpenMaya.MFnNumericAttribute() fn.create(_name, _name, OpenMaya.MFnNumericData.kInt) return fn if issubclass(kType, float): fn = OpenMaya.MFnNumericAttribute() fn.create(_name, _name, OpenMaya.MFnNumericData.kFloat) return fn if isinstance(_val, dict): fn = OpenMaya.MFnMessageAttribute() fn.create(_name, _name) return fn if isinstance(_val, list) or isinstance(type, tuple): if len(_val) < 1: pymel.warning("Can't create attribute {0}, empty array are unsuported".format(_name)) return None # TODO: Throw error when the array have multiple types fn = _createAttribute(_name, _val[0]) fn.setArray(True) return fn if isinstance(_val, pymel.datatypes.Matrix): fn = OpenMaya.MFnMatrixAttribute() fn.create(_name, _name) return fn if issubclass(kType, pymel.Attribute): if not libPymel.is_valid_PyNode(_val): log.warning("Can't serialize {0} attribute because of non-existent pymel Attribute!".format(_name)) return None elif _val.type() == 'doubleAngle': fn = OpenMaya.MFnUnitAttribute() fn.create(_name, _name, OpenMaya.MFnUnitAttribute.kAngle) return fn elif _val.type() == 'time': fn = OpenMaya.MFnUnitAttribute() fn.create(_name, _name, OpenMaya.MFnUnitAttribute.kTime) return fn #elif _val.type() == '???': # fn = OpenMaya.MFnUnitAttribute() # fn.create(_name, _name, OpenMaya.MFnUnitAttribute.kDistance) # return fn # If the attribute doesn't represent anything special, we'll check it's value to know what attribute type to create. else: return _createAttribute(_name, _val.get()) if hasattr(_val, '__melobject__'): # TODO: Really usefull? fn = OpenMaya.MFnMessageAttribute() fn.create(_name, _name) return fn if hasattr(_val, '__dict__'): fn = OpenMaya.MFnMessageAttribute() fn.create(_name, _name) return fn pymel.error("Can't create MFnAttribute for {0} {1} {2}".format(_name, _val, kType))
def unbuild(self): # hack: the ikEffector is parented to the bone chain and need to be deleted manually if libPymel.is_valid_PyNode(self.ikEffector): pymel.delete(self.ikEffector) super(SplineIK, self).unbuild()
def can_fetch_shapes(self): return libPymel.is_valid_PyNode(self.shapes) and self.shapes.getShape()
def get_spaceswitch_targets(self, module, jnt, add_world=True, add_root=True, add_local=True, root_name='Root', world_name='World', **kwargs): """ Return the list of target used by the space switch of a controller. It will try get all module pin location it can find from it's jnt parameter :param module: The module on which we want to process space switch targets :param jnt: A list of joint that will be used to find associated modules to find space objects :param add_world: Is the world will be added as a space switch target of the ctrl :param add_root: Is the root will be added as a space switch target of the ctrl :param add_local: Is the local option will be used. Local will be the same than the first module target :param root_name: The name in the list of targets the root will take :param world_name: The name in the list of targets the world will take :param kwargs: Additional parameters :return: The targets obj, name and index of the found space switch target """ targets = [] targets.extend(self.targets) # The target # Initialize the target name list with the same number of item than the targets keeped before target_names = [] for i in range(0, len(targets)): target_names.append(None) indexes = [] indexes.extend(self.targets_indexes) # Use the grp_rip node as the world target. It will always be the first target in the list if add_world and libPymel.is_valid_PyNode(module.rig.grp_rig): if module.rig.grp_rig not in targets: targets.append(module.rig.grp_rig) # World will always be -1 indexes.append(self.get_bestmatch_index(module.rig.grp_rig, constants.SpaceSwitchReservedIndex.world)) target_names.append(world_name) else: idx = targets.index(module.rig.grp_rig) target_names[idx] = world_name # Add the master ctrl as a spaceswitch target if libPymel.is_valid_PyNode(module.rig.grp_anm): if module.rig.grp_anm not in targets: targets.append(module.rig.grp_anm) target_names.append(root_name) # The root will always be index 1, because we want to let local to be 0 indexes.append(self.get_bestmatch_index(module.rig.grp_anm, constants.SpaceSwitchReservedIndex.root)) else: idx = targets.index(module.rig.grp_anm) target_names[idx] = root_name # Resolve modules targets first_module = True while jnt: m = module.rig.get_module_by_input(jnt) # We will not add as a target the first modules target found if we add the local space # The local space is an equivalent to not having any space activated so as if it follow it's parent which # would be the first module found if m and ((add_local and not first_module) or not add_local): target, target_name = m.get_pin_locations(jnt) if target: if target not in targets: targets.append(target) target_names.append(target_name) indexes.append(self.get_bestmatch_index(target)) else: idx = targets.index(target) target_names[idx] = target_name else: first_module = False jnt = jnt.getParent() # Final check to ensure that not target is None. If one None target is found, we need to remove it and let the # index in the space attribute to be free to fix manually for i, t in reversed(list(enumerate(targets))): if t is None: log.warning("Space switch index {0} target is None on {1}, " "maybe a manual connection will be needed".format(indexes[i], self.name)) targets.pop(i) target_names.pop(i) indexes.pop(i) return targets, target_names, indexes
def build(self, module, ref, ref_tm=None, grp_rig=None, obj_mesh=None, u_coord=None, v_coord=None, flip_lr=False, follow_mesh=True, **kwargs): """ Create an Interactive controller that follow a geometry. :param module: ??? :param ref: :param ref_tm: :param grp_rig: :param obj_mesh: :param u_coord: :param v_coord: :param kwargs: :return: """ # HACK: Ensure flipped shapes are correctly restaured... # This is necessary since when holded, the scale of the ctrl is set to identity. # However ctrl from the right side have an inverted scale on the x axis. -_- if flip_lr and libPymel.is_valid_PyNode(self.shapes): self.shapes.sx.set(-1) pymel.makeIdentity(self.shapes, rotate=True, scale=True, apply=True) # todo: Simplify the setup, too many nodes super(InteractiveCtrl, self).build(**kwargs) #nomenclature_anm = self.get_nomenclature_anm(parent) nomenclature_rig = module.rig.nomenclature(suffix=module.rig.nomenclature.type_rig) #nomenclature_rig = self.get_nomenclature_rig(parent) # TODO: Only use position instead of PyNode or Matrix? if ref_tm is None: ref_tm = ref.getMatrix(worldSpace=True) pos_ref = ref_tm.translate # Resolve u and v coordinates # todo: check if we really want to resolve the u and v ourself since it's now connected. if obj_mesh is None: # We'll scan all available geometries and use the one with the shortest distance. meshes = libRigging.get_affected_geometries(ref) meshes = list(set(meshes) & set(module.rig.get_meshes())) obj_mesh, _, out_u, out_v = libRigging.get_closest_point_on_shapes(meshes, pos_ref) else: _, out_u, out_v = libRigging.get_closest_point_on_shape(obj_mesh, pos_ref) if u_coord is None: u_coord = out_u if v_coord is None: v_coord = out_v if obj_mesh is None: raise Exception("Can't find mesh affected by {0}. Skipping doritos ctrl setup.") if self.jnt: module.debug('Creating doritos on {0} using {1} as reference'.format(obj_mesh, self.jnt)) else: module.debug('Creating doritos on {0}'.format(obj_mesh)) # Initialize external stack # Normally this would be hidden from animators. stack_name = nomenclature_rig.resolve('doritosStack') stack = classNode.Node(self) stack.build(name=stack_name) stack.setTranslation(pos_ref) # Add sensibility attributes # The values will be computed when attach_ctrl will be called libAttr.addAttr_separator( module.grp_rig, "ctrlCalibration" ) self.attr_sensitivity_tx = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TX, defaultValue=1.0 ) self.attr_sensitivity_ty = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TY, defaultValue=1.0 ) self.attr_sensitivity_tz = libAttr.addAttr( module.grp_rig, longName=self._ATTR_NAME_SENSITIVITY_TZ, defaultValue=1.0 ) self.attr_sensitivity_tx.set(channelBox=True) self.attr_sensitivity_ty.set(channelBox=True) self.attr_sensitivity_tz.set(channelBox=True) # Note that to only check in the Z axis, we'll do a raycast first. # If we success this will become our reference position. ''' pos = pos_ref pos.z = 999 dir = pymel.datatypes.Point(0,0,-1) result = next(iter(libRigging.ray_cast(pos, dir, [obj_mesh])), None) if result: pos_ref = result ctrl_tm.translate = result ''' # Create the layer_fol that will follow the geometry layer_fol_name = nomenclature_rig.resolve('doritosFol') layer_fol = stack.append_layer() layer_fol.rename(layer_fol_name) #layer_fol.setParent(self.grp_rig) # TODO: Validate that we don't need to inverse the rotation separately. fol_mesh = None if follow_mesh: fol_name = nomenclature_rig.resolve('doritosFollicle') fol_shape = libRigging.create_follicle2(obj_mesh, u=u_coord, v=v_coord) fol_mesh = fol_shape.getParent() self.follicle = fol_mesh fol_mesh.rename(fol_name) pymel.parentConstraint(fol_mesh, layer_fol, maintainOffset=True) fol_mesh.setParent(self.grp_rig) # HACK: Fix rotation issues. # The doritos setup can be hard to control when the rotation of the controller depend on the layer_fol since # any deformation can affect the normal of the faces. jnt_head = module.rig.get_head_jnt() if jnt_head: pymel.disconnectAttr(layer_fol.rx) pymel.disconnectAttr(layer_fol.ry) pymel.disconnectAttr(layer_fol.rz) pymel.orientConstraint(jnt_head, layer_fol, maintainOffset=True) else: self.follicle = layer_fol pymel.parentConstraint(ref, layer_fol, maintainOffset=True) # # Constraint a specic controller to the avar doritos stack. # Call this method after connecting the ctrl to the necessary avars. # The sensibility of the doritos will be automatically computed in this step if necessary. # # Create inverted attributes for sensibility util_sensitivity_inv = libRigging.create_utility_node('multiplyDivide', operation=2, input1X=1.0, input1Y=1.0, input1Z=1.0, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz ) attr_sensibility_lr_inv = util_sensitivity_inv.outputX attr_sensibility_ud_inv = util_sensitivity_inv.outputY attr_sensibility_fb_inv = util_sensitivity_inv.outputZ # Add an inverse node that will counter animate the position of the ctrl. # TODO: Rename layer_doritos_name = nomenclature_rig.resolve('doritosInv') layer_doritos = pymel.createNode('transform', name=layer_doritos_name) layer_doritos.setParent(stack.node) # Create inverse attributes for the ctrl attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=self.node.t, input2=[-1, -1, -1]).output attr_ctrl_inv_r = libRigging.create_utility_node('multiplyDivide', input1=self.node.r, input2=[-1, -1, -1]).output attr_ctrl_inv_t = libRigging.create_utility_node('multiplyDivide', input1=attr_ctrl_inv_t, input2X=self.attr_sensitivity_tx, input2Y=self.attr_sensitivity_ty, input2Z=self.attr_sensitivity_tz ).output if flip_lr: attr_doritos_tx = libRigging.create_utility_node('multiplyDivide', input1X=attr_ctrl_inv_t.outputX, input2X=-1 ).outputX else: attr_doritos_tx = attr_ctrl_inv_t.outputX attr_doritos_ty = attr_ctrl_inv_t.outputY attr_doritos_tz = attr_ctrl_inv_t.outputZ pymel.connectAttr(attr_doritos_tx, layer_doritos.tx) pymel.connectAttr(attr_doritos_ty, layer_doritos.ty) pymel.connectAttr(attr_doritos_tz, layer_doritos.tz) pymel.connectAttr(attr_ctrl_inv_r, layer_doritos.r) # Apply scaling on the ctrl parent. # This is were the 'black magic' happen. if flip_lr: attr_ctrl_offset_sx_inn = libRigging.create_utility_node('multiplyDivide', input1X=self.attr_sensitivity_tx, input2X=-1 ).outputX else: attr_ctrl_offset_sx_inn = self.attr_sensitivity_tx attr_ctrl_offset_sy_inn = self.attr_sensitivity_ty attr_ctrl_offset_sz_inn = self.attr_sensitivity_tz pymel.connectAttr(attr_ctrl_offset_sx_inn, self.offset.scaleX) pymel.connectAttr(attr_ctrl_offset_sy_inn, self.offset.scaleY) pymel.connectAttr(attr_ctrl_offset_sz_inn, self.offset.scaleZ) # Apply sensibility on the ctrl shape ctrl_shape = self.node.getShape() tmp = pymel.duplicate(self.node.getShape())[0] ctrl_shape_orig = tmp.getShape() ctrl_shape_orig.setParent(self.node, relative=True, shape=True) ctrl_shape_orig.rename('{0}Orig'.format(ctrl_shape.name())) pymel.delete(tmp) ctrl_shape_orig.intermediateObject.set(True) for cp in ctrl_shape.cp: cp.set(0,0,0) # Counter-scale the shape attr_adjustement_sx_inn = attr_sensibility_lr_inv attr_adjustement_sy_inn = attr_sensibility_ud_inv attr_adjustement_sz_inn = attr_sensibility_fb_inv attr_adjustement_scale = libRigging.create_utility_node('composeMatrix', inputScaleX=attr_adjustement_sx_inn, inputScaleY=attr_adjustement_sy_inn, inputScaleZ=attr_adjustement_sz_inn ).outputMatrix attr_adjustement_rot = libRigging.create_utility_node('composeMatrix', inputRotateX=self.node.rotateX, inputRotateY=self.node.rotateY, inputRotateZ=self.node.rotateZ ).outputMatrix attr_adjustement_rot_inv = libRigging.create_utility_node('inverseMatrix', inputMatrix=attr_adjustement_rot).outputMatrix attr_adjustement_tm = libRigging.create_utility_node('multMatrix', matrixIn=[ attr_adjustement_rot, attr_adjustement_scale, attr_adjustement_rot_inv ]).matrixSum attr_transform_geometry = libRigging.create_utility_node('transformGeometry', transform=attr_adjustement_tm, inputGeometry=ctrl_shape_orig.local).outputGeometry pymel.connectAttr(attr_transform_geometry, ctrl_shape.create, force=True) # Constraint ctrl pymel.parentConstraint(layer_doritos, self.offset, maintainOffset=False, skipRotate=['x', 'y', 'z']) pymel.orientConstraint(layer_doritos.getParent(), self.offset, maintainOffset=True) # Clean dag junk if grp_rig: stack.setParent(grp_rig) if fol_mesh: fol_mesh.setParent(grp_rig)
def pre_build(self, create_master_grp=False, create_grp_jnt=True, create_grp_anm=True, create_grp_rig=True, create_grp_geo=True, create_display_layers=True, create_grp_backup=False): # Hack: Invalidate any cache before building anything. # This ensure we always have fresh data. try: del self._cache except AttributeError: pass # Look for a root joint if create_grp_jnt: # For now, we will determine the root jnt by it's name used in each rig. Not the best solution, # but currently the safer since we want to support multiple deformation layer if not libPymel.is_valid_PyNode(self.grp_jnt): # self.grp_jnt = next(iter(libPymel.ls_root(type='joint')), None) if cmds.objExists(self.nomenclature.root_jnt_name): self.grp_jnt = pymel.PyNode(self.nomenclature.root_jnt_name) else: self.warning("Could not find any root joint, master ctrl will not drive anything") # self.grp_jnt = pymel.createNode('joint', name=self.nomenclature.root_jnt_name) # Ensure all joints have segmentScaleComprensate deactivated. # This allow us to scale adequately and support video game rigs. # If for any mean stretch and squash are necessary, implement # them on a new joint chains parented to the skeletton. # TODO: Move elsewere? all_jnts = libPymel.ls(type='joint') for jnt in all_jnts: jnt.segmentScaleCompensate.set(False) # Create the master grp if create_master_grp: self.grp_master = self.build_grp(RigGrp, self.grp_master, self.name + '_' + self.nomenclature.type_rig) # Create grp_anm if create_grp_anm: grp_anim_size = CtrlRoot._get_recommended_radius(self) self.grp_anm = self.build_grp(CtrlRoot, self.grp_anm, self.nomenclature.root_anm_name, size=grp_anim_size) # Create grp_rig if create_grp_rig: self.grp_rig = self.build_grp(RigGrp, self.grp_rig, self.nomenclature.root_rig_name) # Create grp_geo if create_grp_geo: all_geos = libPymel.ls_root_geos() self.grp_geo = self.build_grp(RigGrp, self.grp_geo, self.nomenclature.root_geo_name) #if all_geos: # all_geos.setParent(self.grp_geo) if create_grp_backup: self.grp_backup = self.build_grp(RigGrp, self.grp_backup, self.nomenclature.root_backup_name) #Parent all grp on the master grp if self.grp_master: if self.grp_jnt: self.grp_jnt.setParent(self.grp_master.node) if self.grp_anm: self.grp_anm.setParent(self.grp_master.node) if self.grp_rig: self.grp_rig.setParent(self.grp_master.node) if self.grp_geo: self.grp_geo.setParent(self.grp_master.node) if self.grp_backup: self.grp_backup.setParent(self.grp_master.node) # Setup displayLayers if create_display_layers: if not pymel.objExists(self.nomenclature.layer_anm_name): self.layer_anm = pymel.createDisplayLayer(name=self.nomenclature.layer_anm_name, number=1, empty=True) self.layer_anm.color.set(17) # Yellow else: self.layer_anm = pymel.PyNode(self.nomenclature.layer_anm_name) pymel.editDisplayLayerMembers(self.layer_anm, self.grp_anm, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_rig_name): self.layer_rig = pymel.createDisplayLayer(name=self.nomenclature.layer_rig_name, number=1, empty=True) self.layer_rig.color.set(13) # Red # self.layer_rig.visibility.set(0) # Hidden self.layer_rig.displayType.set(2) # Frozen else: self.layer_rig = pymel.PyNode(self.nomenclature.layer_rig_name) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_rig, noRecurse=True) pymel.editDisplayLayerMembers(self.layer_rig, self.grp_jnt, noRecurse=True) if not pymel.objExists(self.nomenclature.layer_geo_name): self.layer_geo = pymel.createDisplayLayer(name=self.nomenclature.layer_geo_name, number=1, empty=True) self.layer_geo.color.set(12) # Green? self.layer_geo.displayType.set(2) # Frozen else: self.layer_geo = pymel.PyNode(self.nomenclature.layer_geo_name) pymel.editDisplayLayerMembers(self.layer_geo, self.grp_geo, noRecurse=True)
def unbuild(self, **kwargs): # hack: the ikEffector is parented to the bone chain and need to be deleted manually if libPymel.is_valid_PyNode(self.ikEffector): pymel.delete(self.ikEffector) super(SplineIK, self).unbuild(**kwargs)