solver: improve redundancy checking of implicit constraints
Related #403
This commit is contained in:
parent
e785510c68
commit
bc2d5d611f
|
@ -1175,7 +1175,8 @@ class BaseMulti(Base):
|
|||
e = cls._entityDef[0](
|
||||
solver,partInfo,info.Subname,info.Shape)
|
||||
params = props + [e0,e]
|
||||
solver.system.checkRedundancy(obj,partInfo0,partInfo)
|
||||
solver.system.checkRedundancy(
|
||||
obj,partInfo0,partInfo,info0.SubnameRef,info.SubnameRef)
|
||||
h = func(*params,group=solver.group)
|
||||
if isinstance(h,(list,tuple)):
|
||||
ret += list(h)
|
||||
|
@ -1228,16 +1229,19 @@ class BaseMulti(Base):
|
|||
if i==idx0:
|
||||
e0 = cls._entityDef[idx0](
|
||||
solver,partInfo,info.Subname,info.Shape)
|
||||
subname0 = info.SubnameRef
|
||||
info0 = partInfo
|
||||
else:
|
||||
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
|
||||
if e0 and e:
|
||||
if idx0:
|
||||
params = props + [e,e0]
|
||||
solver.system.checkRedundancy(obj,partInfo,info0)
|
||||
solver.system.checkRedundancy(
|
||||
obj,partInfo,info0,info.SubnameRef,subname0)
|
||||
else:
|
||||
params = props + [e0,e]
|
||||
solver.system.checkRedundancy(obj,info0,partInfo)
|
||||
solver.system.checkRedundancy(
|
||||
obj,info0,partInfo,subname0,info.SubnameRef)
|
||||
h = func(*params,group=solver.group)
|
||||
if isinstance(h,(list,tuple)):
|
||||
ret += list(h)
|
||||
|
@ -1270,7 +1274,8 @@ class BaseCascade(BaseMulti):
|
|||
params = props + [e1,e2]
|
||||
else:
|
||||
params = props + [e2,e1]
|
||||
solver.system.checkRedundancy(obj,prevInfo,partInfo)
|
||||
solver.system.checkRedundancy(
|
||||
obj,prevInfo,partInfo,prev.SubnameRef,info.SubnameRef)
|
||||
h = func(*params,group=solver.group)
|
||||
if isinstance(h,(list,tuple)):
|
||||
ret += list(h)
|
||||
|
|
|
@ -18,15 +18,12 @@ from .system import System
|
|||
# plane of the part.
|
||||
# EntityMap: string -> entity handle map, for caching
|
||||
# Group: transforming entity group handle
|
||||
# CstrMap: map from other part to the constrain between this and the other part.
|
||||
# This is for auto constraint DOF reduction. Only some composite
|
||||
# constraints will be mapped.
|
||||
# Update: in case the constraint uses the `Multiplication` feature, only the
|
||||
# first element of all the coplanar edges will be actually constrainted.
|
||||
# The rest ElementInfo will be stored here for later update by matrix
|
||||
# transformation.
|
||||
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
|
||||
'Params','Workplane','EntityMap','Group','CstrMap','Update'))
|
||||
'Params','Workplane','EntityMap','Group','Update'))
|
||||
|
||||
class Solver(object):
|
||||
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
|
||||
|
@ -315,7 +312,6 @@ class Solver(object):
|
|||
Workplane = h,
|
||||
EntityMap = {},
|
||||
Group = group if group else g,
|
||||
CstrMap = {},
|
||||
Update = [])
|
||||
|
||||
self.system.log('{}, {}',partInfo,g)
|
||||
|
|
|
@ -115,6 +115,14 @@ class SystemBase(with_metaclass(System, object)):
|
|||
self.verbose = obj.Verbose
|
||||
self.log = logger.info if obj.Verbose else logger.debug
|
||||
|
||||
def _cstrKey(cstrType, firstPart, secondPart):
|
||||
if firstPart > secondPart:
|
||||
return (cstrType, secondPart, firstPart)
|
||||
else:
|
||||
return (cstrType, firstPart, secondPart)
|
||||
|
||||
# For skipping invalid constraints
|
||||
_DummyCstrList = [None] * 6
|
||||
|
||||
class SystemExtension(object):
|
||||
def __init__(self):
|
||||
|
@ -126,9 +134,16 @@ class SystemExtension(object):
|
|||
self.secondInfo = None
|
||||
self.relax = False
|
||||
self.coincidences = {}
|
||||
self.cstrMap = {}
|
||||
self.elementCstrMap = {}
|
||||
self.elementMap = {}
|
||||
self.firstElement = None
|
||||
self.secondElement = None
|
||||
|
||||
def checkRedundancy(self,obj,firstInfo,secondInfo):
|
||||
def checkRedundancy(self,obj,firstInfo,secondInfo,firstElement,secondElement):
|
||||
self.cstrObj,self.firstInfo,self.secondInfo=obj,firstInfo,secondInfo
|
||||
self.firstElement = firstElement
|
||||
self.secondElement = secondElement
|
||||
|
||||
def addSketchPlane(self,*args,**kargs):
|
||||
_ = kargs
|
||||
|
@ -148,34 +163,121 @@ class SystemExtension(object):
|
|||
h.append(self.addSameOrientation(n1.entity,n,group=group))
|
||||
return h
|
||||
|
||||
def reportRedundancy(self,warn=False):
|
||||
def reportRedundancy(self,firstPart=None,secondPart=None,count=0,limit=0,implicit=False):
|
||||
msg = '{} between {} and {}'.format(cstrName(self.cstrObj),
|
||||
self.firstInfo.PartName, self.secondInfo.PartName)
|
||||
if warn:
|
||||
logger.warn('skip redundant {}', msg, frame=1)
|
||||
firstPart if firstPart else self.firstInfo.PartName,
|
||||
secondPart if secondPart else self.secondInfo.PartName)
|
||||
if implicit:
|
||||
logger.msg('redundant implicit constraint {}, {}', msg, count, frame=1)
|
||||
elif count > limit:
|
||||
logger.warn('skip redundant {}, {}', msg, count, frame=1)
|
||||
else:
|
||||
logger.debug('auto relax {}', msg, frame=1)
|
||||
logger.msg('auto relax {}, {}', msg, count, frame=1)
|
||||
|
||||
def _countConstraints(self,increment,limit,*names):
|
||||
first,second = self.firstInfo,self.secondInfo
|
||||
if not first or not second:
|
||||
return []
|
||||
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,[])
|
||||
cstrs += [None]*increment
|
||||
count = len(cstrs)
|
||||
if limit and count>=limit:
|
||||
self.reportRedundancy(count>limit)
|
||||
def _populateConstraintMap(
|
||||
self,cstrType,firstElement,secondElement,increment,limit,item,implicit):
|
||||
|
||||
firstPart = self.elementMap[firstElement]
|
||||
secondPart = self.elementMap[secondElement]
|
||||
if firstPart == secondPart:
|
||||
return _DummyCstrList
|
||||
|
||||
# A constraint may contain elements belong to more than two parts. For
|
||||
# exmaple, for a constraint with elements from part A, B, C, we'll
|
||||
# expand it into two constraints for parts AB and AC. However, we must
|
||||
# also count the implicit constraint between B and C.
|
||||
#
|
||||
# self.cstrMap is a map for counting constraints of the same type
|
||||
# between pairs of parts. The count is used for checking redundancy and
|
||||
# auto relaxing. The map is keyed using
|
||||
#
|
||||
# tuple(cstrType, firstPartName, secondPartName)
|
||||
#
|
||||
# and the value is a list. The item of this list is constraint defined
|
||||
# (e.g. PlaineAilgnment stores a plane entity as item for auto
|
||||
# relaxing) , the length of this list is use as the constraint count to
|
||||
# be used later to decide how to auto relax the constraint.
|
||||
#
|
||||
# See the following link for difficulties on auto relaxing with implicit
|
||||
# constraints. Right now there is no search performed. So the auto relax
|
||||
# may fail. And the user is required to manually reorder constraints and
|
||||
# the elements within to help the solver.
|
||||
#
|
||||
# https://github.com/realthunder/FreeCAD_assembly3/issues/403#issuecomment-757400349
|
||||
|
||||
key = _cstrKey(cstrType,firstPart,secondPart)
|
||||
cstrs = self.cstrMap.setdefault(key, [])
|
||||
cstrs += [item]*increment
|
||||
count = len(cstrs)
|
||||
if increment and count>=limit:
|
||||
self.reportRedundancy(firstPart, secondPart, count, limit, implicit)
|
||||
return cstrs
|
||||
|
||||
def countConstraints(self,increment,limit,*names):
|
||||
count = len(self._countConstraints(increment,limit,*names))
|
||||
def _countConstraints(self,increment,limit,cstrType,item=None):
|
||||
first, second = self.firstInfo, self.secondInfo
|
||||
if not first or not second:
|
||||
return []
|
||||
|
||||
firstElement, secondElement = self.firstElement, self.secondElement
|
||||
|
||||
if firstElement == secondElement:
|
||||
return _DummyCstrList
|
||||
|
||||
self.elementMap[firstElement] = first.PartName
|
||||
self.elementMap[secondElement] = second.PartName
|
||||
|
||||
# When counting implicit constraints (see comments in
|
||||
# _populateConstraintMap() above), we must also make sure to count them
|
||||
# if and only if they are originated from the same element, i.e. both
|
||||
# AB and AC involving the same element of A. This will be ture if the
|
||||
# those constraints are expanded by us, but may not be so if the user
|
||||
# created them.
|
||||
#
|
||||
# self.elementCstrMap is a map keyed using tuple(cstrType, elementName),
|
||||
# with value of a set of all element names that is involved with the the
|
||||
# same type of constraint. This set is shared by all element entries in
|
||||
# the map.
|
||||
|
||||
firstSet = self.elementCstrMap.setdefault((cstrType, firstElement), set())
|
||||
if not firstSet:
|
||||
firstSet.add(firstElement)
|
||||
secondSet = self.elementCstrMap.setdefault((cstrType, secondElement),firstSet)
|
||||
|
||||
res = _DummyCstrList
|
||||
|
||||
if firstSet is not secondSet:
|
||||
# If the secondSet is different, we shall merge them, and count the
|
||||
# implicit constraints between the elements of first and second set.
|
||||
for element in secondSet:
|
||||
self.elementCstrMap[(cstrType, element)] = firstSet
|
||||
is_second = element == secondElement
|
||||
for e in firstSet:
|
||||
implicit = not is_second or e != firstElement
|
||||
cstrs = self._populateConstraintMap(
|
||||
cstrType,e,element,increment,limit,item,implicit)
|
||||
if not implicit:
|
||||
# save the result (i.e. the explicit constraint pair of
|
||||
# the give first and second element) for return
|
||||
res = cstrs
|
||||
firstSet |= secondSet
|
||||
elif secondElement not in firstSet:
|
||||
# Here means the entry of the secondElement is newly created, count
|
||||
# the implicit constraints between all elements in the set to the
|
||||
# secondElement.
|
||||
for e in firstSet:
|
||||
implicit = e != firstElement
|
||||
cstrs = self._populateConstraintMap(
|
||||
cstrType,e,secondElement,increment,limit,item,implicit)
|
||||
if not implicit:
|
||||
res = cstrs
|
||||
firstSet.add(secondElement)
|
||||
|
||||
if res is _DummyCstrList:
|
||||
self.reportRedundancy(count=len(res), limit=limit)
|
||||
return res
|
||||
|
||||
def countConstraints(self,increment,limit,name):
|
||||
count = len(self._countConstraints(increment,limit,name))
|
||||
if count>limit:
|
||||
return -1
|
||||
return count
|
||||
|
@ -210,7 +312,7 @@ class SystemExtension(object):
|
|||
# in the previous and the current constraint. The point is taken
|
||||
# from the element of the second part of the current constraint.
|
||||
# The projection plane is taken from the element of the first part
|
||||
# of the current constraint.
|
||||
# of the current constraint.
|
||||
#
|
||||
# This 2D PointOnLine effectively reduce the second PlaneCoincidence
|
||||
# constraining DOF down to 1.
|
||||
|
@ -236,15 +338,12 @@ class SystemExtension(object):
|
|||
h = []
|
||||
if self.relax:
|
||||
dof = 2 if lockAngle else 1
|
||||
cstrs = self._countConstraints(dof,3,'Alignment')
|
||||
cstrs = self._countConstraints(dof,3,'Alignment',item=pln1.entity)
|
||||
count = len(cstrs)
|
||||
if count > 3:
|
||||
return
|
||||
if count == 1:
|
||||
cstrs[0] = pln1.entity
|
||||
else:
|
||||
count = 0
|
||||
cstrs = None
|
||||
|
||||
if d:
|
||||
h.append(self.addPointPlaneDistance(
|
||||
|
@ -255,7 +354,7 @@ class SystemExtension(object):
|
|||
if count<=2:
|
||||
n1,n2 = pln1.normal,pln2.normal
|
||||
if count==2 and not lockAngle:
|
||||
self.reportRedundancy()
|
||||
self.reportRedundancy(count=count, limit=count)
|
||||
h.append(self.addParallel(n2.entity,n1.entity,cstrs[0],group))
|
||||
else:
|
||||
self.setOrientation(h,lockAngle,yaw,pitch,roll,n1,n2,group)
|
||||
|
|
Loading…
Reference in New Issue
Block a user