diff --git a/assembly.py b/assembly.py index 7ca6071..cc3c064 100644 --- a/assembly.py +++ b/assembly.py @@ -1181,7 +1181,7 @@ class AsmElementLink(AsmBase): return self.infos if expand else self.info self.info = None - self.infos *= 0 # clear the list + self.infos = [] obj = getattr(self,'Object',None) if not obj: return @@ -1286,19 +1286,32 @@ class AsmElementLink(AsmBase): info.Constraint.setElementVisible(link.Name,False) return link -def setPlacement(part,pla): + +def setPlacement(part,pla,purgeTouched=False): ''' called by solver after solving to adjust the placement. part: obtained by AsmConstraint.getInfo().Part pla: the new placement ''' - if isinstance(part,tuple): + if not isinstance(part,tuple): + if purgeTouched: + obj = part + touched = 'Touched' in obj.State + part.Placement = pla + else: pla = part[0].Placement.inverse().multiply(pla) if part[3]: + if purgeTouched: + obj = part[0] + touched = 'Touched' in obj.State setLinkProperty(part[0],'PlacementList',{part[1]:pla}) else: + if purgeTouched: + obj = part[2] + touched = 'Touched' in obj.State part[2].Placement = pla - else: - part.Placement = pla + if purgeTouched and not touched: + obj.purgeTouched() + class ViewProviderAsmElementLink(ViewProviderAsmOnTop): def __init__(self,vobj): @@ -1418,21 +1431,29 @@ class AsmConstraint(AsmGroup): name = 'Edge1' if not elementCount: shapes.append(None) + e.Proxy.infos = [] else: count += elementCount shapes.append(info.Shape.getElement(name)) + # merge elements that are coplanar + poses = [] + infos = [] for i,e in enumerate(children[1:]): shape = shapes[i] - if not shape or not e.Proxy.infos: + if not shape: continue for j,e2 in enumerate(children[i+2:]): shape2 = shapes[i+j+1] - if not shape2 or not e2.Proxy.infos: + if not shape2: continue if shape.isCoplanar(shape2): e.Proxy.infos += e2.Proxy.infos e2.Proxy.infos = [] + for info in e.Proxy.infos: + infos.append(info) + poses.append(info.Placement.multVec( + utils.getElementPos(info.Shape))) firstChild = children[0] info = firstChild.Proxy.getInfo() @@ -1456,25 +1477,66 @@ class AsmConstraint(AsmGroup): 'ShowElement',ElementCount='Count') if firstChild.AutoCount: - if getLinkProperty(info.Part[0],'ElementCount',None,True) is None: + oldCount = getLinkProperty(info.Part[0],'ElementCount',None,True) + if oldCount is None: firstChild.AutoCount = False - else: + elif oldCount < count: partTouched = 'Touched' in info.Part[0].State setLinkProperty(info.Part[0],'ElementCount',count) if not partTouched: info.Part[0].purgeTouched() if not firstChild.AutoCount: - count = getLinkProperty(info.Part[0],'ElementCount') + oldCount = getLinkProperty(info.Part[0],'ElementCount') + if count > oldCount: + count = oldCount if firstChild.Count != count: firstChild.Count = count + firstChild.Proxy.getInfo(True) if not touched and 'Touched' in firstChild.State: - firstChild.Proxy.getInfo(True) # purge touched to avoid recomputation multi-pass firstChild.purgeTouched() + # To solve the problem of element index reordering, we shall reorder the + # linux array infos by its proximity to the corresponding constraining + # element shape + + poses = poses[:count] + infos0 = firstChild.Proxy.getInfo(expand=True)[:count] + distances = [] + for i,info0 in enumerate(infos0): + pos0 = info0.Placement.multVec(utils.getElementPos(info0.Shape)) + for j,pos in enumerate(poses): + distances.append((pos0.distanceToPoint(pos),i,j)) + + distances.sort() + + used = [-1]*count + order = [None]*count + for _,i,j in distances: + if used[i]>=0 or order[j]: + continue + used[i] = j + order[j] = infos0[i] + count -= 1 + if not count: + break + firstChild.Proxy.infos = order + + for i in used[oldCount:]: + info0 = order[i] + info = infos[i] + pla = info.Placement.multiply( + utils.getElementPlacement(info.Shape)) + pla0 = info0.Placement.multiply( + utils.getElementPlacement(info0.Shape)) + pla = info0.Placement.multiply(pla.multiply(pla0.inverse())) + info0.Placement.Rotation = pla.Rotation + info0.Placement.Base = pla.Base + setPlacement(info0.Part,pla,True) + def execute(self,obj): if not getattr(self,'_initializing',False) and\ getattr(self,'parent',None): @@ -1500,16 +1562,23 @@ class AsmConstraint(AsmGroup): group = obj.Group if Constraint.canMultiply(obj): firstInfo = group[0].Proxy.getInfo(expand=True) - if not firstInfo: + count = len(firstInfo) + if not count: raise RuntimeError('invalid first element') elements.append(group[0]) for o in group[1:]: - info = o.Proxy.getInfo(expand=True) - if not info: + infos = o.Proxy.getInfo(expand=True) + if not infos: continue - elementInfo += info elements.append(o) - for info in zip(firstInfo,elementInfo[:len(firstInfo)]): + if count <= len(infos): + infos = infos[:count] + o.Proxy.infos = infos + elementInfo += infos + break + elementInfo += infos + + for info in zip(firstInfo,elementInfo): Constraint.check(obj,info,True) else: for o in group: @@ -1722,10 +1791,11 @@ class AsmConstraint(AsmGroup): partGroup = cstr.Proxy.getAssembly().getPartGroup() if multiplied: + cstr.recompute(True) subs = elements[0].Proxy.getElementSubname(True).split('.') infos0 = [] - for i in xrange(elements[0].Count): - subs[1] = str(i) + for info0 in elements[0].Proxy.getInfo(expand=True): + subs[1] = str(info0.Part[1]) infos0.append((partGroup,'.'.join(subs))) infos = [] for element in elements[1:]: diff --git a/constraint.py b/constraint.py index a23a56b..6a03db7 100644 --- a/constraint.py +++ b/constraint.py @@ -978,23 +978,62 @@ class BaseMulti(Base): if not count: logger.warn('{} no first part shape'.format(cstrName(obj))) return + + dragPart = solver.getDragPart() + dragIndex = -1 + if isinstance(dragPart,tuple) and \ + dragPart[0]==firstInfo[0].Part[0] and \ + solver.getArrayPartConstraintCount(dragPart)==1 and \ + dragPart[1] < count: + dragIndex = dragPart[1] + idx = 0 for element in elements[1:]: updates = [] - for i,info in enumerate(element.Proxy.getInfo(expand=True)): + info0Ref = None + infoRef = None + shapeRef = None + refIdx = -1 + infos = element.Proxy.getInfo(expand=True) + + # make sure the dragging part is picked as reference for + # coplanar shortcut updating + if dragIndex>=0: + for i,info in enumerate(infos): + if idx+i >= count: + break + info0 = firstInfo[idx+i] + if info0.Part[1] == dragIndex: + dragIndex = -1 + info0Ref = solver.getPartInfo(info0) + infoRef = solver.getPartInfo(info) + shapeRef = info.Shape + refIdx = i + break + + for i,info in enumerate(infos): if idx >= count: break info0 = firstInfo[idx] - if i and solver.getArrayPartConstraintCount(info0.Part)==1: - # We can safely skip those coplanar edges if the part - # array element is involved in one and only one + partInfo = solver.getPartInfo(info) + + if solver.getArrayPartConstraintCount(info0.Part)!=1: + partInfo0 = solver.getPartInfo(info0) + elif not infoRef: + partInfo0 = solver.getPartInfo(info0) + info0Ref = partInfo0 + infoRef = partInfo + shapeRef = info.Shape + elif i == refIdx: + partInfo0 = info0Ref + else: + # We can safely skip those coplanar edges if the + # part array element is involved in one and only one # constraint (i.e. this one). - updates.append((idx,i)) + updates.append((info0,partInfo,info.Shape)) idx += 1 continue - partInfo0 = solver.getPartInfo(info0) - partInfo = solver.getPartInfo(info) e0 = cls._entityDef[0]( solver,partInfo0,info0.Subname,info0.Shape) e = cls._entityDef[0]( @@ -1009,7 +1048,7 @@ class BaseMulti(Base): idx += 1 if updates: - partInfo0.Update.append((updates,element)) + info0Ref.Update.append((infoRef,shapeRef,updates)) return ret diff --git a/solver.py b/solver.py index a8d6a1b..3404c60 100644 --- a/solver.py +++ b/solver.py @@ -44,6 +44,7 @@ class Solver(object): self._cstrMap = {} self._cstrArrayMap = defaultdict(int) self._fixedElements = set() + self._dragPart = dragPart self.system.GroupHandle = self._fixedGroup @@ -207,28 +208,18 @@ class Solver(object): part.FirstAngle = v[1] part.LastAngle = v[2] - if recompute and touched: - assembly.recompute(True) - # Update parts with constraint multiplication, which auto expands # coplanar circular edges of the same radius. For performance sake, only # the first edge of each expansion is used for constraint. We simply # translate the rest of the parts with the same relative offset. - touched = False - for partInfo in updates: - for indices,element in partInfo.Update: - element0 = element.Proxy.parent.Object.Group[0] - infos0 = element0.Proxy.getInfo(expand=True) - infos = element.Proxy.getInfo(expand=True) - pos0 = infos[0].Placement.multVec( - utils.getElementPos(infos[0].Shape)) - for idx0,idx in indices: - info = infos[idx] - pos = info.Placement.multVec( - utils.getElementPos(info.Shape)) - pla = partInfo.Placement.copy() - pla.Base += pos-pos0 - info0 = infos0[idx0] + for partInfo0 in updates: + for infoRef,shapeRef,pairs in partInfo0.Update: + refPos = infoRef.Placement.multVec( + utils.getElementPos(shapeRef)) + for info0,partInfo,shape in pairs: + pos = partInfo.Placement.multVec(utils.getElementPos(shape)) + pla = partInfo0.Placement.copy() + pla.Base += pos-refPos if isSamePlacement(info0.Placement,pla): self.system.log('not moving {}'.format(info0.PartName)) else: @@ -240,9 +231,11 @@ class Solver(object): info0.Part, info0.Placement.copy())) setPlacement(info0.Part,pla) - if touched: + + if recompute and touched: assembly.recompute(True) + def isFixedPart(self,part): if isinstance(part,tuple) and part[0] in self._fixedParts: return True @@ -255,6 +248,9 @@ class Solver(object): def addFixedElement(self,part,subname): self._fixedElements.add((part,subname)) + def getDragPart(self): + return self._dragPart + def countArrayPartConstraint(self,part): self._cstrArrayMap[part] += 1