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
This commit is contained in:
Zheng, Lei 2018-03-23 16:35:13 +08:00
parent 91638cce8d
commit 80bceefcc6
4 changed files with 138 additions and 21 deletions

View File

@ -47,8 +47,9 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system.log('cache {}: {}'.format(key,h)) system.log('cache {}: {}'.format(key,h))
return h if retAll else h[0] return h if retAll else h[0]
v = utils.getElementPos(shape)
if utils.isDraftWire(part): if utils.isDraftWire(part):
v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
v = partInfo.Placement.multVec(v) v = partInfo.Placement.multVec(v)
params = [] params = []
@ -83,7 +84,6 @@ def _p(solver,partInfo,subname,shape,retAll=False):
system.log('{}: add circle point {},{}'.format(key,h,e)) system.log('{}: add circle point {},{}'.format(key,h,e))
else: else:
v = utils.getElementPos(shape)
nameTag = partInfo.PartName + '.' + key nameTag = partInfo.PartName + '.' + key
system.NameTag = nameTag system.NameTag = nameTag
e = system.addPoint3dV(*v) e = system.addPoint3dV(*v)
@ -92,6 +92,8 @@ def _p(solver,partInfo,subname,shape,retAll=False):
h = [h,e] h = [h,e]
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
# use the point entity as key to store its original position vector
partInfo.EntityMap[h[0]] = [v]
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
@ -130,7 +132,14 @@ def _n(solver,partInfo,subname,shape,retAll=False):
system.NameTag = nameTag + 'xt' system.NameTag = nameTag + 'xt'
h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group)) h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group))
# also add local x pointing vector (1,0,0)
px = rot.multVec(FreeCAD.Vector(1,0,0))
hx = system.addPoint3dV(px.x,px.y,px.z)
h.append(system.addTransform(hx,*partInfo.Params,group=partInfo.Group))
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
# use the normal entity as key to store its original rotation
partInfo.EntityMap[h[0]] = [rot]
partInfo.EntityMap[key] = h partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
@ -235,9 +244,8 @@ def _w(solver,partInfo,subname,shape,retAll=False):
n = _n(solver,partInfo,subname,shape,True) n = _n(solver,partInfo,subname,shape,True)
system.NameTag = partInfo.PartName + '.' + key system.NameTag = partInfo.PartName + '.' + key
w = system.addWorkplane(p,n[0],group=partInfo.Group) w = system.addWorkplane(p,n[0],group=partInfo.Group)
h = [w,p] + n h = [w,p,n]
system.log('{}: {},{}'.format(key,h,partInfo.Group)) system.log('{}: {},{}'.format(key,h,partInfo.Group))
partInfo.EntityMap[key] = h
return h if retAll else h[0] return h if retAll else h[0]
def _wa(solver,partInfo,subname,shape,retAll=False): def _wa(solver,partInfo,subname,shape,retAll=False):
@ -269,6 +277,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
if utils.isDraftCircle(partInfo.Part): if utils.isDraftCircle(partInfo.Part):
part = partInfo.Part part = partInfo.Part
w,p,n = partInfo.Workplane[:3] w,p,n = partInfo.Workplane[:3]
n = n[0]
if system.sketchPlane and not solver.isFixedElement(part,subname): if system.sketchPlane and not solver.isFixedElement(part,subname):
system.NameTag = nameTag + '.o' system.NameTag = nameTag + '.o'
@ -313,6 +322,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
partInfo.EntityMap[sub] = h partInfo.EntityMap[sub] = h
else: else:
w,p,n = _w(solver,partInfo,subname,shape,True)[:3] w,p,n = _w(solver,partInfo,subname,shape,True)[:3]
n = n[0]
r = utils.getElementCircular(shape) r = utils.getElementCircular(shape)
if not r: if not r:
raise RuntimeError('shape is not cicular') raise RuntimeError('shape is not cicular')
@ -831,14 +841,17 @@ class BaseMulti(Base):
return return
e0 = None e0 = None
ret = [] ret = []
firstInfo = None
for e in elements: for e in elements:
info = e.Proxy.getInfo() info = e.Proxy.getInfo()
partInfo = solver.getPartInfo(info) partInfo = solver.getPartInfo(info)
if not e0: if not e0:
e0 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape) e0 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
firstInfo = partInfo
else: else:
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape) e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
params = props + [e0,e] params = props + [e0,e]
solver.system.checkRedundancy(obj,firstInfo,partInfo)
h = func(*params,group=solver.group) h = func(*params,group=solver.group)
if isinstance(h,(list,tuple)): if isinstance(h,(list,tuple)):
ret += list(h) ret += list(h)
@ -873,6 +886,7 @@ class BaseCascade(BaseMulti):
params = props + [e1,e2] params = props + [e1,e2]
else: else:
params = props + [e2,e1] params = props + [e2,e1]
solver.system.checkRedendancy(obj,prevInfo,partInfo)
h = func(*params,group=solver.group) h = func(*params,group=solver.group)
if isinstance(h,(list,tuple)): if isinstance(h,(list,tuple)):
ret += list(h) ret += list(h)
@ -892,7 +906,6 @@ class PlaneCoincident(BaseCascade):
'Add a "{}" constraint to conincide planes of two or more parts.\n'\ 'Add a "{}" constraint to conincide planes of two or more parts.\n'\
'The planes are coincided at their centers with an optional distance.' 'The planes are coincided at their centers with an optional distance.'
class PlaneAlignment(BaseCascade): class PlaneAlignment(BaseCascade):
_id = 37 _id = 37
_iconName = 'Assembly_ConstraintAlignment.svg' _iconName = 'Assembly_ConstraintAlignment.svg'

