def test_B(self): cid0 = CORD2R() Lx = 2. Ly = 3. Lz = 5. Fy = 1.5 origin = array([-Lx, -Ly, -Lz]) z_axis = origin + array([0., 0., 1.]) xz_plane = origin + array([1., 0., 1.]) rid = 0 data = [1, rid] + list(origin) + list(z_axis) + list(xz_plane) Fxyz = [0., -Fy, 0.] Mxyz = [0., 0., 0.] cid_new = CORD2R(data=data) model = None Fxyz_local, Mxyz_local = transform_load(Fxyz, Mxyz, cid0, cid_new, model) r = array([Lx, Ly, Lz]) F = array([0., -Fy, 0.]) M = cross(r, F) self.assertTrue(array_equal( Fxyz_local, F)), "expected=%s actual=%s" % (F, Fxyz_local) self.assertTrue(array_equal(Mxyz_local, cross( r, F))), "expected=%s actual=%s" % (M, Mxyz_local)
def sum_forces_moments( self, p0: int, loadcase_id: int, cid: int = 0, include_grav: bool = False, xyz_cid0: Union[None, Dict[int, np.ndarray]] = None ) -> Tuple[np.ndarray, np.ndarray]: """ Sums applied forces & moments about a reference point p0 for all load cases. Considers: - FORCE, FORCE1, FORCE2 - MOMENT, MOMENT1, MOMENT2 - PLOAD, PLOAD2, PLOAD4 - LOAD Parameters ---------- p0 : NUMPY.NDARRAY shape=(3,) or integer (node ID) the reference point loadcase_id : int the LOAD=ID to analyze cid : int; default=0 the coordinate system for the summation include_grav : bool; default=False includes gravity in the summation (not supported) xyz_cid0 : None / Dict[int] = (3, ) ndarray the nodes in the global coordinate system Returns ------- forces : NUMPY.NDARRAY shape=(3,) the forces moments : NUMPY.NDARRAY shape=(3,) the moments .. warning:: not full validated .. todo:: It's super slow for cid != 0. We can speed this up a lot if we calculate the normal, area, centroid based on precomputed node locations. Pressure acts in the normal direction per model/real/loads.bdf and loads.f06 """ self.deprecated( 'forces, moments = model.sum_forces_moments(...)', 'from pyNastran.bdf.mesh_utils.loads import sum_forces_moments\n' 'forces, moments = sum_forces_moments(model, ...)', '1.3') forces, moments = sum_forces_moments(self, p0, loadcase_id, include_grav=include_grav, xyz_cid0=xyz_cid0) if cid == 0: return forces, moments cid0 = 0 forces, moments = transform_load(forces, moments, cid0, cid, self) return forces, moments
def sum_forces_moments_elements(model: BDF, p0: int, loadcase_id: int, eids: List[int], nids: List[int], cid: int=0, include_grav: bool=False, xyz_cid0: Optional[Dict[int, NDArray3float]]=None, ) -> Tuple[NDArray3float, NDArray3float]: """ Sum the forces/moments based on a list of nodes and elements. Parameters ---------- model : BDF() a BDF object eids : List[int] the list of elements to include (e.g. the loads due to a PLOAD4) nids : List[int] the list of nodes to include (e.g. the loads due to a FORCE card) p0 : int; (3,) ndarray the point to sum moments about type = int sum moments about the specified grid point type = (3, ) ndarray/list (e.g. [10., 20., 30]): the x, y, z location in the global frame loadcase_id : int the LOAD=ID to analyze include_grav : bool; default=False includes gravity in the summation (not supported) xyz_cid0 : None / Dict[int] = (3, ) ndarray the nodes in the global coordinate system Returns ------- forces : NUMPY.NDARRAY shape=(3,) the forces moments : NUMPY.NDARRAY shape=(3,) the moments Nodal Types : FORCE, FORCE1, FORCE2, MOMENT, MOMENT1, MOMENT2, PLOAD Element Types: PLOAD1, PLOAD2, PLOAD4, GRAV If you have a CQUAD4 (eid=3) with a PLOAD4 (sid=3) and a FORCE card (nid=5) acting on it, you can incldue the PLOAD4, but not the FORCE card by using: For just pressure: .. code-block:: python eids = [3] nids = [] For just force: .. code-block:: python eids = [] nids = [5] or both: .. code-block:: python eids = [3] nids = [5] .. note:: If you split the model into sections and sum the loads on each section, you may not get the same result as if you summed the loads on the total model. This is due to the fact that nodal loads on the boundary are double/triple/etc. counted depending on how many breaks you have. .. todo:: not done... """ if not isinstance(loadcase_id, integer_types): raise RuntimeError('loadcase_id must be an integer; loadcase_id=%r' % loadcase_id) p = _get_load_summation_point(model, p0, cid=0) if eids is None: eids = list(model.element_ids) if nids is None: nids = list(model.node_ids) #for (key, load_case) in model.loads.items(): #if key != loadcase_id: #continue loads, scale_factors, unused_is_grav = model.get_reduced_loads( loadcase_id, skip_scale_factor0=True) F = array([0., 0., 0.]) M = array([0., 0., 0.]) xyz = get_xyz_cid0_dict(model, xyz_cid0) unsupported_types = set() shell_elements = { 'CTRIA3', 'CQUAD4', 'CTRIAR', 'CQUADR', 'CTRIA6', 'CQUAD8', 'CQUAD', 'CSHEAR'} skip_loads = {'QVOL'} for load, scale in zip(loads, scale_factors): #if load.type not in ['FORCE1']: #continue #print(load.type) loadtype = load.type if loadtype == 'FORCE': if load.node_id not in nids: continue if load.Cid() != 0: cp_ref = load.cid_ref #from pyNastran.bdf.bdf import CORD2R #cp = CORD2R() f = load.mag * cp_ref.transform_vector_to_global(load.xyz) * scale else: f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'FORCE1': not_found_nid = False for nid in load.node_ids: if nid not in nids: not_found_nid = True break if not_found_nid: continue f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'FORCE2': not_found_nid = False for nid in load.node_ids: if nid not in nids: not_found_nid = True break if not_found_nid: continue f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'MOMENT': not_found_nid = False for nid in load.node_ids: if nid not in nids: not_found_nid = True break if not_found_nid: continue if load.Cid() != 0: cp_ref = load.cid_ref m = cp_ref.transform_vector_to_global(load.xyz) else: m = load.xyz M += load.mag * m * scale elif load.type == 'MOMENT1': not_found_nid = False for nid in load.node_ids: if nid not in nids: not_found_nid = True break if not_found_nid: continue m = load.mag * load.xyz * scale M += m elif loadtype == 'MOMENT2': not_found_nid = False for nid in load.node_ids: if nid not in nids: not_found_nid = True break if not_found_nid: continue m = load.mag * load.xyz * scale M += m elif loadtype == 'PLOAD': nodes = load.node_ids nnodes = len(nodes) nodesi = 0 if nnodes == 3: n1, n2, n3 = xyz[nodes[0]], xyz[nodes[1]], xyz[nodes[2]] axb = cross(n1 - n2, n1 - n3) centroid = (n1 + n2 + n3) / 3. elif nnodes == 4: n1, n2, n3, n4 = xyz[nodes[0]], xyz[nodes[1]], xyz[nodes[2]], xyz[nodes[3]] axb = cross(n1 - n3, n2 - n4) centroid = (n1 + n2 + n3 + n4) / 4. if nodes[3] in nids: nodesi += 1 else: raise RuntimeError('invalid number of nodes on PLOAD card; ' 'nodes=%s' % str(nodes)) if nodes[0] in nids: nodesi += 1 if nodes[1] in nids: nodesi += 1 if nodes[2] in nids: nodesi += 1 area, normal = _get_area_normal(axb, nodes, xyz) r = centroid - p f = load.pressure * area * normal * scale m = cross(r, f) node_scale = nodesi / float(nnodes) F += f * node_scale M += m * node_scale elif loadtype == 'PLOAD1': _pload1_elements(model, loadcase_id, load, scale, eids, xyz, F, M, p) elif loadtype == 'PLOAD2': pressure = load.pressure * scale for eid in load.element_ids: if eid not in eids: continue elem = model.elements[eid] if elem.type in shell_elements: normal = elem.Normal() area = elem.Area() f = pressure * normal * area r = elem.Centroid() - p m = cross(r, f) F += f M += m else: #model.log.warning('case=%s etype=%r loadtype=%r not supported' % ( #loadcase_id, elem.type, loadtype)) raise NotImplementedError('case=%s etype=%r loadtype=%r not supported' % ( loadcase_id, elem.type, loadtype)) elif loadtype == 'PLOAD4': _pload4_elements(loadcase_id, load, scale, eids, xyz, F, M, p) elif loadtype == 'GRAV': if include_grav: # this will be super slow g = load.GravityVector() * scale for eid, elem in model.elements.items(): if eid not in eids: continue centroid = elem.Centroid() mass = elem.Mass() r = centroid - p f = mass * g m = cross(r, f) F += f M += m elif loadtype in skip_loads: continue else: # we collect them so we only get one print unsupported_types.add(loadtype) for loadtype in unsupported_types: model.log.warning('case=%s loadtype=%r not supported' % (loadcase_id, loadtype)) #model.log.info("case=%s F=%s M=%s\n" % (loadcase_id, F, M)) if cid == 0: return F, M cid0 = 0 F2, M2 = transform_load(F, M, cid0, cid, model) return F2, M2
def sum_forces_moments(model: BDF, p0: np.ndarray, loadcase_id: int, cid: int=0, include_grav: bool=False, xyz_cid0: Optional[Dict[int, NDArray3float]]=None, ) -> Tuple[NDArray3float, NDArray3float]: """ Sums applied forces & moments about a reference point p0 for all load cases. Considers: - FORCE, FORCE1, FORCE2 - MOMENT, MOMENT1, MOMENT2 - PLOAD, PLOAD2, PLOAD4 - LOAD Parameters ---------- model : BDF() a BDF object p0 : NUMPY.NDARRAY shape=(3,) or integer (node ID) the reference point loadcase_id : int the LOAD=ID to analyze cid : int; default=0 the coordinate system for the summation include_grav : bool; default=False includes gravity in the summation (not supported) xyz_cid0 : None / Dict[int] = (3, ) ndarray the nodes in the global coordinate system Returns ------- forces : NUMPY.NDARRAY shape=(3,) the forces moments : NUMPY.NDARRAY shape=(3,) the moments .. warning:: not full validated .. todo:: It's super slow for cid != 0. We can speed this up a lot if we calculate the normal, area, centroid based on precomputed node locations. Pressure acts in the normal direction per model/real/loads.bdf and loads.f06 """ if not isinstance(loadcase_id, integer_types): raise RuntimeError('loadcase_id must be an integer; loadcase_id=%r' % loadcase_id) p = _get_load_summation_point(model, p0, cid=0) loads, scale_factors, unused_is_grav = model.get_reduced_loads( loadcase_id, skip_scale_factor0=True) F = array([0., 0., 0.]) M = array([0., 0., 0.]) xyz = get_xyz_cid0_dict(model, xyz_cid0=xyz_cid0) unsupported_types = set() for load, scale in zip(loads, scale_factors): #if load.type not in ['FORCE1']: #continue if load.type == 'FORCE': if load.Cid() != 0: cp_ref = load.cid_ref #from pyNastran.bdf.bdf import CORD2R #cp_ref = CORD2R() f = load.mag * cp_ref.transform_vector_to_global(load.xyz) * scale else: f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'FORCE1': f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'FORCE2': f = load.mag * load.xyz * scale node = model.Node(load.node_id) r = xyz[node.nid] - p m = cross(r, f) F += f M += m elif load.type == 'MOMENT': if load.Cid() != 0: cp = load.cid_ref #from pyNastran.bdf.bdf import CORD2R #cp = CORD2R() m = load.mag * cp.transform_vector_to_global(load.xyz) * scale else: m = load.mag * load.xyz * scale M += m elif load.type == 'MOMENT1': m = load.mag * load.xyz * scale M += m elif load.type == 'MOMENT2': m = load.mag * load.xyz * scale M += m elif load.type == 'PLOAD': nodes = load.node_ids nnodes = len(nodes) if nnodes == 3: n1, n2, n3 = xyz[nodes[0]], xyz[nodes[1]], xyz[nodes[2]] axb = cross(n1 - n2, n1 - n3) centroid = (n1 + n2 + n3) / 3. elif nnodes == 4: n1, n2, n3, n4 = xyz[nodes[0]], xyz[nodes[1]], xyz[nodes[2]], xyz[nodes[3]] axb = cross(n1 - n3, n2 - n4) centroid = (n1 + n2 + n3 + n4) / 4. else: msg = 'invalid number of nodes on PLOAD card; nodes=%s' % str(nodes) raise RuntimeError(msg) area, normal = _get_area_normal(axb, nodes, xyz) r = centroid - p f = load.pressure * area * normal * scale m = cross(r, f) F += f M += m elif load.type == 'PLOAD1': _pload1_total(model, loadcase_id, load, scale, xyz, F, M, p) elif load.type == 'PLOAD2': pressure = load.pressure * scale for eid in load.element_ids: elem = model.elements[eid] if elem.type in ['CTRIA3', 'CQUAD4', 'CSHEAR', 'CQUADR', 'CTRIAR']: n = elem.Normal() area = elem.Area() f = pressure * n * area r = elem.Centroid() - p m = cross(r, f) F += f M += m else: model.log.warning('case=%s etype=%r loadtype=%r not supported' % ( loadcase_id, elem.type, load.type)) elif load.type == 'PLOAD4': _pload4_total(loadcase_id, load, scale, xyz, F, M, p) elif load.type == 'GRAV': if include_grav: # this will be super slow gravity = load.GravityVector() * scale for eid, elem in model.elements.items(): centroid = elem.Centroid() mass = elem.Mass() r = centroid - p f = mass * gravity m = cross(r, f) F += f M += m else: # we collect them so we only get one print unsupported_types.add(load.type) for load_type in unsupported_types: model.log.warning('case=%s loadtype=%r not supported' % (loadcase_id, load_type)) #forces, moments = sum_forces_moments(self, p0, loadcase_id, #include_grav=include_grav, xyz_cid0=xyz_cid0) if cid == 0: return F, M cid0 = 0 F2, M2 = transform_load(F, M, cid0, cid, model) return F2, M2
def sum_forces_moments_elements(self, p0: int, loadcase_id: int, eids: List[int], nids: List[int], cid: int=0, include_grav: bool=False, xyz_cid0: Union[None, Dict[int, NDArray3float]]=None, ) -> Tuple[NDArray3float, NDArray3float]: """ Sum the forces/moments based on a list of nodes and elements. Parameters ---------- p0 : int; (3,) ndarray the point to sum moments about type = int sum moments about the specified grid point type = (3, ) ndarray/list (e.g. [10., 20., 30]): the x, y, z location in the global frame loadcase_id : int the LOAD=ID to analyze eids : List[int] the list of elements to include (e.g. the loads due to a PLOAD4) nids : List[int] the list of nodes to include (e.g. the loads due to a FORCE card) cid : int; default=0 the coordinate system for the summation include_grav : bool; default=False includes gravity in the summation (not supported) xyz_cid0 : None / Dict[int] = (3, ) ndarray the nodes in the global coordinate system Returns ------- forces : NUMPY.NDARRAY shape=(3,) the forces moments : NUMPY.NDARRAY shape=(3,) the moments Nodal Types : FORCE, FORCE1, FORCE2, MOMENT, MOMENT1, MOMENT2, PLOAD Element Types: PLOAD1, PLOAD2, PLOAD4, GRAV If you have a CQUAD4 (eid=3) with a PLOAD4 (sid=3) and a FORCE card (nid=5) acting on it, you can incldue the PLOAD4, but not the FORCE card by using: For just pressure: .. code-block:: python eids = [3] nids = [] For just force: .. code-block:: python eids = [] nids = [5] or both: .. code-block:: python eids = [3] nids = [5] Notes ----- If you split the model into sections and sum the loads on each section, you may not get the same result as if you summed the loads on the total model. This is due to the fact that nodal loads on the boundary are double/triple/etc. counted depending on how many breaks you have. .. todo:: not done... """ self.deprecated( 'forces, moments = model.sum_forces_moments_elements(...)', 'from pyNastran.bdf.mesh_utils.loads import sum_forces_moments_elements\n' 'forces, moments = sum_forces_moments_elements(model, ...)', '1.3') forces, moments = sum_forces_moments_elements(self, p0, loadcase_id, eids, nids, include_grav=include_grav, xyz_cid0=xyz_cid0) if cid == 0: return forces, moments cid0 = 0 forces, moments = transform_load(forces, moments, cid0, cid, self) return forces, moments