Skip to content

Commit 80bceef

Browse files
committed
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
1 parent 91638cc commit 80bceef

File tree

4 files changed

+138
-21
lines changed

4 files changed

+138
-21
lines changed

constraint.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ def _p(solver,partInfo,subname,shape,retAll=False):
4747
system.log('cache {}: {}'.format(key,h))
4848
return h if retAll else h[0]
4949

50+
v = utils.getElementPos(shape)
51+
5052
if utils.isDraftWire(part):
51-
v = utils.getElementPos(shape)
5253
nameTag = partInfo.PartName + '.' + key
5354
v = partInfo.Placement.multVec(v)
5455
params = []
@@ -83,7 +84,6 @@ def _p(solver,partInfo,subname,shape,retAll=False):
8384
system.log('{}: add circle point {},{}'.format(key,h,e))
8485

8586
else:
86-
v = utils.getElementPos(shape)
8787
nameTag = partInfo.PartName + '.' + key
8888
system.NameTag = nameTag
8989
e = system.addPoint3dV(*v)
@@ -92,6 +92,8 @@ def _p(solver,partInfo,subname,shape,retAll=False):
9292
h = [h,e]
9393
system.log('{}: {},{}'.format(key,h,partInfo.Group))
9494

95+
# use the point entity as key to store its original position vector
96+
partInfo.EntityMap[h[0]] = [v]
9597
partInfo.EntityMap[key] = h
9698
return h if retAll else h[0]
9799

@@ -130,7 +132,14 @@ def _n(solver,partInfo,subname,shape,retAll=False):
130132
system.NameTag = nameTag + 'xt'
131133
h.append(system.addTransform(e,*partInfo.Params,group=partInfo.Group))
132134

135+
# also add local x pointing vector (1,0,0)
136+
px = rot.multVec(FreeCAD.Vector(1,0,0))
137+
hx = system.addPoint3dV(px.x,px.y,px.z)
138+
h.append(system.addTransform(hx,*partInfo.Params,group=partInfo.Group))
139+
133140
system.log('{}: {},{}'.format(key,h,partInfo.Group))
141+
# use the normal entity as key to store its original rotation
142+
partInfo.EntityMap[h[0]] = [rot]
134143
partInfo.EntityMap[key] = h
135144
return h if retAll else h[0]
136145

@@ -235,9 +244,8 @@ def _w(solver,partInfo,subname,shape,retAll=False):
235244
n = _n(solver,partInfo,subname,shape,True)
236245
system.NameTag = partInfo.PartName + '.' + key
237246
w = system.addWorkplane(p,n[0],group=partInfo.Group)
238-
h = [w,p] + n
247+
h = [w,p,n]
239248
system.log('{}: {},{}'.format(key,h,partInfo.Group))
240-
partInfo.EntityMap[key] = h
241249
return h if retAll else h[0]
242250

243251
def _wa(solver,partInfo,subname,shape,retAll=False):
@@ -269,6 +277,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
269277
if utils.isDraftCircle(partInfo.Part):
270278
part = partInfo.Part
271279
w,p,n = partInfo.Workplane[:3]
280+
n = n[0]
272281

273282
if system.sketchPlane and not solver.isFixedElement(part,subname):
274283
system.NameTag = nameTag + '.o'
@@ -313,6 +322,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
313322
partInfo.EntityMap[sub] = h
314323
else:
315324
w,p,n = _w(solver,partInfo,subname,shape,True)[:3]
325+
n = n[0]
316326
r = utils.getElementCircular(shape)
317327
if not r:
318328
raise RuntimeError('shape is not cicular')
@@ -831,14 +841,17 @@ def prepare(cls,obj,solver):
831841
return
832842
e0 = None
833843
ret = []
844+
firstInfo = None
834845
for e in elements:
835846
info = e.Proxy.getInfo()
836847
partInfo = solver.getPartInfo(info)
837848
if not e0:
838849
e0 = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
850+
firstInfo = partInfo
839851
else:
840852
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
841853
params = props + [e0,e]
854+
solver.system.checkRedundancy(obj,firstInfo,partInfo)
842855
h = func(*params,group=solver.group)
843856
if isinstance(h,(list,tuple)):
844857
ret += list(h)
@@ -873,6 +886,7 @@ def prepare(cls,obj,solver):
873886
params = props + [e1,e2]
874887
else:
875888
params = props + [e2,e1]
889+
solver.system.checkRedendancy(obj,prevInfo,partInfo)
876890
h = func(*params,group=solver.group)
877891
if isinstance(h,(list,tuple)):
878892
ret += list(h)
@@ -892,7 +906,6 @@ class PlaneCoincident(BaseCascade):
892906
'Add a "{}" constraint to conincide planes of two or more parts.\n'\
893907
'The planes are coincided at their centers with an optional distance.'
894908

895-
896909
class PlaneAlignment(BaseCascade):
897910
_id = 37
898911
_iconName = 'Assembly_ConstraintAlignment.svg'