View File

@ -17,8 +17,11 @@ from .system import System
# plane of the part. # plane of the part.
# EntityMap: string -> entity handle map, for caching # EntityMap: string -> entity handle map, for caching
# Group: transforming entity group handle # Group: transforming entity group handle
# CstrMap: map from other part to the constrains between this and the othe part.
# This is for auto constraint DOF reduction. Only some composite
# constraints will be mapped.
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement', PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
'Params','Workplane','EntityMap','Group')) 'Params','Workplane','EntityMap','Group','CstrMap'))
class Solver(object): class Solver(object):
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback): def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
@ -37,13 +40,17 @@ class Solver(object):
self.system.GroupHandle = self._fixedGroup self.system.GroupHandle = self._fixedGroup
# convenience constant of zero # convenience constant of zero and one
self.v0 = self.system.addParamV(0,group=self._fixedGroup) self.v0 = self.system.addParamV(0,group=self._fixedGroup)
self.v1 = self.system.addParamV(1,group=self._fixedGroup)
# convenience normals # convenience normals
rotx = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90) rotx = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90)
self.nx = self.system.addNormal3dV(*utils.getNormal(rotx)) self.nx = self.system.addNormal3dV(*utils.getNormal(rotx))
# convenience x pointing vector
self.px = self.system.addPoint3d(self.v1,self.v0,self.v0)
self._fixedParts = Constraint.getFixedParts(self,cstrs) self._fixedParts = Constraint.getFixedParts(self,cstrs)
for part in self._fixedParts: for part in self._fixedParts:
self._fixedElements.add((part,None)) self._fixedElements.add((part,None))
@ -55,7 +62,8 @@ class Solver(object):
if ret: if ret:
if isinstance(ret,(list,tuple)): if isinstance(ret,(list,tuple)):
for h in ret: for h in ret:
self._cstrMap[h] = cstr if not isinstance(h,(list,tuple)):
self._cstrMap[h] = cstr
else: else:
self._cstrMap[ret] = cstr self._cstrMap[ret] = cstr
@ -217,9 +225,11 @@ class Solver(object):
self.system.NameTag = info.PartName + '.nx' self.system.NameTag = info.PartName + '.nx'
nx = self.system.addTransform(self.nx, nx = self.system.addTransform(self.nx,
self.v0,self.v0,self.v0,*params[3:],group=g) self.v0,self.v0,self.v0,*params[3:],group=g)
px = self.system.addTransform(self.px,self.v0,self.v0,
self.v0,*params[3:],group=g)
self.system.NameTag = info.PartName + '.w' self.system.NameTag = info.PartName + '.w'
w = self.system.addWorkplane(p,n,group=g) w = self.system.addWorkplane(p,n,group=g)
h = (w,p,n,nx) h = (w,p,(n,nx,px))
partInfo = PartInfo(Part = info.Part, partInfo = PartInfo(Part = info.Part,
PartName = info.PartName, PartName = info.PartName,
@ -227,7 +237,8 @@ class Solver(object):
Params = params, Params = params,
Workplane = h, Workplane = h,
EntityMap = {}, EntityMap = {},
Group = group if group else g) Group = group if group else g,
CstrMap = {})
self.system.log('{}, {}'.format(partInfo,g)) self.system.log('{}, {}'.format(partInfo,g))

