def center_of_mass(self): """the centroid formuala is way more complicated if you consider the nonstructural mass axis""" elem = self prop = self.pid_ref node1 = self.ga_ref node2 = self.gb_ref xyz1 = node1.get_position() xyz2 = node2.get_position() #centroid = ( + self.gb_ref.get_position()) / 2. centroid = (xyz1 + xyz2) / 2. #length = norm(xyz2 - xyz1) #cda = model.nodes[n1].cid_ref #cdb = model.nodes[n2].cid_ref model = None log = None is_failed, out = elem.get_axes_by_nodes(model, self.pid_ref, node1, node2, xyz1, xyz2, log) if is_failed: #model.log.error(out) raise RuntimeError(out) wa, wb, _ihat, jhat, khat = out p1 = xyz1 + wa p2 = xyz2 + wb if prop.type == 'PBEAM': rho = prop.Rho() # we don't call the MassPerLength method so we can put the NSM centroid # on a different axis (the PBEAM is weird) mass_per_lengths = [] nsm_per_lengths = [] for (area, nsm) in zip(prop.A, prop.nsm): mass_per_lengths.append(area * rho) nsm_per_lengths.append(nsm) mass_per_length = integrate_positive_unit_line(prop.xxb, mass_per_lengths) nsm_per_length = integrate_positive_unit_line(prop.xxb, nsm_per_lengths) nsm_n1 = (p1 + jhat * prop.m1a + khat * prop.m2a) nsm_n2 = (p2 + jhat * prop.m1b + khat * prop.m2b) #print("nsm_per_length=%s" % nsm_per_length) #print("nsm_n1=%s" % nsm_n1) #print("nsm_n2=%s" % nsm_n2) nsm_centroid = (nsm_n1 + nsm_n2) / 2. #if nsm != 0.: #p1_nsm = p1 + prop.ma #p2_nsm = p2 + prop.mb elif prop.type == 'PBEAML': mass_per_lengths = prop.get_mass_per_lengths() #mass_per_length = prop.MassPerLength() # includes simplified nsm # m1a, m1b, m2a, m2b=0. nsm_centroid = (p1 + p2) / 2. # mass_per_length already includes nsm mass_per_length = integrate_positive_unit_line(prop.xxb, mass_per_lengths) nsm_per_length = 0. #print('mass_per_lengths=%s nsm_per_lengths=%s' % ( #mass_per_lengths, nsm_per_lengths)) #print('mass_per_length=%s nsm_per_length=%s' % ( #mass_per_length, nsm_per_length)) #nsm_centroid = np.zeros(3) # TODO: what is this... #nsm = prop.nsm[0] * length # TODO: simplified elif prop.type == 'PBCOMP': mass_per_length = prop.MassPerLength() nsm_per_length = prop.nsm nsm_n1 = (p1 + jhat * prop.m1 + khat * prop.m2) nsm_n2 = (p2 + jhat * prop.m1 + khat * prop.m2) nsm_centroid = (nsm_n1 + nsm_n2) / 2. #elif prop.type == 'PBMSECT': #continue #mass_per_length = prop.MassPerLength() #m = mass_per_length * length #nsm = prop.nsm else: raise NotImplementedError(prop.type) total_mass = mass_per_length + nsm_per_length if total_mass == 0.0: return centroid centroid2 = (centroid * mass_per_length + nsm_centroid * nsm_per_length) / total_mass return centroid2
def _mass_properties_new(model, element_ids=None, mass_ids=None, reference_point=None, sym_axis=None, scale=None, xyz_cid0=None): # pragma: no cover """ half implemented, not tested, should be faster someday... don't use this Caclulates mass properties in the global system about the reference point. Parameters ---------- model : BDF() a BDF object element_ids : list[int]; (n, ) ndarray, optional An array of element ids. mass_ids : list[int]; (n, ) ndarray, optional An array of mass ids. reference_point : ndarray/str/int, optional type : ndarray An array that defines the origin of the frame. default = <0,0,0>. type : str 'cg' is the only allowed string type : int the node id sym_axis : str, optional The axis to which the model is symmetric. If AERO cards are used, this can be left blank allowed_values = 'no', x', 'y', 'z', 'xy', 'yz', 'xz', 'xyz' scale : float, optional The WTMASS scaling value. default=None -> PARAM, WTMASS is used float > 0.0 xyz_cid0 : dict[nid] : xyz; default=None -> auto-calculate mapping of the node id to the global position Returns ------- mass : float The mass of the model. cg : ndarray The cg of the model as an array. I : ndarray Moment of inertia array([Ixx, Iyy, Izz, Ixy, Ixz, Iyz]). I = mass * centroid * centroid .. math:: I_{xx} = m (dy^2 + dz^2) .. math:: I_{yz} = -m * dy * dz where: .. math:: dx = x_{element} - x_{ref} .. seealso:: http://en.wikipedia.org/wiki/Moment_of_inertia#Moment_of_inertia_tensor .. note:: This doesn't use the mass matrix formulation like Nastran. It assumes m*r^2 is the dominant term. If you're trying to get the mass of a single element, it will be wrong, but for real models will be correct. Example 1 --------- # mass properties of entire structure mass, cg, I = model.mass_properties() Ixx, Iyy, Izz, Ixy, Ixz, Iyz = I Example 2 --------- # mass properties of model based on Property ID pids = list(model.pids.keys()) pid_eids = model.get_element_ids_dict_with_pids(pids) for pid, eids in sorted(iteritems(pid_eids)): mass, cg, I = mass_properties(model, element_ids=eids) """ #if reference_point is None: reference_point = array([0., 0., 0.]) if xyz_cid0 is None: xyz = {} for nid, node in iteritems(model.nodes): xyz[nid] = node.get_position() else: xyz = xyz_cid0 elements, masses = _mass_properties_elements_init(model, element_ids, mass_ids) #mass = 0. #cg = array([0., 0., 0.]) #I = array([0., 0., 0., 0., 0., 0., ]) #if isinstance(reference_point, string_types): #if reference_point == 'cg': #mass = 0. #for pack in [elements, masses]: #for element in pack: #try: #p = element.Centroid() #m = element.Mass() #mass += m #cg += m * p #except: #pass #if mass == 0.0: #return mass, cg, I #reference_point = cg / mass #else: ## reference_point = [0.,0.,0.] or user-defined array #pass mass = 0. cg = array([0., 0., 0.]) I = array([ 0., 0., 0., 0., 0., 0., ]) no_mass = [ 'CELAS1', 'CELAS2', 'CELAS3', 'CELAS4', #'CLEAS5', 'CDAMP1', 'CDAMP2', 'CDAMP3', 'CDAMP4', 'CDAMP5', 'CBUSH', 'CBUSH1D', 'CBUSH2D', 'CVISC', 'CGAP', # is this right? 'CFAST', 'CRAC2D', 'CRAC3D', 'CSSCHD', 'CAERO1', 'CAERO2', 'CAERO3', 'CAERO4', 'CAERO5', 'CBARAO', 'CORD1R', 'CORD2R', 'CORD1C', 'CORD2C', 'CORD1S', 'CORD2S', 'CORD3G', 'CONV', 'CONVM', 'CSET', 'CSET1', 'CLOAD', 'CHBDYG', 'CHBDYE', 'CHBDYP', 'CTRAX3', 'CTRAX6', 'CQUADX8', 'CQUADX4', 'CPLSTN3', 'CPLSTN6', 'CPLSTN4', 'CPLSTN8', ] all_eids = np.array(list(model.elements.keys()), dtype='int32') all_eids.sort() all_mass_ids = np.array(list(model.masses.keys()), dtype='int32') all_mass_ids.sort() #def _increment_inertia0(centroid, reference_point, m, mass, cg, I): #"""helper method""" #(x, y, z) = centroid - reference_point #mass += m #cg += m * centroid #return mass def _increment_inertia(centroid, reference_point, m, mass, cg, I): """helper method""" (x, y, z) = centroid - reference_point x2 = x * x y2 = y * y z2 = z * z I[0] += m * (y2 + z2) # Ixx I[1] += m * (x2 + z2) # Iyy I[2] += m * (x2 + y2) # Izz I[3] += m * x * y # Ixy I[4] += m * x * z # Ixz I[5] += m * y * z # Iyz mass += m cg += m * centroid return mass def get_sub_eids(all_eids, eids): """supports limiting the element/mass ids""" eids = np.array(eids) ieids = np.searchsorted(all_eids, eids) eids2 = eids[all_eids[ieids] == eids] return eids2 etypes_skipped = set([]) for etype, eids in iteritems(model._type_to_id_map): if etype in no_mass: continue elif etype in ['CROD', 'CONROD']: eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2 = elem.node_ids length = norm(xyz[n2] - xyz[n1]) centroid = (xyz[n1] + xyz[n2]) / 2. mpl = elem.MassPerLength() m = mpl * length mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CTUBE': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2 = elem.node_ids length = norm(xyz[n2] - xyz[n1]) centroid = (xyz[n1] + xyz[n2]) / 2. mpl = elem.pid_ref.MassPerLength() m = mpl * length mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CBAR': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2 = elem.node_ids centroid = (xyz[n1] + xyz[n2]) / 2. length = norm(xyz[n2] - xyz[n1]) mpl = elem.pid_ref.MassPerLength() m = mpl * length mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CBEAM': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] prop = elem.pid_ref n1, n2 = elem.node_ids node1 = xyz[n1] node2 = xyz[n2] centroid = (node1 + node2) / 2. length = norm(node2 - node1) #cda = model.nodes[n1].cid_ref #cdb = model.nodes[n2].cid_ref is_failed, wa, wb, _ihat, jhat, khat = elem.get_axes(model) p1 = node1 + wa p2 = node2 + wb if prop.type == 'PBEAM': rho = prop.Rho() mass_per_lengths = [] nsm_per_lengths = [] for (area, nsm) in zip(prop.A, prop.nsm): mass_per_lengths.append(area * rho) nsm_per_lengths.append(nsm) mass_per_length = integrate_positive_unit_line( prop.xxb, mass_per_lengths) nsm_per_length = integrate_positive_unit_line( prop.xxb, nsm_per_lengths) #nsm = np.mean(prop.nsm) nsm = nsm_per_length * length m = mass_per_length * length nsm_n1 = (p1 + jhat * prop.m1a + khat * prop.m2a) nsm_n2 = (p2 + jhat * prop.m1b + khat * prop.m2b) nsm_centroid = (nsm_n1 + nsm_n2) / 2. #if nsm != 0.: #p1_nsm = p1 + prop.ma #p2_nsm = p2 + prop.mb elif prop.type == 'PBEAML': #mass_per_lengths = elem.get_mass_per_lengths() mass_per_length = prop.MassPerLength( ) # includes simplified nsm m = mass_per_length * length nsm_centroid = np.zeros(3) nsm = prop.nsm[0] # TODO: simplified elif prop.type == 'PBCOMP': mass_per_length = prop.MassPerLength() m = mass_per_length * length nsm = prop.nsm nsm_n1 = (p1 + jhat * prop.m1 + khat * prop.m2) nsm_n2 = (p2 + jhat * prop.m1 + khat * prop.m2) nsm_centroid = (nsm_n1 + nsm_n2) / 2. else: raise NotImplementedError(prop.type) #mpl = elem.pid_ref.MassPerLength() #m = mpl * length (x, y, z) = centroid - reference_point (xm, ym, zm) = nsm_centroid - reference_point x2 = x * x y2 = y * y z2 = z * z xm2 = xm * xm ym2 = ym * ym zm2 = zm * zm # Ixx, Iyy, Izz, Ixy, Ixz, Iyz I[0] += m * (y2 + z2) + nsm * (ym2 + zm2) I[1] += m * (x2 + z2) + nsm * (xm2 + zm2) I[2] += m * (x2 + y2) + nsm * (xm2 + ym2) I[3] += m * x * y + nsm * xm * ym I[4] += m * x * z + nsm * xm * zm I[5] += m * y * z + nsm * ym * zm mass += m + nsm cg += m * centroid + nsm * nsm_centroid elif etype in ['CTRIA3', 'CTRIA6', 'CTRIAR']: eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3 = elem.node_ids[:3] prop = elem.pid_ref centroid = (xyz[n1] + xyz[n2] + xyz[n3]) / 3. area = 0.5 * norm(cross(xyz[n1] - xyz[n2], xyz[n1] - xyz[n3])) if prop.type == 'PSHELL': tflag = elem.tflag ti = prop.Thickness() if tflag == 0: # absolute t1 = elem.T1 if elem.T1 else ti t2 = elem.T2 if elem.T2 else ti t3 = elem.T3 if elem.T3 else ti elif tflag == 1: # relative t1 = elem.T1 * ti if elem.T1 else ti t2 = elem.T2 * ti if elem.T2 else ti t3 = elem.T3 * ti if elem.T3 else ti else: raise RuntimeError('tflag=%r' % tflag) assert t1 + t2 + t3 > 0., 't1=%s t2=%s t3=%s' % (t1, t2, t3) t = (t1 + t2 + t3) / 3. # m/A = rho * A * t + nsm #mass_per_area = elem.nsm + rho * elem.t mpa = prop.nsm + prop.Rho() * t #mpa = elem.pid_ref.MassPerArea() m = mpa * area elif prop.type in ['PCOMP', 'PCOMPG']: # PCOMP, PCOMPG #rho_t = prop.get_rho_t() #nsm = prop.nsm #rho_t = [mat.Rho() * t for (mat, t) in zip(prop.mids_ref, prop.ts)] #mpa = sum(rho_t) + nsm # works for PCOMP # F:\Program Files\Siemens\NXNastran\nxn10p1\nxn10p1\nast\tpl\cqr3compbuck.dat mpa = prop.get_mass_per_area() elif prop.type == 'PLPLANE': continue else: raise NotImplementedError(prop.type) m = area * mpa mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype in ['CQUAD4', 'CQUAD8', 'CQUADR']: eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4 = elem.node_ids[:4] prop = elem.pid_ref centroid = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. area = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n4] - xyz[n2])) if prop.type == 'PSHELL': tflag = elem.tflag ti = prop.Thickness() if tflag == 0: # absolute t1 = elem.T1 if elem.T1 else ti t2 = elem.T2 if elem.T2 else ti t3 = elem.T3 if elem.T3 else ti t4 = elem.T4 if elem.T4 else ti elif tflag == 1: # relative t1 = elem.T1 * ti if elem.T1 else ti t2 = elem.T2 * ti if elem.T2 else ti t3 = elem.T3 * ti if elem.T3 else ti t4 = elem.T4 * ti if elem.T4 else ti else: raise RuntimeError('tflag=%r' % tflag) assert t1 + t2 + t3 + t4 > 0., 't1=%s t2=%s t3=%s t4=%s' % ( t1, t2, t3, t4) t = (t1 + t2 + t3 + t4) / 4. # m/A = rho * A * t + nsm #mass_per_area = model.nsm + rho * model.t mpa = prop.nsm + prop.Rho() * t #mpa = elem.pid_ref.MassPerArea() #m = mpa * area elif prop.type in ['PCOMP', 'PCOMPG']: # PCOMP, PCOMPG #rho_t = prop.get_rho_t() #nsm = prop.nsm #rho_t = [mat.Rho() * t for (mat, t) in zip(prop.mids_ref, prop.ts)] #mpa = sum(rho_t) + nsm mpa = prop.get_mass_per_area() elif prop.type == 'PLPLANE': continue #raise NotImplementedError(prop.type) else: raise NotImplementedError(prop.type) m = area * mpa mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CQUAD': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4 = elem.node_ids[:4] prop = elem.pid_ref centroid = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. area = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n4] - xyz[n2])) if prop.type == 'PSHELL': t = prop.Thickness() mpa = prop.nsm + prop.Rho() * t elif prop.type in ['PCOMP', 'PCOMPG']: mpa = prop.get_mass_per_area() elif prop.type == 'PLPLANE': continue #raise NotImplementedError(prop.type) else: raise NotImplementedError(prop.type) m = area * mpa mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CSHEAR': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4 = elem.node_ids prop = elem.pid_ref centroid = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. area = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n4] - xyz[n2])) mpa = prop.MassPerArea() m = area * mpa mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype in [ 'CONM1', 'CONM2', 'CMASS1', 'CMASS2', 'CMASS3', 'CMASS4' ]: eids2 = get_sub_eids(all_mass_ids, eids) for eid in eids2: elem = model.masses[eid] m = elem.Mass() centroid = elem.Centroid() mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CTETRA': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4 = elem.node_ids[:4] centroid = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. #V = -dot(n1 - n4, cross(n2 - n4, n3 - n4)) / 6. volume = -dot(xyz[n1] - xyz[n4], cross(xyz[n2] - xyz[n4], xyz[n3] - xyz[n4])) / 6. m = elem.Rho() * volume mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CPYRAM': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4, n5 = elem.node_ids[:5] centroid1 = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. area1 = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n4] - xyz[n2])) centroid5 = xyz[n5] centroid = (centroid1 + centroid5) / 2. volume = area1 / 3. * norm(centroid1 - centroid5) m = elem.Rho() * volume mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CPENTA': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4, n5, n6 = elem.node_ids[:6] area1 = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n2] - xyz[n1])) area2 = 0.5 * norm(cross(xyz[n6] - xyz[n4], xyz[n5] - xyz[n4])) centroid1 = (xyz[n1] + xyz[n2] + xyz[n3]) / 3. centroid2 = (xyz[n4] + xyz[n5] + xyz[n6]) / 3. centroid = (centroid1 + centroid2) / 2. volume = (area1 + area2) / 2. * norm(centroid1 - centroid2) m = elem.Rho() * volume mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype == 'CHEXA': eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] n1, n2, n3, n4, n5, n6, n7, n8 = elem.node_ids[:8] #(A1, c1) = area_centroid(n1, n2, n3, n4) centroid1 = (xyz[n1] + xyz[n2] + xyz[n3] + xyz[n4]) / 4. area1 = 0.5 * norm(cross(xyz[n3] - xyz[n1], xyz[n4] - xyz[n2])) #(A2, c2) = area_centroid(n5, n6, n7, n8) centroid2 = (xyz[n5] + xyz[n6] + xyz[n7] + xyz[n8]) / 4. area2 = 0.5 * norm(cross(xyz[n7] - xyz[n5], xyz[n8] - xyz[n6])) volume = (area1 + area2) / 2. * norm(centroid1 - centroid2) m = elem.Rho() * volume centroid = (centroid1 + centroid2) / 2. mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype in no_mass: continue elif etype == 'CBEND': model.log.info('elem.type=%s doesnt have mass' % etype) continue elif etype.startswith('C'): eids2 = get_sub_eids(all_eids, eids) for eid in eids2: elem = model.elements[eid] #if elem.pid_ref.type in ['PPLANE']: try: m = elem.Mass() except: model.log.error('etype = %r' % etype) print(elem) print(elem.pid_ref) raise centroid = elem.Centroid() if m > 0.0: model.log.info('elem.type=%s is not supported in new ' 'mass properties method' % elem.type) mass = _increment_inertia(centroid, reference_point, m, mass, cg, I) elif etype not in etypes_skipped: model.log.info('elem.type=%s doesnt have mass' % elem.type) etypes_skipped.add(etype) if mass: cg /= mass mass, cg, I = _apply_mass_symmetry(model, sym_axis, scale, mass, cg, I) # Ixx, Iyy, Izz, Ixy, Ixz, Iyz = I return mass, cg, I