solver.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
# plane of the part.
1818
# EntityMap: string -> entity handle map, for caching
1919
# Group: transforming entity group handle
20+
# CstrMap: map from other part to the constrains between this and the othe part.
21+
# This is for auto constraint DOF reduction. Only some composite
22+
# constraints will be mapped.
2023
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
21-
'Params','Workplane','EntityMap','Group'))
24+
'Params','Workplane','EntityMap','Group','CstrMap'))
2225

2326
class Solver(object):
2427
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
@@ -37,13 +40,17 @@ def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
3740

3841
self.system.GroupHandle = self._fixedGroup
3942

40-
# convenience constant of zero
43+
# convenience constant of zero and one
4144
self.v0 = self.system.addParamV(0,group=self._fixedGroup)
45+
self.v1 = self.system.addParamV(1,group=self._fixedGroup)
4246

4347
# convenience normals
4448
rotx = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90)
4549
self.nx = self.system.addNormal3dV(*utils.getNormal(rotx))
4650

51+
# convenience x pointing vector
52+
self.px = self.system.addPoint3d(self.v1,self.v0,self.v0)
53+
4754
self._fixedParts = Constraint.getFixedParts(self,cstrs)
4855
for part in self._fixedParts:
4956
self._fixedElements.add((part,None))
@@ -55,7 +62,8 @@ def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
5562
if ret:
5663
if isinstance(ret,(list,tuple)):
5764
for h in ret:
58-
self._cstrMap[h] = cstr
65+
if not isinstance(h,(list,tuple)):
66+
self._cstrMap[h] = cstr
5967
else:
6068
self._cstrMap[ret] = cstr
6169

@@ -217,17 +225,20 @@ def getPartInfo(self,info,fixed=False,group=0):
217225
self.system.NameTag = info.PartName + '.nx'
218226
nx = self.system.addTransform(self.nx,
219227
self.v0,self.v0,self.v0,*params[3:],group=g)
228+
px = self.system.addTransform(self.px,self.v0,self.v0,
229+
self.v0,*params[3:],group=g)
220230
self.system.NameTag = info.PartName + '.w'
221231
w = self.system.addWorkplane(p,n,group=g)
222-
h = (w,p,n,nx)
232+
h = (w,p,(n,nx,px))
223233

224234
partInfo = PartInfo(Part = info.Part,
225235
PartName = info.PartName,
226236
Placement = info.Placement.copy(),
227237
Params = params,
228238
Workplane = h,
229239
EntityMap = {},
230-
Group = group if group else g)
240+
Group = group if group else g,
241+
CstrMap = {})
231242

232243
self.system.log('{}, {}'.format(partInfo,g))
233244

system.py

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
2-
from .utils import getIcon, syslogger as logger, objName
2+
from .constraint import cstrName
3+
from .utils import getIcon, syslogger as logger, objName, project2D
34
from .proxy import ProxyType, PropertyInfo
45

56
class System(ProxyType):
@@ -58,12 +59,12 @@ def getSystem(mcs,obj):
5859
return proxy.getSystem(obj)
5960

6061
@classmethod
61-
def isConstraintSupported(mcs,obj,cstrName):
62-
if cstrName == 'Locked':
62+
def isConstraintSupported(mcs,obj,name):
63+
if name == 'Locked':
6364
return True
6465
proxy = mcs.getProxy(obj)
6566
if proxy:
66-
return proxy.isConstraintSupported(cstrName)
67+
return proxy.isConstraintSupported(name)
6768

6869
def _makePropInfo(name,tp,doc=''):
6970
PropertyInfo(System,name,tp,doc,group='Solver')
@@ -112,6 +113,12 @@ def __init__(self):
112113
super(SystemExtension,self).__init__()
113114
self.NameTag = ''
114115
self.sketchPlane = None
116+
self.cstrObj = None
117+
self.firstInfo = None
118+
self.secondInfo = None
119+
120+
def checkRedundancy(self,obj,firstInfo,secondInfo):
121+
self.cstrObj,self.firstInfo,self.secondInfo=obj,firstInfo,secondInfo
115122

116123
def addSketchPlane(self,*args,**kargs):
117124
_ = kargs
@@ -127,12 +134,71 @@ def setOrientation(self,h,lockAngle,angle,n1,n2,nx1,nx2,group):
127134
h.append(self.addAngle(angle,False,nx1,nx2,group=group))
128135
return h
129136