109
system.py
View File

@ -1,5 +1,6 @@
import os import os
from .utils import getIcon, syslogger as logger, objName from .constraint import cstrName
from .utils import getIcon, syslogger as logger, objName, project2D
from .proxy import ProxyType, PropertyInfo from .proxy import ProxyType, PropertyInfo
class System(ProxyType): class System(ProxyType):
@ -58,12 +59,12 @@ class System(ProxyType):
return proxy.getSystem(obj) return proxy.getSystem(obj)
@classmethod @classmethod
def isConstraintSupported(mcs,obj,cstrName): def isConstraintSupported(mcs,obj,name):
if cstrName == 'Locked': if name == 'Locked':
return True return True
proxy = mcs.getProxy(obj) proxy = mcs.getProxy(obj)
if proxy: if proxy:
return proxy.isConstraintSupported(cstrName) return proxy.isConstraintSupported(name)
def _makePropInfo(name,tp,doc=''): def _makePropInfo(name,tp,doc=''):
PropertyInfo(System,name,tp,doc,group='Solver') PropertyInfo(System,name,tp,doc,group='Solver')
@ -112,6 +113,12 @@ class SystemExtension(object):
super(SystemExtension,self).__init__() super(SystemExtension,self).__init__()
self.NameTag = '' self.NameTag = ''
self.sketchPlane = None 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): def addSketchPlane(self,*args,**kargs):
_ = kargs _ = kargs
@ -127,12 +134,71 @@ class SystemExtension(object):
h.append(self.addAngle(angle,False,nx1,nx2,group=group)) h.append(self.addAngle(angle,False,nx1,nx2,group=group))
return h 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): def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
if not group: if not group:
group = self.GroupHandle group = self.GroupHandle
w1,p1,n1,nx1 = e1[:4] w1,p1,n1 = e1[:3]
_,p2,n2,nx2 = e2[:4] _,p2,n2 = e2[:3]
n1,nx1 = n1[:2]
n2,nx2 = n2[:2]
h = [] 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: if d:
h.append(self.addPointPlaneDistance(d,p2,w1,group=group)) h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
h.append(self.addPointsCoincident(p1,p2,w1,group=group)) h.append(self.addPointsCoincident(p1,p2,w1,group=group))
@ -143,20 +209,41 @@ class SystemExtension(object):
def addPlaneAlignment(self,d,lockAngle,angle,e1,e2,group=0): def addPlaneAlignment(self,d,lockAngle,angle,e1,e2,group=0):
if not group: if not group:
group = self.GroupHandle group = self.GroupHandle
w1,_,n1,nx1 = e1[:4] w1,_,n1 = e1[:4]
_,p2,n2,nx2 = e2[:4] _,p2,n2 = e2[:4]
n1,nx1 = n1[:2]
n2,nx2,px2 = n2[:3]
h = [] 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: if d:
h.append(self.addPointPlaneDistance(d,p2,w1,group=group)) h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
else: else:
h.append(self.addPointInPlane(p2,w1,group=group)) h.append(self.addPointInPlane(p2,w1,group=group))
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,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): def addAxialAlignment(self,lockAngle,angle,e1,e2,group=0):
if not group: if not group:
group = self.GroupHandle group = self.GroupHandle
w1,p1,n1,nx1 = e1[:4] count = self.countConstraints(0,1,'Coincident','Alignment')
_,p2,n2,nx2 = e2[:4] if count<0:
return
w1,p1,n1 = e1[:3]
_,p2,n2 = e2[:3]
n1,nx1 = n1[:2]
n2,nx2 = n2[:2]
h = [] h = []
h.append(self.addPointsCoincident(p1,p2,w1,group=group)) h.append(self.addPointsCoincident(p1,p2,w1,group=group))
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group) return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group)

View File

@ -572,3 +572,9 @@ def getLabel(obj):
label = label[:-i] label = label[:-i]
break break
return label return label
def project2D(rot,*vectors):
vx = rot.multVec(FreeCAD.Vector(1,0,0))
vy = rot.multVec(FreeCAD.Vector(0,1,0))
return [FreeCAD.Vector(v.dot(vx),v.dot(vy),0) for v in vectors]