def set_parent(self, parent, **kwargs): """ Overrides set parent to parent proper root/auto nodes if necessary :param parent: str, parent transform node name :return: """ # Whether to parent ctrl transform or take into account also root/auto groups parent_top = kwargs.pop('parent_top', True) node_to_parent = self.get() root_group = self.get_root() auto_group = self.get_auto() if parent_top: mirror_group = self.get_mirror( suffix=kwargs.pop('mirror_suffix', None)) if mirror_group: node_to_parent = mirror_group elif root_group and dcc.node_exists(root_group): node_to_parent = root_group else: if auto_group and dcc.node_exists(auto_group): node_to_parent = auto_group dcc.set_parent(node_to_parent, parent)
def _reference_alembic(self, alembic_file, namespace, parent): """ Internal function that references given alembic file :param alembic_file: str :param namespace: str :return: """ all_nodes = alembic.reference_alembic(project=self._project, alembic_file=alembic_file, namespace=namespace) if not all_nodes: logger.warning( 'Error while reference Alembic file: {}'.format(alembic_file)) return for obj in all_nodes: if not dcc.node_exists(obj): continue if not dcc.client().node_type(obj) == 'transform': continue obj_parent = dcc.client().node_parent(obj) if obj_parent: continue dcc.client().set_parent(node=obj, parent=parent) return all_nodes
def _create_nurbs_plane(self): """ Internal function that creates the NURBS plane that will drive the rig system """ self._nurbs_plane = dcc.create_nurbs_plane( name=self._get_name('flexiPlane', node_type='surface'), axis=(0, 1, 0), width=self._num_joints * 2, length=self._length, patches_u=self._num_joints, construction_history=False) dcc.freeze_transforms(self._nurbs_plane, translate=True, rotate=True, scale=True) dcc.hide_keyable_attributes(self._nurbs_plane, skip_visibility=True) material_name = self._get_name('flexiMat', node_type='material') if not dcc.node_exists(material_name): self._nurbs_plane_material = dcc.create_lambert_material( name=material_name, no_surface_shader=True, color=(0.0, 0.85, 1.0), transparency=(0.8, 0.8, 0.8)) else: self._nurbs_plane_material = material_name dcc.apply_shader(self._nurbs_plane_material, self._nurbs_plane) if self._display_nurbs_plane_as_template: dcc.set_node_template_display(self._nurbs_plane, True) nurbs_plane_shape = dcc.list_shapes(self._nurbs_plane)[0] dcc.set_node_renderable(nurbs_plane_shape, False) dcc.set_attribute_value(nurbs_plane_shape, 'doubleSided', True) dcc.set_parent(self._nurbs_plane, self._global_move_group)
def is_deleted(self): """ Returns whether or not the current wrapped DCC node is being deleted from current scene :return: bool """ return not dcc.node_exists(self._dcc_native_object)
def get_selected_info(self, data, reply): """ Function that returns selected geometry info (its name and its vertices) :return: tuple(str, list(list(float, float, float)) """ nodes = dcc.selected_nodes(flatten=True) selected_geo = dcc.filter_nodes_by_selected_components(filter_type=12, nodes=nodes, full_path=True) selected_vertices = None if selected_geo: selected_geo = selected_geo[0] if not selected_geo: hilited_geo = dcc.selected_hilited_nodes(full_path=True) if len(hilited_geo) == 1: selected_geo = hilited_geo[0] selected_vertices = dcc.filter_nodes_by_selected_components( filter_type=31, nodes=nodes, full_path=False) elif len(hilited_geo) > 1: logger.warning( 'Only one object can be hilited in component mode!') if selected_geo: if dcc.node_is_a_shape(selected_geo): selected_geo = dcc.node_parent(selected_geo, full_path=True) if not selected_geo or not dcc.node_exists(selected_geo): reply['success'] = False reply['result'] = '', list() return reply['success'] = True reply['result'] = selected_geo, selected_vertices
def _get_curve_transform(self, curve): parent = curve if dcc.node_exists(curve): if dcc.node_type(curve) == 'nurbsCurve': parent = dcc.node_parent(curve) else: parent = curve return parent
def _create_connector_controls(self): self._global_control = control.RigControl(name=self._get_name('global', node_type='control')) global_circle1 = maya.cmds.circle(normal=(0, 1, 0), radius=0.3)[0] dcc.move_node(global_circle1, 0, 0, -self._length) global_circle2 = maya.cmds.circle(normal=(0, 1, 0), radius=0.3)[0] dcc.move_node(global_circle2, 0, 0, self._length) dcc.freeze_transforms(global_circle1) dcc.freeze_transforms(global_circle2) self._global_control.set_shape([global_circle1, global_circle2]) self._global_control.set_color_rgb(255, 255, 0) dcc.delete_node([global_circle1, global_circle2]) for shape in dcc.list_shapes(self._global_control.get()): dcc.rename_node(shape, '{}Shape'.format(self._global_control.get())) self._global_control.create_root_group() dcc.set_parent(self._global_control.get_top(), self._top_group) dcc.set_parent(self._global_move_group, self._global_control.get()) top_end_distance = self._num_joints self._start_control = control.RigControl(name=self._get_name('startConnector', node_type='control')) self._start_control.set_curve_type( 'square', color=(255, 255, 0), axis_order='YXZ', control_size=self._control_size) dcc.move_node(self._start_control.get_top(), -top_end_distance, 0, 0) self._end_control = control.RigControl(name=self._get_name('endConnector', node_type='control')) self._end_control.set_curve_type( 'square', color=(255, 255, 0), axis_order='YXZ', control_size=self._control_size) dcc.move_node(self._end_control.get_top(), top_end_distance, 0, 0) sphere_control = maya.cmds.polySphere(subdivisionsX=12, subdivisionsY=12, radius=0.3) sphere_shape = dcc.list_shapes(sphere_control)[0] sphere_material = self._get_name('flexiBendControlMat', node_type='material') if not dcc.node_exists(sphere_material): self._mid_control_material = dcc.create_surface_shader(shader_name=sphere_material) dcc.set_attribute_value(self._mid_control_material, 'outColor', (1, 1, 0)) else: self._mid_control_material = sphere_material dcc.apply_shader(self._mid_control_material, sphere_shape) dcc.set_node_renderable(sphere_shape, False) self._mid_control = control.RigControl(name=self._get_name('midBend', node_type='control')) self._mid_control.set_shape(sphere_control) maya.cmds.delete(sphere_control) mid_control_shape = dcc.list_shapes(self._mid_control.get())[0] dcc.rename_node(mid_control_shape, '{}Shape'.format(self._mid_control.get())) for connector_control in [self._start_control, self._mid_control, self._end_control]: connector_control.create_root_group() connector_control.create_auto_group() dcc.set_parent(connector_control.get_top(), self._controls_group) # Mid control will be positioned in the mid position between start and end controls dcc.create_point_constraint( self._mid_control.get_buffer_group('auto'), (self._start_control.get(), self._end_control.get()), maintain_offset=False)
def validate_curve(crv): """ Returns whether the given name corresponds to a valid NURBS curve :param crv: str, name of the curve we want to check :return: bool """ if crv is None or not dcc.node_exists(crv): return False curve_shapes = dcc.get_curve_shapes(crv) return curve_shapes
def is_character_definition(node): """ Returns whether or not given node is an HumanIK character node :param node: str :return: bool """ if not dcc.node_exists(node): return False if dcc.object_type(node) != 'HIKCharacterNode': return False return True
def __init__(self, nodes): error_text = '======= Renamer: Failed to rename one or more nodes =======' if not hasattr(nodes, '__iter__'): nodes = [nodes] for node in nodes: if not dcc.node_exists(node): error_text += "\t'%s' no longer exists.\n" % node elif dcc.node_is_locked(node): error_text += "\t'%s' is locked.\n" % node else: error_text += "\t'%s' failure unknows.\n" % node Exception.__init__(self, error_text)
def __init__(self, name, **kwargs): super(BaseRigControl, self).__init__() self._name = name or 'newCtrl' self._curve_type = kwargs.pop('control_type', '') self._controls_path = kwargs.pop('controls_path', '') self._naming_file = kwargs.pop('naming_file', '') self._name_rule = kwargs.pop('rule_name', '') self._name_data = kwargs.pop('name_data', dict()) self._side = kwargs.pop('side', None) if self._naming_file: self._name = self.solve_name(self._name, unique_name=False) if not dcc.node_exists(self._name): self._create(**kwargs)
def create_character(character_name, character_namespace=None, lock=True): """ Creates a HumanIK character and tries to use the given name to name the new character :param character_name: str, name of the new HumanIK character """ if character_namespace and not character_namespace.endswith(':'): character_namespace = '{}:'.format(character_namespace) character_namespace = character_namespace or '' character_definition = maya.mel.eval('hikCreateCharacter("{0}")'.format(character_name)) set_current_character(character_definition) try: maya.mel.eval('hikUpdateCharacterList()') maya.mel.eval('hikSelectDefinitionTab()') except Exception: pass for bone_name, bone_data in HIK_BONES.items(): bone_full_name = '{}{}'.format(character_namespace, bone_name) if not dcc.node_exists(bone_full_name): logger.debug('HIK bone "{}" not found in scene!'.format(bone_name)) continue bone_index = bone_data['index'] set_character_object(character_definition, bone_full_name, bone_index, 0) property_node = get_properties_node(character_definition) dcc.set_attribute_value(property_node, 'ForceActorSpace', 0) dcc.set_attribute_value(property_node, 'ScaleCompensationMode', 1) dcc.set_attribute_value(property_node, 'Mirror', 0) dcc.set_attribute_value(property_node, 'HipsHeightCompensationMode', 1) dcc.set_attribute_value(property_node, 'AnkleProximityCompensationMode', 1) dcc.set_attribute_value(property_node, 'AnkleHeightCompensationMode', 0) dcc.set_attribute_value(property_node, 'MassCenterCompensationMode', 1) if lock: maya.mel.eval('hikToggleLockDefinition') # else: # generator_node = maya.cmds.createNode('HIKSkeletonGeneratorNode') # dcc.connect_attribute( # generator_node, 'CharacterNode', character_definition, 'SkeletonGenerator', force=True) return character_definition
def import_data(self, **kwargs): filepath = self.format_identifier() if not filepath.endswith(ControlCVsData.EXTENSION): filepath = '{}{}'.format(filepath, ControlCVsData.EXTENSION) if not filepath: logger.warning( 'Impossible to load Control CVs from file: "{}"!'.format( filepath)) return False library = self._initialize_library(filepath) objects = kwargs.get('objects', None) if not objects: objects = dcc.client().selected_nodes(full_path=True) or list() # We make sure that we store the short name of the controls objects = [dcc.node_short_name(obj) for obj in objects] updated_controls = list() controls = objects or controllib.get_controls() for control in controls: shapes = dcc.client().get_curve_shapes(control) if not shapes: continue library.set_curve(control, check_curve=True) updated_controls.append(control) # We force the update of all the curves that are stored in the library and are in the scene for curve_data in list(library._library_curves.values()): for curve_name in list(curve_data.keys()): if curve_name and dcc.node_exists( curve_name) and curve_name not in updated_controls: library.set_curve(curve_name, check_curve=True) updated_controls.append(curve_name) logger.info('Imported {} data'.format(self.name())) return True
def _calculate_mirror_axis(self, source_object): """ Internal function that calculates the mirror axis of the given object name :param source_object: str :return: list(int) """ result = [1, 1, 1] target_object = self.mirror_object(source_object) or source_object mirror_plane = self.mirror_plane() if target_object == source_object or not dcc.node_exists(target_object): result = dcc.get_mirror_axis(source_object, mirror_plane) else: if dcc.is_axis_mirrored(source_object, target_object, [1, 0, 0], mirror_plane): result[0] = -1 if dcc.is_axis_mirrored(source_object, target_object, [0, 1, 0], mirror_plane): result[1] = -1 if dcc.is_axis_mirrored(source_object, target_object, [0, 0, 1], mirror_plane): result[2] = -1 return result
def _on_import_alembic(self, as_reference=False): """ Overrides base AlembicImporter _on_import_alembic function Internal callback function that is called when Import/Reference Alembic button is clicked :param as_reference: bool """ reference_nodes = super( MayaAlembicImporter, self)._on_import_alembic(as_reference=as_reference) if not self._auto_smooth_display.isChecked(): return if not reference_nodes or type(reference_nodes) not in [list, tuple]: return for obj in reference_nodes: if not obj or not dcc.node_exists(obj): continue if dcc.client().node_type(obj) == 'shape': if dcc.client().attribute_exists( node=obj, attribute_name='aiSubdivType'): dcc.client().set_integer_attribute_value( node=obj, attribute_name='aiSubdivType', attribute_value=1) elif dcc.client().node_type(obj) == 'transform': shapes = dcc.client().list_shapes(node=obj, full_path=True) if not shapes: continue for s in shapes: if dcc.client().attribute_exists( node=s, attribute_name='aiSubdivType'): dcc.client().set_integer_attribute_value( node=s, attribute_name='aiSubdivType', attribute_value=1)
def transfer_static(self, source_object, target_object, mirror_axis=None, attrs=None, option=None): option = option or MirrorOptions.Swap source_value = None target_value = None source_valid = self.is_valid_mirror(source_object, option) target_valid = self.is_valid_mirror(target_object, option) attrs = attrs or dcc.list_attributes(source_object, keyable=True) or list() for attr in attrs: target_attr = '{}.{}'.format(target_object, attr) if dcc.node_exists(target_attr): if target_valid: source_value = dcc.get_attribute_value(source_object, attr) if source_valid: target_value = dcc.get_attribute_value(target_object, attr) if target_valid: self.set_attribute(target_object, attr, source_value, mirror_axis=mirror_axis) if source_valid: self.set_attribute(source_object, attr, target_value, mirror_axis=mirror_axis) else: logger.warning('Cannot find destination attribute "{}"'.format(target_attr))
def exists(self): return dcc.node_exists(self.name())
def cache_node(self, source_node, target_node, **kwargs): """ :param source_node: Node :param target_node: Node Caches the given pair of nodes :param kwargs: dict """ attrs = kwargs.get('attrs', None) ignore_connected = kwargs.get('ignore_connected', None) only_connected = kwargs.get('only_connected', None) using_namespaces = kwargs.get('using_namespaces', None) mirror_axis = None mirror_object = None # remove first pipe in case object has a parent node target_node.strip_first_pipe() source_name = source_node.name() if self.mirror_table(): mirror_object = self.mirror_table().mirror_object(source_name) if not mirror_object or not dcc.node_exists(mirror_object): mirror_object = self.mirror_table().mirror_object( dcc.client().node_short_name(source_name)) if not mirror_object: mirror_object = source_name logger.warning( 'Cannot find mirror object in pose for "{}"'.format( source_name)) # retrieve mirror axis from mirror object or from source node mirror_axis = self.mirror_axis(mirror_object) or self.mirror_axis( source_name) if mirror_object and not dcc.node_exists(mirror_object): logger.warning( 'Mirror object does not exist in the scene {}'.format( mirror_object)) if using_namespaces: try: target_node = target_node.to_short_name() except exceptions.NoObjectFoundError as exc: logger.warning(exc) return except exceptions.MoreThanOneObjectFoundError as exc: logger.warning(exc) return for attr in self.attrs(source_name): if attrs and attr not in attrs: continue target_attribute = utils.Attribute(target_node.name(), attr) is_connected = target_attribute.is_connected() if (ignore_connected and is_connected) or (only_connected and not is_connected): continue attr_type = self.attr_type(source_name, attr) attr_value = self.attr_value(source_name, attr) source_mirror_value = self.mirror_value(mirror_object, attr, mirror_axis=mirror_axis) source_attribute = utils.Attribute(target_node.name(), attr, value=attr_value, type=attr_type) target_attribute.clear_cache() self._cache.append( (source_attribute, target_attribute, source_mirror_value))
def import_data(self, *args, **kwargs): filepath = self.format_identifier() if not filepath.endswith(JointsData.EXTENSION): filepath = '{}{}'.format(filepath, JointsData.EXTENSION) if not filepath or not os.path.isfile(filepath): LOGGER.warning('Impossible to import joints data from: "{}"'.format(filepath)) return LOGGER.debug('Loading: {} | {}'.format(filepath, kwargs)) with open(filepath, 'r') as fh: joints_data = json.load(fh) if not joints_data: LOGGER.warning('No joints data found in file: "{}"'.format(filepath)) return False # TODO: Use metadata to verify DCC and also to create nodes with proper up axis metadata = self.metadata() nodes_list = list() created_nodes = dict() for node_data in joints_data: node_index = node_data.get('index', 0) node_parent_index = node_data.get('parent_index', -1) node_parent_name = node_data.get('parent_name', '') node_name = node_data.get('name', 'new_node') node_type = node_data.get('type', 'joint') node_namespace = node_data.get('namespace', '') node_label_side = node_data.get('side', '') node_label_type = node_data.get('bone_type', '') node_label_other_type = node_data.get('bone_other_type', '') node_label_draw = node_data.get('draw_label', False) node_radius = node_data.get('radius', 1.0) node_world_matrix = node_data.get( 'world_matrix', [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0]) dcc.client().clear_selection() if node_type == 'joint': new_node = dcc.client().create_joint(name=node_name) else: new_node = dcc.client().create_empty_group(name=node_name) dcc.client().set_node_world_matrix(new_node, node_world_matrix) created_nodes[node_index] = { 'node': new_node, 'parent_index': node_parent_index, 'parent_name': node_parent_name, 'namespace': node_namespace, 'label_side': node_label_side, 'label_type': node_label_type, 'label_other_type': node_label_other_type, 'label_draw': node_label_draw, 'radius': node_radius } if node_type == 'joint': dcc.client().zero_orient_joint(new_node) nodes_list.append(new_node) for node_index, node_data in created_nodes.items(): parent_index = node_data['parent_index'] if parent_index < -1: continue node_data = created_nodes.get(node_index, None) if not node_data: continue node_name = node_data.get('node') dcc.client().set_side_labelling(node_name, node_data.get('label_side')) dcc.client().set_type_labelling(node_name, node_data.get('label_type')) dcc.client().set_other_type_labelling(node_name, node_data.get('label_other_type')) dcc.client().set_draw_label_labelling(node_name, node_data.get('label_draw')) dcc.client().set_joint_radius(node_name, node_data.get('radius')) parent_node_name = None parent_node_data = created_nodes.get(parent_index, None) if not parent_node_data: parent_node_name = node_data.get('parent_name', '') if not parent_node_name or not dcc.node_exists(parent_node_name): continue if not parent_node_name: parent_node_name = parent_node_data.get('node') dcc.client().set_parent(node_name, parent_node_name) # We assign namespaces once the hierarchy of nodes is created for node_index, node_data in created_nodes.items(): node_name = node_data.get('node') node_namespace = node_data.get('namespace') if node_namespace: dcc.client().assign_node_namespace(node_name, node_namespace, force_create=True) # dcc.client().select_node(nodes_list) # dcc.client().fit_view() dcc.client().clear_selection() LOGGER.debug('Loaded: {} | {}'.format(filepath, kwargs)) return nodes_list
def _import_skin_weights(self, data_path, mesh): if not dcc.node_exists(mesh) or not os.path.isdir(data_path): return False try: if not dcc.is_plugin_loaded('ngSkinTools2'): dcc.load_plugin('ngSkinTools2') import ngSkinTools2 from ngSkinTools2 import api as ngst_api except ImportError: logger.warning( 'NgSkinTools 2.0 is not installed. Impossible to import ngSkin data' ) return False ng_skin_data_path = path_utils.join_path(data_path, 'ngdata.json') if not path_utils.is_file(ng_skin_data_path): logger.warning( 'No Ng Skin Data file found: "{}", aborting import skin weights operation ...' .format(ng_skin_data_path)) return False is_valid_mesh = False shape_types = ['mesh', 'nurbsSurface', 'nurbsCurve', 'lattice'] for shape_type in shape_types: if shape_utils.has_shape_of_type(mesh, shape_type): is_valid_mesh = True break if not is_valid_mesh: logger.warning( 'Node "{}" is not a valid mesh node! Currently supported nodes include: {}' .format(mesh, shape_types)) return False logger.info('Importing skin clusters {} --> "{}"'.format( mesh, data_path)) influence_dict = self._get_influences(data_path) if not influence_dict: logger.warning('No influences data found for: {}'.format(mesh)) return False influences = influence_dict.keys() if not influences: logger.warning('No influences found for: "{}"'.format(mesh)) return False influences.sort() logger.debug('Influences found for {}: {}'.format(mesh, influences)) short_name = dcc.node_short_name(mesh) transfer_mesh = None if shape_utils.has_shape_of_type(mesh, 'mesh'): orig_mesh = self._import_mesh_obj(data_path) if orig_mesh: mesh_match = geo_utils.is_mesh_compatible(orig_mesh, mesh) if not mesh_match: transfer_mesh = mesh mesh = orig_mesh else: dcc.delete_node(orig_mesh) # Check if there are duplicated influences and also for the creation of influences that does not currently # in the scene add_joints = list() remove_entries = list() for influence in influences: joints = dcc.list_nodes(influence, full_path=True) if type(joints) == list and len(joints) > 1: add_joints.append(joints[0]) conflicting_count = len(joints) logger.warning( 'Found {} joints with name {}. Using only the first one: {}' .format(conflicting_count, influence, joints[0])) remove_entries.append(influence) influence = joints[0] if not dcc.node_exists(influence): dcc.clear_selection() dcc.create_joint( name=influence, position=influence_dict[influence]['position']) for entry in remove_entries: influences.remove(entry) influences += add_joints settings_data = dict() settings_path = path_utils.join_path(data_path, 'settings.info') if path_utils.is_file(settings_path): lines = fileio.get_file_lines(settings_path) for line in lines: test_line = line.strip() if not test_line: continue line_list = eval(line) attr_name = line_list[0] value = line_list[1] settings_data[attr_name] = value # Create skin cluster and removes if it already exists skin_cluster = deform_utils.find_deformer_by_type(mesh, 'skinCluster') if skin_cluster: dcc.delete_node(skin_cluster) skin_node_name = settings_data.pop('skinNodeName', 'skin_{}'.format(short_name)) skin_cluster = maya.cmds.skinCluster( influences, mesh, tsb=True, n=dcc.find_unique_name(skin_node_name))[0] dcc.set_attribute_value(skin_cluster, 'normalizeWeights', 0) skin_utils.set_skin_weights_to_zero(skin_cluster) # TODO: This Influence mapping configuration should be generated during export and imported here as JSON file # Import ng skin data config = ngst_api.InfluenceMappingConfig() config.use_distance_matching = True config.use_label_matching = True config.use_name_matching = True ngst_api.import_json(mesh, file=ng_skin_data_path, influences_mapping_config=config) maya.cmds.skinCluster(skin_cluster, edit=True, normalizeWeights=1) maya.cmds.skinCluster(skin_cluster, edit=True, forceNormalizeWeights=True) for attr_name, value in settings_data.items(): if attr_name == 'blendWeights': skin_utils.set_skin_blend_weights(skin_cluster, value) else: if dcc.attribute_exists(skin_cluster, attr_name): dcc.set_attribute_value(skin_cluster, attr_name, value) if transfer_mesh: logger.info( 'Import sking weights: mesh topology does not match. Trying to transfer topology ...' ) skin_utils.skin_mesh_from_mesh(mesh, transfer_mesh) dcc.delete_node(mesh) logger.info('Import skinCluster weights: {} from {}'.format( short_name, data_path)) return True
def save(self, *args, **kwargs): """ Saves NG Skin weights file """ dependencies = dict() filepath = self.format_identifier() if not filepath: logger.warning( 'Impossible to save NGSkin Cluster Weights file because save file path not defined!' ) return objects = kwargs.get('objects', None) if not objects: objects = dcc.client().selected_nodes(full_path=True) if not objects: logger.warning( 'Nothing selected to export skin weights of. Please, select a mesh,' ' curve, NURBS surface or lattice with skin weights to export') return False logger.debug('Saving {} | {}'.format(filepath, kwargs)) try: if not dcc.is_plugin_loaded('ngSkinTools2'): dcc.load_plugin('ngSkinTools2') import ngSkinTools2 from ngSkinTools2 import api as ngst_api except ImportError: logger.warning( 'NgSkinTools 2.0 is not installed. Impossible to export ngSkin data' ) return False valid_nodes = list() # Check that all objects that we are going to export have at least one skin cluster node associated # Make sure also that all objects skin output folder have been created obj_dirs = OrderedDict() skin_nodes = OrderedDict() geo_paths = OrderedDict() skin_weights = OrderedDict() for obj in objects: if dcc.client().node_is_a_shape(obj): obj = dcc.client().node_parent(obj, full_path=True) obj_filename = obj if obj.find('|') > -1: obj_filename = obj_filename.replace('|', '.') if obj_filename.startswith('.'): obj_filename = obj_filename[1:] if obj_filename.find(':') > -1: obj_filename = obj_filename.replace(':', '-') skin = dcc.client().find_deformer_by_type(obj, 'skinCluster') if not skin: logger.warning( 'Skip skin weights export for object because no skinCluster found!' .format(obj)) continue valid_nodes.append((obj, obj_filename, skin)) if not valid_nodes: logger.warning( 'Skin exported failed! No objects found with skinClusters applied!' ) return False # Create skin folder only is valid nodes are available file_name = '.{}'.format(os.path.basename(filepath)) file_folder = path_utils.join_path(os.path.dirname(filepath), file_name) if not os.path.isdir(file_folder): skin_folder = folder.create_folder(file_folder, make_unique=True) dependencies[skin_folder] = 'skin_folder' for node_data in valid_nodes: obj, obj_filename, skin = node_data geo_path = path_utils.join_path(file_folder, obj_filename) if path_utils.is_dir(geo_path): folder.delete_folder(obj_filename, file_folder) geo_path = folder.create_folder(obj_filename, file_folder) if not geo_path: logger.error( 'Unable to create skin weights directory: "{}" in "{}"'. format(obj_filename, file_folder)) return False dependencies[geo_path] = 'geo_path' weights = dcc.client().get_skin_weights(skin) obj_dirs[obj] = obj_filename skin_nodes[obj] = skin geo_paths[obj] = geo_path skin_weights[obj] = weights for (obj, skin_node), (_, geo_path), (_, skin_weights) in zip( skin_nodes.items(), geo_paths.items(), skin_weights.items()): logger.info('Exporting weights: {} > {} --> "{}"'.format( obj, skin_node, geo_path)) info_lines = list() info_file = fileio.create_file('influence.info', geo_path) for influence in skin_weights: if influence is None or influence == 'None': continue weight_list = skin_weights[influence] if not weight_list: continue influence_name = skin_utils.get_skin_influence_at_index( influence, skin_node) if not influence_name or not dcc.node_exists(influence_name): continue influence_position = dcc.node_world_space_translation( influence_name) influence_line = "{'%s' : {'position' : %s}}" % ( influence_name, str(influence_position)) info_lines.append(influence_line) writer = fileio.FileWriter(info_file) writer.write(info_lines) settings_file = fileio.create_file('settings.info', geo_path) setting_lines = list() if shape_utils.has_shape_of_type(obj, 'mesh'): self._export_mesh_obj(obj, geo_path) setting_lines.append("['skinNodeName', '{}']".format( dcc.node_short_name(skin_node))) if dcc.attribute_exists(skin_node, 'blendWeights'): blend_weights = skin_utils.get_skin_blend_weights(skin_node) setting_lines.append( "['blendWeights', {}]".format(blend_weights)) if dcc.attribute_exists(skin_node, 'skinningMethod'): skin_method = dcc.get_attribute_value(skin_node, 'skinningMethod') setting_lines.append( "['skinningMethod', {}]".format(skin_method)) write_settings = fileio.FileWriter(settings_file) write_settings.write(setting_lines) ng_skin_file_name = os.path.join(geo_path, 'ngdata.json') ngst_api.export_json(obj, file=ng_skin_file_name) logger.info( 'Skin weights exported successfully: {} > {} --> "{}"'.format( obj, skin_node, geo_path)) data_to_save = OrderedDict() for obj, obj_filename in obj_dirs.items(): data_to_save[obj] = {'enabled': True, 'folder': obj_filename} with open(filepath, 'w') as fh: json.dump(data_to_save, fh) logger.info('Skin weights export operation completed successfully!') return True
def _find_manual_available_name( self, items, name, prefix=None, suffix=None, side='', index=-1, padding=0, letters=False, capital=False, remove_first=0, remove_last=0, search_str=None, replace_str=None, joint_end=False): if dcc.is_maya(): import maya.api.OpenMaya if prefix: if side and side != '': test_name = '{}_{}_{}'.format(prefix, side, name) else: test_name = '{}_{}'.format(prefix, name) else: if side and side != '': test_name = '{}_{}'.format(side, name) else: test_name = name if index >= 0: if letters: letter = strings.get_alpha(index, capital) test_name = '{}_{}'.format(test_name, letter) else: test_name = '{}_{}'.format(test_name, str(index).zfill(padding)) if suffix: test_name = '{}_{}'.format(test_name, suffix) if remove_first and remove_first > 0: test_name = test_name[remove_first:] if remove_last and remove_last > 0: test_name = test_name[:-remove_last] if search_str is not None and search_str != '' and replace_str is not None: test_name = test_name.replace(search_str, replace_str) item_names = list() for item in items: if hasattr(item, 'obj'): item_names.append(item.obj) else: if dcc.is_maya(): if hasattr(item, 'object'): mobj = item.object() try: dag_path = maya.api.OpenMaya.MDagPath.getAPathTo(mobj) item_to_add = dag_path.partialPathName() item_names.append(item_to_add) continue except Exception as exc: LOGGER.warning('Error while retrieving node path from MObject: {}'.format(exc)) continue else: item_names.append(item) # if object exists, try next index if dcc.node_exists(test_name) or test_name in item_names: new_index = int(index) + 1 return self._find_manual_available_name( items, name, prefix=prefix, index=new_index, padding=padding, letters=letters, capital=capital, remove_first=remove_first, remove_last=remove_last, joint_end=joint_end, search_str=search_str, replace_str=replace_str ) return test_name
def reference_alembic(project, alembic_path, namespace=None, fix_path=False): """ References alembic file in current DCC scene :param project: ArtellaProject :param alembic_path: str :param namespace: str :param fix_path: bool """ if not alembic_path or not os.path.isfile(alembic_path): logger.warning( 'Alembic file {} does not exits!'.format(alembic_path)) return None abc_name = os.path.basename(alembic_path).split('.')[0] tag_json_file = os.path.join( os.path.dirname(alembic_path), os.path.basename(alembic_path).replace('.abc', '_abc.info')) if not os.path.isfile(tag_json_file): logger.warning('No Alembic Info file found!') return with open(tag_json_file, 'r') as f: tag_info = json.loads(f.read()) if not tag_info: logger.warning('No Alembic Info loaded!') return root = dcc.client().create_empty_group(name=abc_name) BaseAlembicImporter._add_tag_info_data(project, tag_info, root) sel = [root] sel = sel or None if not namespace: namespace = abc_name new_nodes = alembic.reference_alembic(project=project, alembic_file=alembic_path, namespace=namespace, fix_path=fix_path) if not new_nodes: logger.warning( 'Error while reference Alembic file: {}'.format(alembic_path)) return for obj in new_nodes: if not dcc.node_exists(obj): continue if not dcc.client().node_type(obj) == 'transform': continue obj_parent = dcc.client().node_parent(obj) if obj_parent: continue dcc.client().set_parent(obj, sel[0]) dcc.client().select_node(sel[0]) new_nodes.insert(0, sel[0]) # After parenting referenced nodes, full path changes, here we update node paths if dcc.client().is_maya(): new_paths = list() for n in new_nodes: if dcc.node_exists(n): new_paths.append(n) else: if n.startswith('|'): new_paths.append('{}{}'.format(sel[0], n)) else: new_paths.append('{}|{}'.format(sel[0], n)) return new_paths return new_nodes
def load(self, *args, **kwargs): objects = kwargs.get('objects', None) namespaces = kwargs.get('namespaces', None) option = kwargs.get('option', None) keys_option = kwargs.get('keys_option', None) time = kwargs.get('time', None) if option and not isinstance(option, int): if option.lower() == 'swap': option = 0 elif option.lower() == 'left to right': option = 1 elif option.lower() == 'right to left': option = 2 else: raise ValueError('Invalid load option: {}'.format(option)) self.validate(namespaces=namespaces) results = dict() animation = True found_object = False source_objects = list(self.objects().keys()) if option is None: option = mirrortable.MirrorOptions.Swap if keys_option == mirrortable.KeysOptions.All: time = None elif keys_option == mirrortable.KeysOptions.SelectedRange: time = anim_utils.get_selected_frame_range() # check that given time is not a single frame if time and time[0] == time[1]: time = None animation = None matches = utils.match_names(source_objects=source_objects, target_objects=objects, target_namespaces=namespaces) for source_node, target_node in matches: target_object = target_node.name() target_object2 = self.mirror_object(target_object) or target_object if target_object2 not in results: results[target_object] = target_object2 mirror_axis = self.mirror_axis(source_node.name()) target_object_exists = dcc.node_exists(target_object) target_object2_exists = dcc.node_exists(target_object2) if target_object_exists and target_object2_exists: found_object = True if animation: self.transfer_animation(target_object, target_object2, mirror_axis=mirror_axis, option=option, time=time) else: self.transfer_static(target_object, target_object2, mirror_axis=mirror_axis, option=option) else: if not target_object_exists: logger.warning( 'Cannot find destination object {}'.format( target_object)) if not target_object2_exists: logger.warning( 'Cannot find mirrored destination object {}'. format(target_object2)) dcc.focus_ui_panel('MayaWindow') if not found_object: raise exceptions.NoMatchFoundError( 'No objects match wne loading mirror table data')
def _export(self): """ Internal function that exports Alembic """ out_folder = self._export_path_line.text() if not os.path.exists(out_folder): dcc.client().confirm_dialog( title='Error during Alembic Exportation', message='Output Path does not exists: {}. Select a valid one!'. format(out_folder)) return nodes_to_export = dcc.client().selected_nodes() if not nodes_to_export: logger.error('No nodes to export as Alembic!') return False for n in nodes_to_export: if not dcc.node_exists(n): logger.error( 'Node "{}" does not exists in current scene!'.format(n)) return False root_nodes = list() for n in nodes_to_export: root_node = dcc.client().node_root(node=n) if root_node not in root_nodes: root_nodes.append(root_node) if not root_nodes: logger.error('Not nodes to export as Alembic!') return False file_paths = list() export_info = list() if self._export_all_alembics_together_cbx.isChecked(): export_path = path_utils.clean_path( out_folder + dcc.client().node_short_name(root_nodes[0]) + '.abc') file_paths.append(export_path) export_info.append({'path': export_path, 'nodes': root_nodes}) else: for n in root_nodes: export_path = path_utils.clean_path( out_folder + dcc.client().node_short_name(n) + '.abc') file_paths.append(export_path) export_info.append({'path': export_path, 'nodes': [n]}) res = dcc.client().confirm_dialog( title='Export Alembic File', message='Are you sure you want to export Alembic to files?\n\n' + '\n'.join([p for p in file_paths]), button=['Yes', 'No'], default_button='Yes', cancel_button='No', dismiss_string='No') if res != 'Yes': logger.debug('Aborting Alembic Export operation ...') return result = True try: self._export_alembics(export_info) except Exception as exc: logger.error( 'Something went wrong during Alembic export process: {}'. format(exc)) result = False self._stack.slide_in_index(0) return result
def mirror_control(source_control, target_control=None, mirror_axis='X', mirror_mode=0, mirror_color=None, mirror_replace=False, keep_color=True, from_name=None, to_name=None): """ Find the right side control of a left side control and mirrors the control following next rules: - Mirror only will be applied if corresponding right side name exists - Replace left prefix and suffixes checking for validity :param mirror_axis: str :param mirror_mode: int or None :param mirror_color: int or list(float, float, float) :param mirror_replace: bool :param keep_color: bool :return: str, mirrored control """ if keep_color: target_control_color = get_control_color(source_control) else: target_control_color = get_control_color( source_control ) if not mirror_color and keep_color else mirror_color source_shapes = dcc.list_shapes_of_type(source_control, 'nurbsCurve') if not source_shapes: return None duplicated_control = duplicate_control(source_control) mirror_pivot_grp = dcc.create_empty_group(name='temp_mirrorPivot') duplicated_control = dcc.set_parent(duplicated_control, mirror_pivot_grp) dcc.set_attribute_value(mirror_pivot_grp, 'scale{}'.format(mirror_axis.upper()), -1) target_control = target_control or source_control.replace( from_name, to_name) # We force this conversion. This is something that we should remove in the future if target_control and not dcc.node_exists(target_control): target_control = target_control.replace('Left', 'Right') if target_control and not dcc.node_exists(target_control): target_control = dcc.node_short_name(target_control) if target_control and dcc.node_exists(target_control) and mirror_replace: if keep_color: target_control_color = get_control_color(target_control) mirrored_control = xform_utils.parent_transforms_shapes( target_control, duplicated_control, delete_original=True) else: mirrored_control = dcc.set_parent_to_world(duplicated_control) maya.cmds.delete(mirror_pivot_grp) if mirror_mode == 0: dcc.move_node(mirrored_control, 0, 0, 0, world_space=True) elif mirror_mode == 1: orig_pos = dcc.node_world_space_pivot(source_control) dcc.move_node(mirrored_control, orig_pos[0], orig_pos[1], orig_pos[2], world_space=True) if target_control_color: target_shapes = dcc.list_shapes_of_type(mirrored_control, shape_type='nurbsCurve') for target_shape in target_shapes: dcc.set_node_color(target_shape, target_control_color) if from_name and to_name and from_name != to_name: if from_name in mirrored_control: mirrored_control = dcc.rename_node( mirrored_control, source_control.replace(from_name, to_name)) return mirrored_control
def create_control_curve(control_name='new_ctrl', control_type='circle', controls_path=None, control_size=1.0, translate_offset=(0.0, 0.0, 0.0), rotate_offset=(0.0, 0.0, 0.0), scale=(1.0, 1.0, 1.0), axis_order='XYZ', mirror=None, color=None, line_width=-1, create_buffers=False, buffers_depth=0, match_translate=False, match_rotate=False, match_scale=False, parent=None, **kwargs): """ Creates a new curve based control :param control_name: str, name of the new control to create :param control_type: str, curve types used by the new control :param controls_path: str or None, path were control curve types can be located :param control_size: float, global size of the control :param translate_offset: tuple(float, float, float), XYZ translation offset to apply to the control curves :param rotate_offset: tuple(float, float, float), XYZ rotation offset to apply to the control curves :param scale: tuple(float, float, float), scale of the control. :param axis_order: str, axis order of the control. Default is XYZ. :param mirror: str or None, axis mirror to apply to the control curves (None, 'X', 'Y' or 'Z') :param color: list(float, float, float), RGB or index color to apply to the control :param line_width: str, If given, the new shapes will be parented to given node :param create_buffers: bool, Whether or not control buffer groups should be created. :param buffers_depth: int, Number of buffers groups to create. :param parent: str, If given, the new shapes will be parented to given node :param match_translate: bool, Whether or not new control root node should match the translate with the translation of the current DCC selected node :param match_translate: bool, Whether or not new control root node should match the rotate with the rotation of the current DCC selected node :param match_scale: bool, Whether or not new control root node should match the scale with the scale of the current DCC selected node :return: """ current_selection = dcc.selected_nodes() parent_mobj = None if parent: parent_mobj = api_node.as_mobject(parent) runner = command.CommandRunner() control_data = kwargs.pop('control_data', None) if control_data: parent_mobj, shape_mobjs = runner.run( 'tpDcc-libs-curves-dccs-maya-createCurveFromData', curve_data=control_data, curve_size=control_size, translate_offset=translate_offset, scale=scale, axis_order=axis_order, mirror=mirror, parent=parent_mobj) else: parent_mobj, shape_mobjs = runner.run( 'tpDcc-libs-curves-dccs-maya-createCurveFromPath', curve_type=control_type, curves_path=controls_path, curve_size=control_size, translate_offset=translate_offset, scale=scale, axis_order=axis_order, mirror=mirror, parent=parent_mobj) if parent_mobj: for shape in shape_mobjs: api_node.rename_mobject(shape, control_name) curve_long_name_list = api_node.names_from_mobject_handles(shape_mobjs) else: api_node.rename_mobject(shape_mobjs[0], control_name) curve_long_name = api_node.names_from_mobject_handles(shape_mobjs)[0] curve_long_name_list = [curve_long_name] if rotate_offset != (0.0, 0.0, 0.0): shape_utils.rotate_node_shape_cvs(curve_long_name_list, rotate_offset) if line_width != -1: curve.set_curve_line_thickness(curve_long_name_list, line_width=line_width) # TODO: Support index based color if color is not None: if isinstance(color, int): node_utils.set_color(curve_long_name_list, color) else: node_utils.set_rgb_color(curve_long_name_list, color, linear=True, color_shapes=True) transforms = list() for curve_shape in curve_long_name_list: parent = dcc.node_parent(curve_shape) if parent: if parent not in transforms: transforms.append(parent) else: if curve_shape not in transforms: transforms.append(curve_shape) if not parent and transforms: if create_buffers and buffers_depth > 0: transforms = create_buffer_groups(transforms, buffers_depth) if current_selection: match_transform = current_selection[0] if match_transform and dcc.node_exists(match_transform): for transform in transforms: if match_translate: dcc.match_translation(match_transform, transform) if match_rotate: dcc.match_rotation(match_transform, transform) if match_scale: dcc.match_scale(match_transform, transform) return transforms
def import_alembic(cls, project, alembic_path, parent=None, fix_path=False): """ Implements AlembicImporter import_alembic function Imports Alembic in current DCC scene :param project: ArtellaProject :param alembic_path: str :param parent: object :param fix_path: bool :return: bool """ if not alembic_path or not os.path.isfile(alembic_path): logger.warning( 'Alembic file {} does not exits!'.format(alembic_path)) return None tag_json_file = os.path.join( os.path.dirname(alembic_path), os.path.basename(alembic_path).replace('.abc', '_abc.info')) valid_tag_info = True if os.path.isfile(tag_json_file): with open(tag_json_file, 'r') as f: tag_info = json.loads(f.read()) if not tag_info: logger.warning('No Alembic Info loaded!') valid_tag_info = False else: logger.warning( 'No Alembic Info file found! ' 'Take into account that imported Alembic is not supported by our current pipeline!' ) valid_tag_info = False if not parent: parent = dcc.client().create_empty_group( name=os.path.basename(alembic_path)) else: if not dcc.node_exists(parent): parent = dcc.client().create_empty_group(name=parent) else: logger.warning( 'Impossible to import Alembic into scene because' ' node named "{}" already exists in the scene!'.format( parent)) return if parent and valid_tag_info: cls._add_tag_info_data(project=project, tag_info=tag_info, attr_node=parent) track_nodes = maya_scene.TrackNodes() track_nodes.load() valid_import = alembic.import_alembic(project, alembic_path, mode='import', nodes=None, parent=parent, fix_path=fix_path) if not valid_import: return res = track_nodes.get_delta() # maya.cmds.viewFit(res, animate=True) return res