FreeCAD_assembly3/system.py
Zheng, Lei 80bceefcc6 system: auto relax some redundant composit constraints
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
2018-03-24 12:12:22 +08:00

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