首页/文章/ 详情

ANSA二次开发:万向铰的创建

3月前浏览496
在整车碰撞模型的搭建过程中,在转向系统中涉及到万向铰的创建。采用手动创建如下:

创建过程步骤多,选点容易出错,多新手不友好还费时。
通过二次开发手段,通过工程师手动选取节点后,后续动作均全自动生成,不仅可大大提高效率还可以确保不出错。效果如下:

那如何实现的呢?源码如下:







from ansa import basefrom ansa import constantsfrom ansa import calcimport mathimport randomfrom typing import ListTupleUnion, Iterable, DictOptional


















































































class Vector(object):  @classmethod  def point2Point(cls,                  point1:Union[List[float], Tuple[float], base.Entity],                  point2:Union[List[float], Tuple[float], base.Entity],                  unit:bool=True)->List[float]:    """    计算两点构成的向量。    Args:      point1: 第一个点坐标,可以是列表、元组或 Entity 对象      point2: 第二个点坐标,可以是列表、元组或 Entity 对象      unit: 是否返回单位向量,默认为 True    Returns:      List[float]: 两点构成的向量,若 unit 为 True 则返回单位向量    """        if isinstance(point1, base.Entity):point1 = point1.position    if isinstance(point2, base.Entity):point2 = point2.position    vect:List[float] = [p2-p1 for p1, p2 in zip(point1, point2)]    return cls.unit(vect) if unit else vect  @staticmethod  def unit(vect:Union[List[float], Tuple[float]])->List[float]:    """    将一个向量单位化。    Args:      vect: 输入向量,可以是列表或元组    Returns:      List[float]: 单位向量    """    return calc.Normalize(vect)  @staticmethod  def distance(point1:Union[List[float], Tuple[float], base.Entity],                point2:Union[List[float], Tuple[float], base.Entity]                )->float:    """    计算两点的距离。    Args:      point1: 第一个点坐标,可以是列表、元组或 Entity 对象      point2: 第二个点坐标,可以是列表、元组或 Entity 对象    Returns:      float: 两点之间的距离    """    if isinstance(point1, base.Entity):point1 = point1.position    if isinstance(point2, base.Entity):point2 = point2.position    # 计算欧氏距离    return math.dist(point1, point2)  @staticmethod  def move(point:Union[List[float], Tuple[float], base.Entity],           vect:Union[List[float], Tuple[float]],           dist:float)->List[float]:    """    沿指定向量移动点。    Args:      point: 点坐标,可以是列表、元组或 Entity 对象      vect: 移动方向向量      dist: 移动距离    Returns:      List[float]: 移动后的点坐标    """    if isinstance(point, base.Entity):point = point.position    return list(map(lambda p, v: p+dist*v, point, vect))  @staticmethod  def getPointCenter(points:List[Union[List[float], Tuple[float], base.Entity]])->Tuple[float]:    """    计算输入点集 合的中心点。    Args:      points: 点集 合,可以是列表、元组或 Entity 对象    Returns:      Tuple[float]: 中心点坐标 (x, y, z)    """    if not points: return (0.00.00.0)        # 累计求和    xSum, ySum, zSum = 0.00.00.0    for point in points:      if isinstance(point, base.Entity): point = point.position      xSum += point[0]      ySum += point[1]      zSum += point[2]    count:int = len(points)        return (xSum/count, ySum/count, zSum/count)










































































































































