137+
def reportRedundancy(self,warn=False):
138+
msg = '{} between {} and {}'.format(cstrName(self.cstrObj),
139+
self.firstInfo.PartName, self.secondInfo.PartName)
140+
if warn:
141+
logger.warn('skip redundant ' + msg)
142+
else:
143+
logger.info('auto relax ' + msg)
144+
145+
def countConstraints(self,increment,count,*names):
146+
first,second = self.firstInfo,self.secondInfo
147+
if not first or not second:
148+
return
149+
ret = 0
150+
for name in names:
151+
cstrs = first.CstrMap.get(second.Part,{}).get(name,None)
152+
if not cstrs:
153+
if increment:
154+
cstrs = second.CstrMap.setdefault(
155+
first.Part,{}).setdefault(name,[])
156+
else:
157+
cstrs = second.CstrMap.get(first.Part,{}).get(name,[])
158+
if increment:
159+
cstrs += [None]*increment
160+
ret += len(cstrs)
161+
if ret >= count:
162+
if ret>count:
163+
self.reportRedundancy(True)
164+
return -1
165+
else:
166+
self.reportRedundancy()
167+
return ret
168+
130169
def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
131170
if not group:
132171
group = self.GroupHandle
133-
w1,p1,n1,nx1 = e1[:4]
134-
_,p2,n2,nx2 = e2[:4]
172+
w1,p1,n1 = e1[:3]
173+
_,p2,n2 = e2[:3]
174+
n1,nx1 = n1[:2]
175+
n2,nx2 = n2[:2]
135176
h = []
177+
count = self.countConstraints(0,1,'Alignment','Axial')
178+
if count<0:
179+
# We do not allow plane coincident coexist with other composite
180+
# constraints
181+
return
182+
count = self.countConstraints(2 if lockAngle else 1,2,'Coincident')
183+
if count<0:
184+
return
185+
if not lockAngle and count==2:
186+
# if there is already some other plane coincident constraint set for
187+
# this pair of parts, we reduce this second constraint to either a
188+
# points horizontal or vertical constraint, i.e. reduce the
189+
# constraining DOF down to 1.
190+
#
191+
# We project the initial points to the first element plane, and
192+
# check for differences in x and y components of the points to
193+
# determine whether to use horizontal or vertical constraint.
194+
v1,v2 = project2D(self.firstInfo.EntityMap[n1][0],
195+
self.firstInfo.EntityMap[p1][0],
196+
self.secondInfo.EntityMap[p2][0])
197+
if abs(v1.x-v2.x) < abs(v1.y-v2.y):
198+
h.append(self.addPointsHorizontal(p1,p2,w1,group=group))
199+
else:
200+
h.append(self.addPointsVertical(p1,p2,w1,group=group))
201+
return h
136202
if d:
137203
h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
138204
h.append(self.addPointsCoincident(p1,p2,w1,group=group))
@@ -143,20 +209,41 @@ def addPlaneCoincident(self,d,lockAngle,angle,e1,e2,group=0):
143209
def addPlaneAlignment(self,d,lockAngle,angle,e1,e2,group=0):
144210
if not group:
145211
group = self.GroupHandle
146-
w1,_,n1,nx1 = e1[:4]
147-
_,p2,n2,nx2 = e2[:4]
212+
w1,_,n1 = e1[:4]
213+
_,p2,n2 = e2[:4]
214+
n1,nx1 = n1[:2]
215+
n2,nx2,px2 = n2[:3]
148216
h = []
217+
count = self.countConstraints(0,1,'Coincident','Axial')
218+
if count<0:
219+
return
220+
count = self.countConstraints(2 if lockAngle else 1,3,'Alignment')
221+
if count<0:
222+
return
149223
if d:
150224
h.append(self.addPointPlaneDistance(d,p2,w1,group=group))
151225
else:
152226
h.append(self.addPointInPlane(p2,w1,group=group))
153-
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group)
227+
if count==1 or (lockAngle and count==2):
228+
h.append(self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group))
229+
elif count==2:
230+
self.reportRedundancy()
231+
if d:
232+
h.append(self.addPointPlaneDistance(d,px2,w1,group=group))
233+
else:
234+
h.append(self.addPointInPlane(px2,w1,group=group))
235+
return h
154236

155237
def addAxialAlignment(self,lockAngle,angle,e1,e2,group=0):
156238
if not group:
157239
group = self.GroupHandle
158-
w1,p1,n1,nx1 = e1[:4]
159-
_,p2,n2,nx2 = e2[:4]
240+
count = self.countConstraints(0,1,'Coincident','Alignment')
241+
if count<0:
242+
return
243+
w1,p1,n1 = e1[:3]
244+
_,p2,n2 = e2[:3]
245+
n1,nx1 = n1[:2]
246+
n2,nx2 = n2[:2]
160247
h = []
161248
h.append(self.addPointsCoincident(p1,p2,w1,group=group))
162249
return self.setOrientation(h,lockAngle,angle,n1,n2,nx1,nx2,group)

utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,3 +572,9 @@ def getLabel(obj):
572572
label = label[:-i]
573573
break
574574
return label
575+
576+
def project2D(rot,*vectors):
577+
vx = rot.multVec(FreeCAD.Vector(1,0,0))
578+
vy = rot.multVec(FreeCAD.Vector(0,1,0))
579+
return [FreeCAD.Vector(v.dot(vx),v.dot(vy),0) for v in vectors]
580+

0 commit comments

Comments
 (0)