diff --git a/constraint.py b/constraint.py index ee4bfe7..b5ff19e 100644 --- a/constraint.py +++ b/constraint.py @@ -497,14 +497,14 @@ class Constraint(ProxyType): return mcs.getProxy(obj).prepare(obj,solver) @classmethod - def getFixedParts(mcs,solver,cstrs,partGroup): + def getFixedParts(mcs,solver,cstrs,partGroup,rollback): firstInfo = None if partGroup.Proxy.derivedParts: ret = set(partGroup.Proxy.derivedParts) else: ret = set() - from .assembly import isTypeOf, AsmWorkPlane - for obj in partGroup.Group: + from .assembly import isTypeOf, AsmWorkPlane, flattenGroup + for obj in flattenGroup(partGroup): if not hasattr(obj,'Placement'): ret.add(obj) logger.debug('part without Placement {}',objName(obj)) @@ -513,9 +513,13 @@ class Constraint(ProxyType): logger.debug('fix workplane {}',objName(obj)) found = len(ret) + attachments = [] for obj in cstrs: cstr = mcs.getProxy(obj) + if isinstance(cstr, Attachment): + attachments.append(obj) + # Build array constraint map for constraint multiplication if mcs.canMultiply(obj): element0 = obj.Proxy.getElements()[0].Proxy @@ -556,6 +560,106 @@ class Constraint(ProxyType): logger.debug('\t{}.{}',o[0].Name,o[1]) else: logger.debug('\t{}',o.Name) + + if not ret: + return ret + + # Early solving for Attachment constraint, which requires only simple + # matrix calculation. We only solve those directly or indirectly + # attached to some fixed part, and treat the rest as PlaneCoincident + # with locked angle. + + from .assembly import setPlacement + handled = set() + while True: + nxt = [] + for obj in attachments: + cstr = obj.Proxy + + firstInfo = [] + infos = [] + + if obj.Cascade: + # for cascading attachment, we should handle it if there is + # any fixed part inside. We also need to sort the element + # pairs properly so that they can be recognized as fixed one + # after another + prev = None + fixed = None + for e in cstr.getElements(): + info = e.Proxy.getInfo() + if prev and prev.Part!=info.Part: + firstInfo.append(prev) + infos.append(info) + if fixed is None \ + and (prev.Part in ret or info.Part in ret): + fixed = len(firstInfo) + prev = info + + if fixed is None: + continue + + if fixed: + # reorder the element pairs to put the pair with the + # fixed part first, and then reversed the ones that are + # before it. + firstInfo = firstInfo[fixed:] +\ + list(reversed(firstInfo[:fixed])) + infos = infos[fixed:] + list(reversed(infos[:fixed])) + else: + if not mcs.canMultiply(obj): + infos = cstr.getElementsInfo() + firstInfo = [infos[0]]*(len(infos)-1) + infos = infos[1:] + else: + elements = cstr.getElements() + firstInfo = elements[0].Proxy.getInfo(expand=True) + infos = [] + for element in elements[1:]: + infos += element.Proxy.getInfo(expand=True) + count = min(len(infos),len(firstInfo)) + firstInfo = firstInfo[:count] + infos = infos[:count] + + if any([ not (info0.Part in ret or info.Part in ret) + for info0,info in zip(firstInfo,infos)]): + # we only handle if all element pair has at least one + # part that are fixed + continue + + handled.add(obj) + + for info0,info in zip(firstInfo,infos): + if info.Part in ret: + if info0.Part in ret: + logger.warn('skip fixed part "{}" and "{}" in {}', + info.PartName,info0.PartName,cstrName(obj)) + continue + info0,info = info,info0 + + ret.add(info.Part) + + pla0 = info0.Placement.multiply( + utils.getElementPlacement(info0.Shape)) + pla = pla0.multiply( + utils.getElementPlacement(info.Shape).inverse()) + if not utils.isSamePlacement(pla,info.Placement): + solver.touched = True + solver.system.log('attaching "{}" -> "{}"', + info.PartName, info0.PartName) + if rollback is not None: + rollback.append((info.PartName, + info.Part, + info.Placement.copy())) + setPlacement(info.Part,pla) + + if len(nxt) == len(attachments): + break + attachments = nxt + + if handled: + cstrs[:] = [ obj for obj in cstrs if obj not in handled ] + return ret @classmethod @@ -1087,8 +1191,10 @@ class BaseMulti(Base): elements.append(e) if len(elements)<=1: - logger.warn('{} has no effective constraint',cstrName(obj)) + logger.warn('{} has no effective constraining element', + cstrName(obj)) return + e0 = None e = None info0 = None @@ -1171,6 +1277,16 @@ class PlaneCoincident(BaseCascade): 'The faces are coincided at their centers with an optional distance.' +class Attachment(BaseCascade): + _id = 45 + _iconName = 'Assembly_ConstraintAttachment.svg' + _props = ['Multiply', 'Cascade'] + _tooltip = \ + 'Add a "{}" constraint to attach two parts by the selected geometry\n'\ + 'elements. This constraint completely fixes the parts realtive to each\n'\ + 'other.' + + class AxialAlignment(BaseMulti): _id = 36 _entityDef = (_lna,) diff --git a/solver.py b/solver.py index d0a8b13..06a07ec 100644 --- a/solver.py +++ b/solver.py @@ -43,8 +43,10 @@ class Solver(object): self._partMap = {} self._cstrMap = {} self._cstrArrayMap = defaultdict(int) + self._fixedParts = set() self._fixedElements = set() self._dragPart = dragPart + self.touched = False self.system.GroupHandle = self._fixedGroup @@ -64,10 +66,20 @@ class Solver(object): self.ny = self.system.addNormal3dV(*utils.getNormal(roty)) partGroup = assembly.Proxy.getPartGroup() - self._fixedParts = Constraint.getFixedParts(self,cstrs,partGroup) + self._fixedParts = Constraint.getFixedParts( + self,cstrs,partGroup,rollback) for part in self._fixedParts: self._fixedElements.add((part,None)) + if self.touched: + if not assembly.recompute(True): + raise RuntimeError( + 'Failed to recompute {}'.format(objName(assembly))) + + if not cstrs: + self.system.log('no constraints') + return + for cstr in cstrs: self.system.log('preparing {}',cstrName(cstr)) self.system.GroupHandle += 1 diff --git a/system.py b/system.py index a661088..7388861 100644 --- a/system.py +++ b/system.py @@ -221,6 +221,9 @@ class SystemExtension(object): return self.setOrientation(h, lockAngle, yaw, pitch, roll, pln1.normal, pln2.normal, group) + def addAttachment(self, pln1, pln2, group=0): + return self.addPlaneCoincident(0,0,0,False,0,0,0, pln1, pln2, group) + def addPlaneAlignment(self,d,lockAngle,yaw,pitch,roll,pln1,pln2,group=0): if not group: group = self.GroupHandle