class JointBase(object):  """  关节基础类。  定义关节的通用属性和行为。  """  DECK:int = constants.LSDYNA  def __init__(self, entType:str, nameCN:str=None):    """    初始化关节基础类。    Args:      entType: 实体类型名称      nameCN: 中文名称(可选)    """    self.entType:str = entType    self.nameCN:str = nameCN    self.masterNodes:List[base.Entity] = []    if nameCN: print(f'[INFO] 初始化关节类型:{self.nameCN}')  def getMasterNodes(self):    """    获取主节点列表。    Returns:      List[base.Entity]: 主节点列表    """    ...  @classmethod  def createRbe2(cls, nodes:List[base.Entity], masterNode:base.Entity=None)->base.Entity:    """    创建 RBE2 刚性连接。    Args:      nodes: 节点列表      masterNode: 主节点,若为 None 则自动创建位于重心处的节点    Returns:      base.Entity: 创建的 CONSTRAINED_NODAL_RIGID_BODY 实体    """    print('[INFO]创建RBE2')    setEnt:base.Entity = base.CreateEntity(cls.DECK, 'SET')    base.AddToSet(setEnt, nodes)    setEnt.set_entity_values(cls.DECK, {'Name'f'SET_NODE_{setEnt._id:010d}'})    if not masterNode:      cog = base.cog(setEnt)      masterNode = base.CreateEntity(cls.DECK, 'NODE', {'X': cog[0], 'Y': cog[1], 'Z':cog[2]})    base.AddToSet(setEnt, masterNode)    fields = {'NSID':setEnt._id'MASTER':masterNode._id'No.of.Nodes':len(nodes)+1}    return base.CreateEntity(cls.DECK, 'CONSTRAINED_NODAL_RIGID_BODY', fields)class UniversalJoint(JointBase):  """  万向铰关节类。  用于创建万向铰类型的连接。  """  def __init__(self):    """    初始化万向铰关节。    """    super().__init__('CONSTRAINED_JOINT_UNIVERSAL', nameCN='万向铰')    self.pickTypes:Tuple[str] = ('ELEMENT_SHELL''ELEMENT_SOLID')    self.pickNodeResultFlag:int = 0      def getNodesBy2Axis(self):    """    获取两轴上的节点。    通过用户交互选择两个轴上的所有节点,并提取端点。    """    print(f'[INFO] 请选择{self.nameCN}第一轴上的所有节点')    axis1Nodes:List[base.Entity] = base.PickNodes(self.DECK, self.pickTypes)    if not axis1Nodes:      print(f'[WARNING] 未选择{self.nameCN}第一轴上的任何节点')      self.pickNodeResultFlag = 1      return None    print(f'[INFO] 请选择{self.nameCN}第二轴上的所有节点')    axis2Nodes:List[base.Entity] = base.PickNodes(self.DECK, self.pickTypes)    if not axis2Nodes:      print(f'[WARNING] 未选择{self.nameCN}第二轴上的任何节点')      self.pickNodeResultFlag = 2      return None        self.axis1Node1, self.axis1Node2, _ = self.getVertixByAxis(axis1Nodes)    if not self.axis1Node1 or not self.axis1Node2:      print(f'[WARNING] 未选择{self.nameCN}第一轴上的一端点未被选中')      self.pickNodeResultFlag = 3      return None        self.axis2Node1, self.axis2Node2, _ = self.getVertixByAxis(axis2Nodes)    if not self.axis2Node1 or not self.axis2Node2:      print(f'[WARNING] 未选择{self.nameCN}第二轴上的一端点未被选中')      self.pickNodeResultFlag = 4      return None        self.pickNodeResultFlag = 0    return None      @classmethod  def getVertixByAxis(cls, nodes:List[base.Entity])->Tuple[List[base.Entity], List[base.Entity], bool]:    """    根据轴上的节点获取两个端点簇。    Args:      nodes: 轴上的节点列表    Returns:      Tuple[List, List, bool]: (簇1点列表, 簇2点列表, 是否只有一个簇)    """    obj = ClusterPoints(nodes)    return obj.run()  @classmethod  def createCenterNode(cls, nodes:List[base.Entity])->base.Entity:    """    创建位于节点集 合重心处的节点。    Args:      nodes: 节点列表    Returns:      base.Entity: 创建的节点实体    """    cog:Tuple[float] = Vector.getPointCenter(nodes)    return base.CreateEntity(cls.DECK, 'NODE', {'X': cog[0], 'Y': cog[1],'Z': cog[2]})  @classmethod  def getNode4(cls, node1:base.Entity, node2:base.Entity, node3:base.Entity)->base.Entity:    """    计算并创建第四个节点。    通过向量运算确定第四个节点的位置,用于构建万向铰。    算法原理:      1. vect1 = node1 → node2:确定第一轴方向      2. vect2 = node1 → node3:确定第二轴的参考方向      3. vect3 = vect1 × vect2:叉积得到垂直于两向量所在平面的法向量      4. vect4 = vect1 × (vect3/|vect3|):再次叉积,得到同时垂直于第一轴和法向量的向量,即第二轴方向      5. dist = |node1 - node3|:计算参考距离      6. node4 = node1 + dist × vect4:沿第二轴方向移动参考距离得到第四个节点    Args:      node1: 原点节点      node2: 定义第一轴方向的节点      node3: 定义第二轴参考方向的节点    Returns:      base.Entity: 创建的第四个节点实体    """    vect1:Tuple[float] = Vector.point2Point(node1, node2)    vect2:Tuple[float] = Vector.point2Point(node1, node3)    vect3:Tuple[float] = calc.CrossProduct(vect1, vect2)    vect4:Tuple[float] = Vector.unit(calc.CrossProduct(vect1, Vector.unit(vect3)))    dist:float = Vector.distance(node1, node3)    pos:List[float] = Vector.move(node1, vect4, dist)    return base.CreateEntity(cls.DECK, 'NODE', {'X': pos[0], 'Y': pos[1],'Z': pos[2]})      def run(self, pickFlag:int=2)->None:    """    执行万向铰创建流程。    Args:      pickFlag: 选择模式标志,默认为2    """    if pickFlag==2:      self.getNodesBy2Axis()      node3:base.Entity = self.createCenterNode(self.axis1Node1)      axis1MasterPos2:Tuple[float] = Vector.getPointCenter(self.axis1Node2)      axis2MasterPos1:Tuple[float] = Vector.getPointCenter(self.axis2Node1)      axis2MasterPos2:Tuple[float] = Vector.getPointCenter(self.axis2Node2)      cog:Tuple[float] = Vector.getPointCenter([node3, axis1MasterPos2, axis2MasterPos1, axis2MasterPos2])      node1:base.Entity = base.CreateEntity(self.DECK, 'NODE', {'X': cog[0], 'Y': cog[1],'Z': cog[2]})      node2:base.Entity = base.CreateEntity(self.DECK, 'NODE', {'X': cog[0], 'Y': cog[1],'Z': cog[2]})      node4:base.Entity = self.getNode4(node1, node3, axis2MasterPos1)      rbe1:base.Entity = self.createRbe2(self.axis1Node1+self.axis1Node2+[node3], node1)      rbe2:base.Entity = self.createRbe2(self.axis2Node1+self.axis2Node2+[node4], node2)      fields:Dict[strany] = {'N1': node1, 'N2': node2, 'N3': node3, 'N4': node4}      return base.CreateEntity(self.DECK, self.entType, fields)def main():  """  主函数。  创建并运行万向铰。  """  jointObj = UniversalJoint()  jointObj.run()
  if __name__ == '__main__':  main()













































































































































































































































































































































































































