
The order of appearance of the constraints determines how redundancies are handled. PlaneCoincident: the second PlaneCoincident of the same pair or parts is relax to a 2d point horizontal or vertical constraint. The third and onwards are ignored. Any PlaneCoincident constraint will be ignored if there are any existing PlaneCoincident or AxialAlignment. PlaneAlignment: the second one is relaxed to two PointInPlane or PointPlaneDistance constraint. The third one is relaxed to one PointInPlane or PointPlaneDistance. The forth and onwards are ignored. Any PlaneCoincident constraint will be ignored if there are any existing PlaneCoincident or AxialAlignment. AxialAlignment: ignored if there are any existing PlaneAlignment or PlaneCoincident constraints
283 lines
9.3 KiB
Python
283 lines
9.3 KiB
Python
import os
|
|
from .constraint import cstrName
|
|
from .utils import getIcon, syslogger as logger, objName, project2D
|
|
from .proxy import ProxyType, PropertyInfo
|
|
|
|
class System(ProxyType):
|
|
'solver system meta class'
|
|
|
|
_typeID = '_SolverType'
|
|
_typeEnum = 'SolverType'
|
|
_propGroup = 'Solver'
|
|
_iconName = 'Assembly_Assembly_Tree.svg'
|
|
|
|
@classmethod
|
|
def setDefaultTypeID(mcs,obj,name=None):
|
|
if not name:
|
|
info = mcs.getInfo()
|
|
idx = 1 if len(info.TypeNames)>1 else 0
|
|
name = info.TypeNames[idx]
|
|
super(System,mcs).setDefaultTypeID(obj,name)
|
|
|
|
@classmethod
|
|
def getIcon(mcs,obj):
|
|
func = getattr(mcs.getProxy(obj),'getIcon',None)
|
|
if func:
|
|
icon = func(obj)
|
|
if icon:
|
|
return icon
|
|
return getIcon(mcs,mcs.isDisabled(obj))
|
|
|
|
@classmethod
|
|
def isDisabled(mcs,obj):
|
|
proxy = mcs.getProxy(obj)
|
|
return not proxy or proxy.isDisabled(obj)
|
|
|
|
@classmethod
|
|
def isTouched(mcs,obj):
|
|
proxy = mcs.getProxy(obj)
|
|
return proxy and proxy.isTouched(obj)
|
|
|
|
@classmethod
|
|
def touch(mcs,obj,touched=True):
|
|
proxy = mcs.getProxy(obj)
|
|
if proxy:
|
|
proxy.touch(obj,touched)
|
|
|
|
@classmethod
|
|
def onChanged(mcs,obj,prop):
|
|
proxy = mcs.getProxy(obj)
|
|
if proxy:
|
|
proxy.onChanged(obj,prop)
|
|
if super(System,mcs).onChanged(obj,prop):
|
|
obj.Proxy.onSolverChanged()
|
|
|
|
@classmethod
|
|
def getSystem(mcs,obj):
|
|
proxy = mcs.getProxy(obj)
|
|
if proxy:
|
|
return proxy.getSystem(obj)
|
|
|
|
@classmethod
|
|
def isConstraintSupported(mcs,obj,name):
|
|
if name == 'Locked':
|
|
return True
|
|
proxy = mcs.getProxy(obj)
|
|
if proxy:
|
|
return proxy.isConstraintSupported(name)
|
|
|
|
def _makePropInfo(name,tp,doc=''):
|
|
PropertyInfo(System,name,tp,doc,group='Solver')
|
|
|
|
_makePropInfo('Verbose','App::PropertyBool')
|
|
|
|
class SystemBase(object):
|
|
__metaclass__ = System
|
|
_id = 0
|
|
_props = ['Verbose']
|
|
|
|
def __init__(self,obj):
|
|
self._touched = True
|
|
self.verbose = obj.Verbose
|
|
self.log = logger.info if self.verbose else logger.debug
|
|
super(SystemBase,self).__init__()
|
|
|
|
@classmethod
|
|
def getPropertyInfoList(cls):
|
|
return cls._props
|
|
|
|
@classmethod
|
|
def getName(cls):
|
|
return 'None'
|
|
|
|
def isConstraintSupported(self,_cstrName):
|
|
return True
|
|
|
|
def isDisabled(self,_obj):
|
|
return True
|
|
|
|
def isTouched(self,_obj):
|
|
return getattr(self,'_touched',True)
|
|
|
|
def touch(self,_obj,touched=True):
|
|
self._touched = touched
|
|
|
|
def onChanged(self,obj,prop):
|
|
if prop == 'Verbose':
|
|
self.verbose = obj.Verbose
|
|
self.log = logger.info if obj.Verbose else logger.debug
|
|
|
|
|
|
class SystemExtension(object):
|
|
def __init__(self):
|
|
super(SystemExtension,self).__init__()
|
|
self.NameTag = ''
|
|
self.sketchPlane = None
|
|
self.cstrObj = None
|
|
self.firstInfo = None
|
|
self.secondInfo = None
|
|
|
|
def checkRedundancy(self,obj,firstInfo,secondInfo):
|
|
self.cstrObj,self.firstInfo,self.secondInfo=obj,firstInfo,secondInfo
|
|
|
|
def addSketchPlane(self,*args,**kargs):
|
|
_ = kargs
|
|
self.sketchPlane = args[0] if args else None
|
|
return self.sketchPlane
|
|
|
|
def setOrientation(self,h,lockAngle,angle,n1,n2,nx1,nx2,group):
|
|
if lockAngle and not angle:
|
|
h.append(self.addSameOrientation(n1,n2,group=group))
|
|
else:
|
|
h.append(self.addParallel(n1,n2,group=group))
|
|
if lockAngle:
|
|
h.append(self.addAngle(angle,False,nx1,nx2,group=group))
|
|
return h
|
|
|
|
def reportRedundancy(self,warn=False):
|
|
msg = '{} between {} and {}'.format(cstrName(self.cstrObj),
|
|
self.firstInfo.PartName, self.secondInfo.PartName)
|
|
if warn:
|
|
logger.warn('skip redundant ' + msg)
|
|
else:
|
|
logger.info('auto relax ' + msg)
|
|
|
|
def countConstraints(self,increment,count,*names):
|
|
first,second = self.firstInfo,self.secondInfo
|
|
if not first or not second:
|
|
return
|
|
ret = 0
|
|
for name in names:
|
|
cstrs = first.CstrMap.get(second.Part,{}).get(name,None)
|
|
if not cstrs:
|
|
if increment:
|
|
cstrs = second.CstrMap.setdefault(
|
|
first.Part,{}).setdefault(name,[])
|
|
else:
|
|
cstrs = second.CstrMap.get(first.Part,{}).get(name,[])
|
|
if increment:
|
|
cstrs += [None]*increment
|
|
ret += len(cstrs)
|
|
if ret >= count:
|
|
if ret>count:
|
|
self.reportRedundancy(True)
|
|
return -1
|
|
else:
|
|
self.reportRedundancy()
|
|
return ret
|
|
|
|
def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
|
|
if not group:
|
|
group = self.GroupHandle
|
|
w1,p1,n1 = e1[:3]
|
|
_,p2,n2 = e2[:3]
|
|
n1,nx1 = n1[:2]
|
|
n2,nx2 = n2[:2]
|
|
h = []
|
|
count = self.countConstraints(0,1,'Alignment','Axial')
|
|
if count<0:
|
|
# We do not allow plane coincident coexist with other composite
|
|
# constraints
|
|
return
|
|
count = self.countConstraints(2 if lockAngle else 1,2,'Coincident')
|
|
if count<0:
|
|
return
|
|
if not lockAngle and count==2:
|
|
# if there is already some other plane coincident constraint set for
|
|
# this pair of parts, we reduce this second constraint to either a
|
|
# points horizontal or vertical constraint, i.e. reduce the
|
|
# constraining DOF down to 1.
|
|
#
|
|
# We project the initial points to the first element plane, and
|
|
# check for differences in x and y components of the points to
|
|
# determine whether to use horizontal or vertical constraint.
|
|
v1,v2 = project2D(self.firstInfo.EntityMap[n1][0],
|
|
self.firstInfo.EntityMap[p1][0],
|
|
self.secondInfo.EntityMap[p2][0])
|
|
if abs(v1.x-v2.x) < abs(v1.y-v2.y):
|
|
h.append(self.addPointsHorizontal(p1,p2,w1,group=group))
|
|
else:
|
|
h.append(self.addPointsVertical(p1,p2,w1,group=group))
|
|
return h
|
|
if d:
|
|
h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
|
|
h.append(self.addPointsCoincident(p1,p2,w1,group=group))
|
|
else:
|
|
h.append(self.addPointsCoincident(p1,p2,group=group))
|
|
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group)
|
|
|
|
def addPlaneAlignment(self,d,lockAngle,angle,e1,e2,group=0):
|
|
if not group:
|
|
group = self.GroupHandle
|
|
w1,_,n1 = e1[:4]
|
|
_,p2,n2 = e2[:4]
|
|
n1,nx1 = n1[:2]
|
|
n2,nx2,px2 = n2[:3]
|
|
h = []
|
|
count = self.countConstraints(0,1,'Coincident','Axial')
|
|
if count<0:
|
|
return
|
|
count = self.countConstraints(2 if lockAngle else 1,3,'Alignment')
|
|
if count<0:
|
|
return
|
|
if d:
|
|
h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
|
|
else:
|
|
h.append(self.addPointInPlane(p2,w1,group=group))
|
|
if count==1 or (lockAngle and count==2):
|
|
h.append(self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group))
|
|
elif count==2:
|
|
self.reportRedundancy()
|
|
if d:
|
|
h.append(self.addPointPlaneDistance(d,px2,w1,group=group))
|
|
else:
|
|
h.append(self.addPointInPlane(px2,w1,group=group))
|
|
return h
|
|
|
|
def addAxialAlignment(self,lockAngle,angle,e1,e2,group=0):
|
|
if not group:
|
|
group = self.GroupHandle
|
|
count = self.countConstraints(0,1,'Coincident','Alignment')
|
|
if count<0:
|
|
return
|
|
w1,p1,n1 = e1[:3]
|
|
_,p2,n2 = e2[:3]
|
|
n1,nx1 = n1[:2]
|
|
n2,nx2 = n2[:2]
|
|
h = []
|
|
h.append(self.addPointsCoincident(p1,p2,w1,group=group))
|
|
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group)
|
|
|
|
def addMultiParallel(self,lockAngle,angle,e1,e2,group=0):
|
|
h = []
|
|
isPlane = isinstance(e1,list),isinstance(e2,list)
|
|
if all(isPlane):
|
|
return self.setOrientation(
|
|
h,lockAngle,angle,e1[2],e2[2],e1[3],e2[3],group);
|
|
if not any(isPlane):
|
|
h.append(self.addParallel(e1,e2,group=group))
|
|
elif isPlane[0]:
|
|
h.append(self.addPerpendicular(e1[2],e2,group=group))
|
|
else:
|
|
h.append(self.addPerpendicular(e1,e2[2],group=group))
|
|
return h
|
|
|
|
def addColinear(self,e1,e2,wrkpln=0,group=0):
|
|
h = []
|
|
h.append(self.addParallel(e1[0],e2,wrkpln=wrkpln,group=group))
|
|
h.append(self.addPointOnLine(e1[1],e2,wrkpln=wrkpln,group=group))
|
|
return h
|
|
|
|
def addPlacement(self,pla,group=0):
|
|
q = pla.Rotation.Q
|
|
base = pla.Base
|
|
nameTagSave = self.NameTag
|
|
nameTag = nameTagSave+'.' if nameTagSave else 'pla.'
|
|
ret = []
|
|
for n,v in (('x',base.x),('y',base.y),('z',base.z),
|
|
('qw',q[3]),('qx',q[0]),('qy',q[1]),('qz',q[2])):
|
|
self.NameTag = nameTag+n
|
|
ret.append(self.addParamV(v,group))
|
|
self.NameTag = nameTagSave
|
|
return ret
|