def validateInertiaData(obj, *args, adjust=False): """Validates an inertia dictionary or object. This checks for the *inertia* and *mass* values in the dictionary (*inertial/inertia* and *inertial/mass* for an object respectively). Also, the inertia values are checked to be positive definite (for diagonal, determinant and eigenvalues). If adjust is set, values are adjusted/fixed for the returned dict/object. E.g. this sets a negative mass to 1e-3. Args: obj(dict/bpy.types.Object): inertia dictionary or object to validate *args: other arguments adjust: if True, bad values will be fixed/complemented (Default value = False) Returns: tuple: list of :class:`ValidateMessage`\ s and the fixed dictionary/object """ from phobos.model.inertia import inertiaListToMatrix, inertiaMatrixToList from phobos.utils.io import getExpSettings import numpy errors = [] expsetting = 10**(-getExpSettings().decimalPlaces) # check dictionary parameters (most of the time pre object creation) if isinstance(obj, dict): missing = [] if 'inertia' not in obj: missing.append('inertia') if 'mass' not in obj: missing.append('mass') if missing: errors.append( ValidateMessage( "Inertia dictionary not fully defined!", 'WARNING', None, None, { 'log_info': "Missing: " + ' '.join(["'{0}'".format(miss) for miss in missing]) + " Set to default 1e-3." }, )) if 'inertia' in missing: obj['inertia'] = (1e-3, 0., 0., 1e-3, 0., 1e-3) if 'mass' in missing: obj['mass'] = 1e-3 inertia = obj['inertia'] mass = obj['mass'] # check existing object properties elif isinstance(obj, bpy.types.Object): if 'inertial/inertia' not in obj: errors.append( ValidateMessage( "Inertia not defined!", 'WARNING', obj, 'phobos.generate_inertial_objects', {'log_info': "Set to default 1e-3."}, )) obj['inertial/inertia'] = (1e-3, 0., 0., 1e-3, 0., 1e-3) if 'inertial/mass' not in obj: errors.append( ValidateMessage( "Mass is not defined!", 'WARNING', obj, 'phobos.generate_inertial_objects', {'log_info': "Set to default 1e-3."}, )) obj['inertial/mass'] = 1e-3 inertia = obj['inertial/inertia'] mass = obj['inertial/mass'] # Check inertia vector for various properties, round to export precision inertia = numpy.around(numpy.array(inertiaListToMatrix(inertia)), decimals=getExpSettings().decimalPlaces) if any(element <= expsetting for element in inertia.diagonal()): errors.append( ValidateMessage( "Negative semidefinite main diagonal in inertia data!", 'WARNING', None if isinstance(obj, dict) else obj, None, {'log_info': "Diagonal: " + str(inertia.diagonal())}, )) # Calculate the determinant if consistent, quick check if numpy.linalg.det(inertia) <= expsetting: errors.append( ValidateMessage( "Negative semidefinite determinant in inertia data! Checking singular values.", 'WARNING', None if isinstance(obj, dict) else obj, None, {'log_info': "Determinant: " + str(numpy.linalg.det(inertia))}, )) # Calculate the eigenvalues if not consistent if any(element <= expsetting for element in numpy.linalg.eigvals(inertia)): # Apply singular value decomposition and correct the values S, V = numpy.linalg.eig(inertia) S[S <= expsetting] = expsetting inertia = V.dot(numpy.diag(S).dot(V.T)) errors.append( ValidateMessage( "Negative semidefinite eigenvalues in inertia data!", 'WARNING', None if isinstance(obj, dict) else obj, None, { 'log_info': "Eigenvalues: " + str(numpy.linalg.eigvals(inertia)) }, )) if mass <= 0.: errors.append( ValidateMessage( "Mass is {}!".format('zero' if mass == 0. else 'negative'), 'WARNING', None if isinstance(obj, dict) else obj, None, {} if not adjust else {'log_info': "Adjusted to 1e-3."}, )) mass = expsetting inertia = inertiaMatrixToList(inertia) if adjust and isinstance(obj, bpy.types.Object): obj['inertial/inertia'] = inertia obj['inertial/mass'] = mass elif adjust: obj['inertia'] = inertia obj['mass'] = mass return errors, obj
def validateInertiaData(obj, *args, adjust=False): """Validates an inertia dictionary or object. This checks for the *inertia* and *mass* values in the dictionary (*inertial/inertia* and *inertial/mass* for an object respectively). Also, the inertia values are checked to be positive definite (for diagonal, determinant and eigenvalues). If adjust is set, values are adjusted/fixed for the returned dict/object. E.g. this sets a negative mass to 1e-3. Args: obj(dict/bpy.types.Object): inertia dictionary or object to validate *args: other arguments adjust: if True, bad values will be fixed/complemented (Default value = False) Returns: tuple: list of :class:`ValidateMessage`\ s and the fixed dictionary/object """ from phobos.model.inertia import inertiaListToMatrix, inertiaMatrixToList from phobos.utils.io import getExpSettings import numpy errors = [] # check dictionary parameters (most of the time pre object creation) if isinstance(obj, dict): missing = [] if 'inertia' not in obj: missing.append('inertia') if 'mass' not in obj: missing.append('mass') if missing: errors.append( ValidateMessage( "Inertia dictionary not fully defined!", 'WARNING', None, None, { 'log_info': "Missing: " + ' '.join(["'{0}'".format(miss) for miss in missing]) + " Set to default 1e-3." }, ) ) if 'inertia' in missing: obj['inertia'] = (1e-3, 0., 0., 1e-3, 0., 1e-3) if 'mass' in missing: obj['mass'] = 1e-3 inertia = obj['inertia'] mass = obj['mass'] # check existing object properties elif isinstance(obj, bpy.types.Object): if 'inertial/inertia' not in obj: errors.append( ValidateMessage( "Inertia not defined!", 'WARNING', obj, 'phobos.generate_inertial_objects', {'log_info': "Set to default 1e-3."}, ) ) obj['inertial/inertia'] = (1e-3, 0., 0., 1e-3, 0., 1e-3) if 'inertial/mass' not in obj: errors.append( ValidateMessage( "Mass is not defined!", 'WARNING', obj, 'phobos.generate_inertial_objects', {'log_info': "Set to default 1e-3."}, ) ) obj['inertial/mass'] = 1e-3 inertia = obj['inertial/inertia'] mass = obj['inertial/mass'] # Check inertia vector for various properties, round to export precision inertia = numpy.around( numpy.array(inertiaListToMatrix(inertia)), decimals=getExpSettings().decimalPlaces ) if any(element <= 0.0 for element in inertia.diagonal()): errors.append( ValidateMessage( "Negative semidefinite main diagonal in inertia data!", 'WARNING', None if isinstance(obj, dict) else obj, None, {'log_info': "Diagonal: " + str(inertia.diagonal())}, ) ) # Calculate the determinant if consistent, quick check if numpy.linalg.det(inertia) <= 0.0: errors.append( ValidateMessage( "Negative semidefinite determinant in inertia data! Checking singular values.", 'WARNING', None if isinstance(obj, dict) else obj, None, {'log_info': "Determinant: " + str(numpy.linalg.det(inertia))}, ) ) # Calculate the eigenvalues if not consistent if any(element <= 0.0 for element in numpy.linalg.eigvals(inertia)): # Apply singular value decomposition and correct the values S, V = numpy.linalg.eig(inertia) S[S <= 0.0] = 1e-3 inertia = V.dot(numpy.diag(S).dot(V.T)) errors.append( ValidateMessage( "Negative semidefinite eigenvalues in inertia data!", 'WARNING', None if isinstance(obj, dict) else obj, None, {'log_info': "Eigenvalues: " + str(numpy.linalg.eigvals(inertia))}, ) ) if mass <= 0.: errors.append( ValidateMessage( "Mass is {}!".format('zero' if mass == 0. else 'negative'), 'WARNING', None if isinstance(obj, dict) else obj, None, {} if not adjust else {'log_info': "Adjusted to 1e-3."}, ) ) mass = 1e-3 inertia = inertiaMatrixToList(inertia) if adjust and isinstance(obj, bpy.types.Object): obj['inertial/inertia'] = inertia obj['inertial/mass'] = mass elif adjust: obj['inertia'] = inertia obj['mass'] = mass return errors, obj