class ClusterPoints(object):  """  基于点的聚类分析工具。  使用二分K-Means算法将一组点分成两个簇。  """  def __init__(self,               points:Iterable[base.Entity],               singleClusterThrehold:float = 7.5,               maxIterations:int=20               ):    """    初始化聚类分析器。    Args:      points: 输入的实体点集 合      singleClusterThrehold: 簇间最小间隙阈值,用于判断是否为同一簇      maxIterations: 最大迭代次数    """    self.inputValidatedCheck(points)    self.singleClusterThrehold = singleClusterThrehold    self.maxIterations = maxIterations        self.distCache:Dict[Tuple[intint], float] = {} # 距离缓存,避免重复计算  def inputValidatedCheck(self, points:Iterable[base.Entity])->None:    """    输入验证和标准化。    Args:      points: 输入的点集 合    Raises:      TypeError: 输入不是列表或元组时抛出      ValueError: 点数量少于2时抛出    """    # 输入验证    if not isinstance(points, listand not isinstance(points, tuple):      raise TypeError('输入必须是一个列表或元组')        if len(points)<2:      raise ValueError('至少需要两个点才能进行聚类分析')        # 验证并标准化点    self.points:List[base.Entity] = [point for point in points if isinstance(point, base.Entity)]    self.numOfPoints:int = len(self.points)        return None  def getDistance(self, idx1:int, idx2:int)->float:    """    获取或计算两点间的距离。    Args:      idx1: 第一个点的索引      idx2: 第二个点的索引    Returns:      float: 两点之间的距离    """    if idx1==idx2:      return 0.0        # 确保索引顺序一致    if idx1>idx2:       idx1, idx2 = idx2, idx1        key = (idx1, idx2)    try:      dist = self.distCache[key]    except KeyError:      dist = math.dist(self.points[idx1].position, self.points[idx2].position)      self.distCache[key] = dist        return dist  def calcCog(self, indices:Iterable[int])->Optional[Tuple[float]]:    """    快速计算输入下标对应节点构成的簇的中心点。    Args:      indices: 节点索引集 合    Returns:      Optional[Tuple[float]]: 中心点坐标 (x, y, z),若索引为空则返回 None    """    if not indices: return None        # 累计求和    xSum, ySum, zSum = 0.00.00.0    for idx in indices:      pos = self.points[idx].position      xSum += pos[0]      ySum += pos[1]      zSum += pos[2]    count:int = len(indices)        return (xSum/count, ySum/count, zSum/count)  def calcDistToCenter(self,                       center1:Tuple[float],                       center2:Tuple[float],                       )->Tuple[List[float]]:    """    批量计算所有点到输入的名义中心点的距离。    Args:      center1: 第一个中心点坐标      center2: 第二个中心点坐标    Returns:      Tuple[List[float]]: (到center1的距离列表, 到center2的距离列表)    """    distancesToCenter1:List[float] = []    distancesToCenter2:List[float] = []        # 预计算中心点到所有点的距离    # 注意:这里需要计算欧式距离,不能使用缓存    for idx in range(self.numOfPoints):      if self.farthestPointIdx1==idx:        distancesToCenter1.append(0.0)        distancesToCenter2.append(math.dist(self.points[idx].position, center2))      elif self.farthestPointIdx2==idx:        distancesToCenter1.append(math.dist(self.points[idx].position, center1))        distancesToCenter2.append(0.0)      else:        distancesToCenter1.append(math.dist(self.points[idx].position, center1))        distancesToCenter2.append(math.dist(self.points[idx].position, center2))        return (distancesToCenter1, distancesToCenter2)  def calcClusterStats(self, indices:List[int])->Tuple[float]:    '''    快速计算簇的统计信息:平均距离、最小距离、最大距离        Args:      indices: 簇中点的索引        Return:      (平均距离, 最小距离, 最大距离)    '''        numOfIndices:int = len(indices)    if numOfIndices<2:      return 0.00.00.0        totalDist, minDist, maxDist = 0.0, math.inf, 0.0    count:int = 0        # 只计算部分点对,近似估计    if numOfIndices<=50:      # 小簇,计算所有点对      for i, i_idx in enumerate(indices):        for j in range(i+1, numOfIndices):          j_idx = indices[j]          dist = self.getDistance(i_idx, j_idx)          totalDist += dist          minDist = min(minDist, dist)          maxDist = max(maxDist, dist)          count += 1    else:      # 大簇,采样估计      sampleSize:int = min(500, numOfIndices*(numOfIndices-1)//2)      for _ in range(sampleSize):        i = random.choice(indices)        j = random.choice(indices)        while i==j: j=random.choice(indices)                dist = self.getDistance(i, j)        totalDist += dist        minDist = min(minDist, dist)        maxDist = max(maxDist, dist)        count += 1        return (totalDist/count if count>0 else 0.0,            minDist if minDist<math.inf else 0.0,            maxDist)  def findFarthestPointsByBest(self)->Tuple[int]:    """    全局遍历获取距离最远的两个点。    Returns:      Tuple[int]: 距离最远两个点的索引    """    maxDistance:float = -1.0    point1Idx, point2Idx = 01        for i in range(self.numOfPoints):      for j in range(i+1self.numOfPoints):        dist = self.getDistance(i, j)        if dist>maxDistance:          maxDistance = dist          point1Idx, point2Idx = i, j    return point1Idx, point2Idx  def findFarthestPointToPoint(self, startIdx:int)->int:    """    找到离起点最远的点。    Args:      startIdx: 起始点索引    Returns:      int: 距离起始点最远的点索引    """    farthestFromStart:int = startIdx    maxDist:float = -1.0        for i in range(self.numOfPoints):      if i==startIdx:continue      dist = self.getDistance(startIdx, i)      if dist>maxDist:        maxDist = dist        farthestFromStart = i    return farthestFromStart  def findFarthestPoints(self)->Tuple[int]:    """    使用随机采样和三角形不等式找到近似最远的两个点。    算法原理:      1. 随机选择一个点作为起点      2. 找到离起点最远的点作为第一个候选点      3. 找到离第一个候选点最远的点作为第二个候选点      4. 重复几次找到更好的近似    Returns:      Tuple[int]: 距离最远的两个点的索引    """    if self.numOfPoints<2:      return (00)        # 如果点数较少,使用简单方法    if self.numOfPoints<=100:      return self.findFarthestPointsByBest()        # 对于大数据集,使用近似方法    bestDist:float = -1.0    bestPair:Tuple[intint] = (01)        # 进行几次随机采样    for _ in range(min(5self.numOfPoints//10)):      # 随机选择一个起点      startIdx:int = random.randint(0self.numOfPoints-1)            # 找到离起始点最远的点      farthestFromStart:int = self.findFarthestPointToPoint(startIdx)            # 找到离最远点最远的点      farthestFromFarthest:int = self.findFarthestPointToPoint(farthestFromStart)            # 计算这对点的距离      dist = self.getDistance(farthestFromStart, farthestFromFarthest)      if dist>bestDist:        bestDist = dist        bestPair = (farthestFromStart, farthestFromFarthest)        return bestPair      def initCluster(self):    """    初始化簇分配。    将点分配给距离最近的最远点对应的簇。    """    # 初始化簇分配    self.cluster1Indices = [self.farthestPointIdx1]    self.cluster2Indices = [self.farthestPointIdx2]        # 为剩余点分配初始簇    for idx in range(self.numOfPoints):      if idx == self.farthestPointIdx1 or idx==self.farthestPointIdx2:continue            dist1 = self.getDistance(idx, self.farthestPointIdx1)      dist2 = self.getDistance(idx, self.farthestPointIdx2)            if dist1<dist2:        self.cluster1Indices.append(idx)      else:        self.cluster2Indices.append(idx)    return None  def iterOpti(self):    """    使用优化后的迭代策略进行簇分配优化。    迭代计算中心点并将点重新分配到最近的簇,直到收敛或达到最大迭代次数。    """    # 使用优化后的迭代策略    for _ in range(self.maxIterations):      # 计算当前簇的中心点      center1 = self.calcCog(self.cluster1Indices)      center2 = self.calcCog(self.cluster2Indices)            # 如果某个簇为空,重新初始化      if center1 is None or center2 is None:        self.farthestPointIdx1, self.farthestPointIdx2 = self.findFarthestPoints()        self.cluster1Indices = [self.farthestPointIdx1]        self.cluster2Indices = [self.farthestPointIdx2]        continue            # 批量计算到中心点的距离      distanceToCenter1, distanceToCenter2 = self.calcDistToCenter(center1, center2)            # 根据与中心点的距离进行重新分配点到最近的簇      cluster1Indices = [self.farthestPointIdx1]      cluster2Indices = [self.farthestPointIdx2]            for i in range(self.numOfPoints):        if i==self.farthestPointIdx1 or i==self.farthestPointIdx2:continue        if distanceToCenter1[i]<distanceToCenter2[i]:          cluster1Indices.append(i)        else:          cluster2Indices.append(i)            # 检查是否收敛      if (set(self.cluster1Indices)==set(cluster1Indices)) and (set(self.cluster2Indices)==set(cluster2Indices)):        break            self.cluster1Indices = cluster1Indices      self.cluster2Indices = cluster2Indices    return None  def rebuildResult(self):    """    构建返回结果。    根据索引构建对应的实体点列表,确保两个簇都不为空。    """    self.cluster1Points = [self.points[i] for i in self.cluster1Indices]    self.cluster2Points = [self.points[i] for i in self.cluster2Indices]        # 确保两个簇都不为空    if not self.cluster1Points and self.cluster2Points:      self.cluster1Points.append(self.cluster2Points.pop())    elif self.cluster1Points and not self.cluster2Points:      self.cluster2Points.append(self.cluster1Points.pop())        return None  def isOneCluster(self):    """    判断是否只有一个簇。    根据簇内平均距离和簇间平均距离判断两个簇是否应该合并为一个簇。    Returns:      Tuple[List, List, bool]: (所有点, 空列表, True) 如果是一个簇,否则返回 None    """    # 使用快速方法判断是否只有一个簇    if self.numOfPoints<=50:      # 小数据集:详细计算      avgDist1 = self.calcClusterStats(self.cluster1Indices)[0]      avgDist2 = self.calcClusterStats(self.cluster2Indices)[0]            # 计算簇间平均距离      interClusterAvg = 0.0      if self.cluster1Points and self.cluster2Points:        # 采样计算簇间距离        sampleSize:int = min(100len(self.cluster1Indices)*len(self.cluster2Indices))        for _ in range(sampleSize):          i = random.choice(self.cluster1Indices)          j = random.choice(self.cluster2Indices)                    interClusterAvg += self.getDistance(i, j)        interClusterAvg /= sampleSize                # 判断逻辑        # 如果簇间平局距离小于簇内平均距离则认为是一个簇        intraClusterAvg = max(avgDist1, avgDist2)        if interClusterAvg<intraClusterAvg:          allPoints = self.cluster1Points + self.cluster2Points          return allPoints, [], True    else:      # 大数据集:简化判断      # 计算中心点距离      center1 = self.calcCog(self.cluster1Indices)      center2 = self.calcCog(self.cluster2Indices)      if center1 and center2:        centerDist = math.dist(center1, center2)              # 计算簇的大小        size1 = len(self.cluster1Indices)        size2 = len(self.cluster2Indices)                # 如果簇大小悬殊且中心点距离不大,可能是一个簇        if (max(size1, size2)) > 3*min(size1, size2) and centerDist<self.singleClusterThrehold:          allPoints = self.cluster1Points + self.cluster2Points          return allPoints, [], True        return None  def run(self):    """    执行聚类分析。    流程:      1. 找到近似最远的两个点作为初始种子点      2. 初始化簇分配      3. 迭代优化簇分配      4. 构建返回结果      5. 判断是否只有一个簇    Returns:      Tuple[List, List, bool]: (簇1点列表, 簇2点列表, 是否只有一个簇)    """    # 特殊情况处理    if self.numOfPoints==2:      dist = self.getDistance(01)      if dist<self.singleClusterThrehold:        return [self.points[0], self.points[1]], [], True      else:        return [self.points[0]], [self.points[1]], False        # 步骤1:使用优化方法找到近似最远的两个点    self.farthestPointIdx1, self.farthestPointIdx2 = self.findFarthestPoints()        # 步骤2:初始化簇分配    self.initCluster()        # 步骤3:使用优化后的迭代策略    self.iterOpti()        # 步骤4:构建返回结果    self.rebuildResult()        # 步骤5:判断是否只有一个簇    result = self.isOneCluster()        return result if result else (self.cluster1Points, self.cluster2Points, False)
未经作者同意,不得转载该文!!!


来源:檐苔
碰撞二次开发通用ANSAUMGID
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2026-05-27
最近编辑:3月前
青瓦松
硕士 签名征集中
获赞 46粉丝 12文章 63课程 0
点赞
收藏
作者推荐

ANSA二次开发:线束单元创建(1)

随着碰撞工况逐渐严苛及电车的普及,电安全愈发重要。在碰撞仿真过程中,对线束的考察也越来越普遍。笔者将分两篇文章介绍利用ANSA自有方案和针对自有方案的不足开发的二次插件介绍线束单元的创建方法。在碰撞仿真分析中,常采用双层实体(solid)单元或beam-RB2-shell单元来模拟线束单元。本文先介绍ANSA的两种自有方法。 方法一:分步创建采用该方法步骤繁琐,针对整车线束建模过程中将导致建模效率极低。 方法二:ANSA自有插件(Tubes/Wires Mesh Generator)采用该方法可同时处理对多条线束。从而大大提高建模速度。但是,在实际使用过程中,该方法也存在如下短板:1、若一条线束在CAD建模过程中在卡扣处被分为多段,如下图所示,利用上述工具将创建3组不同的单元,且不共节点,后续还需投入大量的人力进行修改。 2、根据不同的线束径向尺寸,将采用不同的建模方式进行建模。例如:当线束直径大于5时,采用双层实体(solid)单元;否则采用beam-RB2-shell单元进行建模。则需要建模工程师花费大量时间进行相关测量分类,然后再进行建模。来源:檐苔

未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