diff --git a/assembly.py b/assembly.py index 4345be0..d30c384 100644 --- a/assembly.py +++ b/assembly.py @@ -25,6 +25,13 @@ def getProxy(obj,tp): checkType(obj,tp) return obj.Proxy +def getLinkProperty(obj,name,default): + try: + getter = getattr(obj.getLinkedObject(True),'getLinkExtProperty',None) + return getter(name) + except Exception: + return default + def resolveAssembly(obj): '''Try various ways to obtain an assembly from the input object @@ -86,6 +93,8 @@ class ViewProviderAsmBase(object): _addToSceneGraph = False def attach(self,vobj): + if hasattr(self,'ViewObject'): + return self.ViewObject = vobj vobj.signalChangeIcon() vobj.setPropertyStatus('Visibility','Hidden') @@ -142,9 +151,6 @@ class AsmGroup(AsmBase): obj.addProperty("App::PropertyEnumeration","GroupMode","Base",'') super(AsmGroup,self).attach(obj) - def allowDuplicateLabel(self,_obj): - return True - class ViewProviderAsmGroup(ViewProviderAsmBase): def claimChildren(self): @@ -176,6 +182,14 @@ class AsmPartGroup(AsmGroup): def canLoadPartial(self,_obj): return 1 if self.getAssembly().frozen else 0 + def onChanged(self,obj,prop): + if obj.Removing: + return + if prop == 'Group': + parent = getattr(self,'parent',None) + if parent: + parent.getRelationGroup().Proxy.getRelations(True) + @staticmethod def make(parent,name='Parts'): obj = parent.Document.addObject("Part::FeaturePython",name, @@ -190,9 +204,6 @@ class AsmPartGroup(AsmGroup): class ViewProviderAsmPartGroup(ViewProviderAsmGroup): _iconName = 'Assembly_Assembly_Part_Tree.svg' - def onDelete(self,_obj,_subs): - return False - def canDropObjectEx(self,obj,_owner,_subname,_elements): return isTypeOf(obj,Assembly, True) or not isTypeOf(obj,AsmBase) @@ -233,7 +244,6 @@ class ViewProviderAsmPartGroup(ViewProviderAsmGroup): class AsmElement(AsmBase): def __init__(self,parent): self._initializing = True - self.shape = None self.parent = getProxy(parent,AsmElementGroup) super(AsmElement,self).__init__() @@ -243,19 +253,23 @@ class AsmElement(AsmBase): obj.addProperty("App::PropertyPlacement","Offset"," Link",'') if not hasattr(obj,'Placement'): obj.addProperty("App::PropertyPlacement","Placement"," Link",'') - obj.setPropertyStatus('Placement',['Immutable','Hidden']) + obj.setPropertyStatus('Placement','Hidden') if not hasattr(obj,'LinkTransform'): obj.addProperty("App::PropertyBool","LinkTransform"," Link",'') obj.LinkTransform = True + if not hasattr(obj,'Detach'): + obj.addProperty('App::PropertyLink','Detach', ' Link','') obj.setPropertyStatus('LinkTransform',['Immutable','Hidden']) obj.configLinkProperty('LinkedObject','Placement','LinkTransform') obj.setPropertyStatus('LinkedObject','ReadOnly') - self.shape = None def attach(self,obj): obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'') super(AsmElement,self).attach(obj) + def getViewProviderName(self,_obj): + return '' + def canLinkProperties(self,_obj): return False @@ -267,46 +281,47 @@ class AsmElement(AsmBase): if parent and not getattr(self,'_initializing',False): return parent.onChildLabelChange(obj,label) - def onChanged(self,_obj,prop): + def onChanged(self,obj,prop): parent = getattr(self,'parent',None) - if not parent or FreeCAD.isRestoring(): + if not parent or obj.Removing or FreeCAD.isRestoring(): return if prop=='Offset': - self.update() + self.updatePlacement() elif prop == 'Label': parent.Object.cacheChildLabel() if prop not in _IgnoredProperties and \ not Constraint.isDisabled(parent.Object): Assembly.autoSolve() - def execute(self,_obj): - self.updatePlacement() + def execute(self,obj): + info = None + if not obj.Detach and hasattr(obj,'Shape'): + info = getElementInfo(self.getAssembly().getPartGroup(), + self.getElementSubname()) + shape = info.Shape + shape.transformShape(info.Placement.toMatrix()) + # make a compound to keep the shape's transformation + shape = Part.makeCompound(shape) + shape.ElementMap = info.Shape.ElementMap + obj.Shape = shape + + self.updatePlacement(info) return False - def updatePlacement(self): + def updatePlacement(self,info=None): obj = self.Object - info = getElementInfo(self.getAssembly().getPartGroup(), - self.getElementSubname(),offset=self.Object.Offset) - if not info: - return - self.shape = info.Shape - if not utils.isSamePlacement(info.PlacementOffset,obj.Placement): - obj.setPropertyStatus('Placement','-Immutable') - obj.Placement = info.PlacementOffset - obj.setPropertyStatus('Placement','Immutable') - - def freeze(self): - obj = self.Object - if self.getAssembly().Freeze: - if not self.shape: - self.updatePlacement(); - if self.shape: - # make a compound to contain the shape's transformation - shape = Part.makeCompound(self.shape) - shape.ElementMap = self.shape.ElementMap - obj.Shape = shape - elif not obj.Shape.isNull(): - obj.Shape = Part.Shape() + if obj.Offset.isIdentity(): + obj.Placement = FreeCAD.Placement() + else: + if not info: + info = getElementInfo(self.getAssembly().getPartGroup(), + self.getElementSubname()) + # obj.Offset is in the element shape's coordinate system, we need to + # transform it to the assembly coordinate system + mat = utils.getElementPlacement(info.Shape).toMatrix() + mat = info.Placement.toMatrix()*mat + obj.Placement = FreeCAD.Placement( + mat*obj.Offset.toMatrix()*mat.inverse()) def getAssembly(self): return self.parent.parent @@ -325,33 +340,7 @@ class AsmElement(AsmBase): return link[1] def getElementSubname(self): - ''' - Resolve the geometry element link relative to the parent assembly's part - group - ''' - - subname = self.getSubName() - obj = self.Object.getLinkedObject(False) - if not obj or obj == self.Object: - raise RuntimeError('Borken element link') - if not isTypeOf(obj,AsmElement): - # If not pointing to another element, then assume we are directly - # pointing to the geometry element, just return as it is, which is a - # subname relative to the parent assembly part group - return subname - - childElement = obj.Proxy - - # If pointing to another element in the child assembly, first pop two - # names in the subname reference, i.e. element label and element group - # name - idx = subname.rfind('.',0,subname.rfind('.',0,-1)) - subname = subname[:idx+1] - - # append the child assembly part group name, and recursively call into - # child element - return subname+childElement.getAssembly().getPartGroup().Name+'.'+\ - childElement.getElementSubname() + return self.getSubName() # Element: optional, if none, then a new element will be created if no # pre-existing. Or else, it shall be the element to be amended @@ -568,6 +557,8 @@ class AsmElement(AsmBase): class ViewProviderAsmElement(ViewProviderAsmOnTop): + _iconName = 'Assembly_Assembly_Element.svg' + def __init__(self,vobj): if hasattr(vobj,'OverrideMaterial'): vobj.OverrideMaterial = True @@ -603,22 +594,6 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop): AsmElement.make(AsmElement.Selection(Element=vobj.Object, Group=owner, Subname=subname+element),undo=True) - def updateData(self,obj,prop): - if not hasattr(self,'ViewObject') or FreeCAD.isRestoring(): - return - if prop == 'Shape': - vobj = obj.ViewObject - if obj.Shape.isNull(): - vobj.ChildViewProvider = '' - elif not vobj.ChildViewProvider: - vobj.ChildViewProvider = 'PartGui::ViewProviderPartExt' - vobj.DefaultMode = 1 - - def onFinishRestoring(self): - vobj = self.ViewObject - if getattr(vobj,'ChildViewProvider',None): - vobj.DefaultMode = 1 - class AsmElementSketch(AsmElement): def __init__(self,obj,parent): @@ -655,9 +630,6 @@ class AsmElementSketch(AsmElement): ret[0] = obj return ret - def freeze(self): - pass - class ViewProviderAsmElementSketch(ViewProviderAsmElement): def getIcon(self): @@ -680,9 +652,9 @@ class ViewProviderAsmElementSketch(ViewProviderAsmElement): ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part', - 'PartName','Placement','Object','Subname','Shape','PlacementOffset')) + 'PartName','Placement','Object','Subname','Shape')) -def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): +def getElementInfo(parent,subname,checkPlacement=False,shape=None): '''Return a named tuple containing the part object element information Parameters: @@ -697,8 +669,6 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): 'shape' is not given, then the output shape will be obtained through 'parent' and 'subname' - offset: an optional offset in the element shape's coordinate space - Return a named tuple with the following fields: Parent: set to the input parent object @@ -722,10 +692,6 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): Shape: Part.Shape of the linked element. The shape's placement is relative to the owner Part. - - PlacementOffset: if 'offset' is given, then this field holds the necessary - placement offset in assembly coordinate space to achieve an equivalant - 'offset', which is in element shape's coordinate space. ''' subnameRef = subname @@ -774,12 +740,10 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): obj = None if not isTypeOf(part,Assembly,True): - getter = getattr(part.getLinkedObject(True), - 'getLinkExtProperty',None) # special treatment of link array (i.e. when ElementCount!=0), we # allow the array element to be moveable by the solver - if getter and getter('ElementCount'): + if getLinkProperty(part,'ElementCount',0): # store both the part (i.e. the link array), and the array # element object @@ -789,7 +753,7 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): sub = '.'.join(names[2:]) # There are two states of an link array. - if getter('ElementList'): + if getLinkProperty(part[0],'ElementList',None): # a) The elements are expanded as individual objects, i.e # when ElementList has members, then the moveable Placement # is a property of the array element. So we obtain the shape @@ -799,7 +763,8 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): pla = part[1].Placement obj = part[0].getLinkedObject(False) partName = part[1].Name - part = part[1] + idx = int(partName.split('_i')[-1]) + part = (part[0],idx,part[1],False) else: # b) The elements are collapsed. Then the moveable Placement # is stored inside link object's PlacementList property. So, @@ -809,12 +774,12 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): shape=part[1].getSubObject(sub) obj = part[1] try: - idx = names[1].split('_i')[-1] + idx = int(names[1].split('_i')[-1]) # we store the array index instead, in order to modified # Placement later when the solver is done. Also because # that when the elements are collapsed, there is really # no element object here. - part = (part[0],int(idx),part[1]) + part = (part[0],idx,part[1],True) pla = part[0].PlacementList[idx] except ValueError: raise RuntimeError('invalid array subname of element {}: ' @@ -844,14 +809,6 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): if transformShape: shape.transformShape(pla.toMatrix().inverse()) - if not offset or offset.isIdentity(): - plaOffset = FreeCAD.Placement() - else: - mat = utils.getElementPlacement(shape).toMatrix() - shape.transformShape(mat*offset.toMatrix()*mat.inverse()) - mat = pla.toMatrix()*mat - plaOffset = FreeCAD.Placement(mat*offset.toMatrix()*mat.inverse()) - return ElementInfo(Parent = parent, SubnameRef = subnameRef, Part = part, @@ -859,14 +816,14 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None,offset=None): Placement = pla.copy(), Object = obj, Subname = subname, - Shape = shape, - PlacementOffset = plaOffset) + Shape = shape) class AsmElementLink(AsmBase): def __init__(self,parent): super(AsmElementLink,self).__init__() self.info = None + self.part = None self.parent = getProxy(parent,AsmConstraint) def linkSetup(self,obj): @@ -874,6 +831,7 @@ class AsmElementLink(AsmBase): obj.configLinkProperty('LinkedObject') obj.setPropertyStatus('LinkedObject','ReadOnly') self.info = None + self.part = None def attach(self,obj): obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'') @@ -883,11 +841,18 @@ class AsmElementLink(AsmBase): return False def execute(self,_obj): - self.info = None + info = self.getInfo(True) + if not self.part or self.part!=info.Part: + oldPart = self.part + self.part = info.Part + self.getAssembly().getRelationGroup().Proxy.update( + self.parent.Object,oldPart,info.Part,info.PartName) return False - def onChanged(self,_obj,prop): - if not getattr(self,'parent',None) or FreeCAD.isRestoring(): + def onChanged(self,obj,prop): + if obj.Removing or \ + not getattr(self,'parent',None) or \ + FreeCAD.isRestoring(): return if prop not in _IgnoredProperties and \ not Constraint.isDisabled(self.parent.Object): @@ -991,7 +956,7 @@ class AsmElementLink(AsmBase): pla: the new placement ''' if isinstance(part,tuple): - if isinstance(part[1],int): + if part[3]: part[0].PlacementList = {part[1]:pla} else: part[1].Placement = pla @@ -1037,21 +1002,22 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop): return mover.movePart() def canDropObjectEx(self,_obj,owner,subname,elements): - if not elements: - elements = [''] + if len(elements)>1: + return False + elif elements: + subname += elements[0] obj = self.ViewObject.Object - msg = 'Cannot drop to AsmLink {}'.format(objName(obj)) - for element in elements: - if not logger.catchTrace(msg, obj.Proxy.setLink, - owner,subname+element,True): - return False - return True + msg = 'Cannot drop to AsmElementLink {}'.format(objName(obj)) + if not logger.catchTrace(msg, obj.Proxy.setLink,owner,subname,True): + return True + return False def dropObjectEx(self,vobj,_obj,owner,subname,elements): - if not elements: - elements = [''] - for element in elements: - vobj.Object.Proxy.setLink(owner,subname+element) + if len(elements)>1: + return + elif elements: + subname += elements[0] + vobj.Object.Proxy.setLink(owner,subname) class AsmConstraint(AsmGroup): @@ -1088,7 +1054,7 @@ class AsmConstraint(AsmGroup): System.getTypeName(assembly))) def onChanged(self,obj,prop): - if prop not in _IgnoredProperties: + if not obj.Removing and prop not in _IgnoredProperties: Constraint.onChanged(obj,prop) Assembly.autoSolve() @@ -1383,9 +1349,6 @@ class ViewProviderAsmConstraintGroup(ViewProviderAsmGroup): def canDropObjects(self): return False - def onDelete(self,_obj,_subs): - return False - class AsmElementGroup(AsmGroup): def __init__(self,parent): @@ -1434,9 +1397,6 @@ class AsmElementGroup(AsmGroup): class ViewProviderAsmElementGroup(ViewProviderAsmGroup): _iconName = 'Assembly_Assembly_Element_Tree.svg' - def onDelete(self,_obj,_subs): - return False - def canDropObjectEx(self,_obj,owner,_subname,elements): if not owner or not elements: return False @@ -1461,6 +1421,320 @@ class ViewProviderAsmElementGroup(ViewProviderAsmGroup): sel.SubElementNames[0]+obj.Name+'.') +class AsmRelationGroup(AsmBase): + def __init__(self,parent): + self.relations = {} + self.parent = getProxy(parent,Assembly) + super(AsmRelationGroup,self).__init__() + + def attach(self,obj): + # AsmRelationGroup do not install LinkBaseExtension + # obj.addExtension('App::LinkBaseExtensionPython', None) + + obj.addProperty('App::PropertyLinkList','Group','') + obj.setPropertyStatus('Group','Hidden') + obj.addProperty('App::PropertyLink','Constraints','') + # this is to make sure relations are recomputed after all constraints + obj.Constraints = self.parent.getConstraintGroup() + obj.setPropertyStatus('Constraints',('Hidden','Immutable')) + self.linkSetup(obj) + + def getViewProviderName(self,_obj): + return '' + + def linkSetup(self,obj): + super(AsmRelationGroup,self).linkSetup(obj) + for o in obj.Group: + o.Proxy.parent = self + if o.Count: + for child in o.Group: + if isTypeOf(child,AsmRelation): + child.Proxy.parent = o.Proxy + + def getAssembly(self): + return self.parent + + def hasChildElement(self,_obj): + return True + + def isElementVisible(self,obj,element): + child = obj.Document.getObject(element) + if not child or not getattr(child,'Part',None): + return 0 + return self.parent.getPartGroup().isElementVisible(child.Part.Name) + + def setElementVisible(self,obj,element,vis): + child = obj.Document.getObject(element) + if not child or not getattr(child,'Part',None): + return 0 + return self.parent.getPartGroup().setElementVisible(child.Part.Name,vis) + + def canLoadPartial(self,_obj): + return 2 if self.getAssembly().frozen else 0 + + def getRelations(self,refresh=False): + if not refresh and getattr(self,'relations',None): + return self.relations + obj = self.Object + self.relations = {} + for o in obj.Group: + if o.Part: + self.relations[o.Part] = o + group = [] + relations = self.relations.copy() + touched = False + new = [] + for part in self.getAssembly().getPartGroup().Group: + o = relations.get(part,None) + if not o: + touched = True + new.append(AsmRelation.make(obj,part)) + group.append(new[-1]) + self.relations[part] = new[-1] + else: + group.append(o) + relations.pop(part) + + if relations or touched: + obj.Group = group + + for k,o in relations.items(): + self.relations.pop(k) + if o.Count: + for child in o.Group: + if isTypeOf(child,AsmRelation): + child.Document.removeObject(child.Name) + o.Document.removeObject(o.Name) + + for o in new: + o.Proxy.getConstraints() + + return self.relations + + def findRelation(self,part): + relations = self.getRelations() + if not isinstance(part,tuple): + return relations.get(part,None) + + relation = relations.get(part[0],None) + if not relation: + return + if part[1]>=relation.Count: + relation.recompute() + group = relation.Group + try: + relation = group[part[1]] + checkType(relation,AsmRelation) + return relation + except Exception as e: + logger.error('invalid relation of part array: '+str(e)) + + def update(self,cstr,oldPart,newPart,partName): + relation = self.findRelation(oldPart) + if relation: + try: + group = relation.Group + group.remove(cstr) + relation.Group = group + except ValueError: + pass + relation = self.findRelation(newPart) + if not relation: + logger.warn('Cannot find relation of part {}'.format(partName)) + elif cstr not in relation.Group: + relation.Group = {-1:cstr} + + @staticmethod + def make(parent,name='Relations'): + obj = parent.Document.addObject("App::FeaturePython",name, + AsmRelationGroup(parent),None,True) + ViewProviderAsmRelationGroup(obj.ViewObject) + obj.Label = name + obj.purgeTouched() + return obj + + +class ViewProviderAsmRelationGroup(ViewProviderAsmBase): + _iconName = 'Assembly_Assembly_Relation_Tree.svg' + + def canDropObjects(self): + return False + + def canDelete(self,_obj): + return False + + def claimChildren(self): + return self.ViewObject.Object.Group + + +class AsmRelation(AsmBase): + def __init__(self,parent): + self.parent = getProxy(parent,(AsmRelationGroup,AsmRelation)) + super(AsmRelation,self).__init__() + + def linkSetup(self,obj): + super(AsmRelation,self).linkSetup(obj) + obj.configLinkProperty(LinkedObject = 'Part') + + def attach(self,obj): + obj.addProperty("App::PropertyLink","Part"," Link",'') + obj.setPropertyStatus('Part','ReadOnly') + obj.addProperty("App::PropertyInteger","Count"," Link",'') + obj.setPropertyStatus('Count','Hidden') + obj.addProperty("App::PropertyInteger","Index"," Link",'') + obj.setPropertyStatus('Index','Hidden') + obj.addProperty('App::PropertyLinkList','Group','') + obj.setPropertyStatus('Group','Hidden') + super(AsmRelation,self).attach(obj) + + def getAssembly(self): + return self.parent.getAssembly() + + def updateLabel(self): + obj = self.Object + if obj.Part: + obj.Label = obj.Part.Label + + def execute(self,obj): + part = obj.Part + if not part: + return False + + if not isinstance(self.parent,AsmRelationGroup): + return False + + count = getLinkProperty(part,'ElementCount',0) + remove = [] + if obj.Count > count: + group = obj.Group + remove = group[count:] + obj.Group = group[:count] + for o in remove: + if isTypeOf(o,AsmRelation): + o.Document.removeObject(o.Name) + obj.Count = count + self.getConstraints() + elif obj.Count < count: + new = [] + for i in xrange(obj.Count,count): + new.append(AsmRelation.make(obj,(part,i))) + obj.Count = count + obj.Group = obj.Group[:obj.Count]+new + for o in new: + o.Proxy.getConstraints() + + return False + + def allowDuplicateLabel(self,_obj): + return True + + def hasChildElement(self,_obj): + return True + + def _getGroup(self): + if isinstance(self.parent,AsmRelation): + return self.parent.Object.Part + return self.getAssembly().getConstraintGroup() + + def isElementVisible(self,obj,element): + if not obj.Part: + return + child = obj.Document.getObject(element) + if isTypeOf(child,AsmRelation): + group = obj.Part + element = str(child.Index) + else: + group = self.getAssembly().getConstraintGroup() + return group.isElementVisible(element) + + def setElementVisible(self,obj,element,vis): + if not obj.Part: + return + child = obj.Document.getObject(element) + if isTypeOf(child,AsmRelation): + group = obj.Part + element = str(child.Index) + else: + group = self.getAssembly().getConstraintGroup() + return group.setElementVisible(element,vis) + + def redirectSubName(self,obj,subname,_topParent,child): + if not obj.Part: + return + if isinstance(self.parent,AsmRelation): + subname = subname.split('.') + if not child: + subname[-3] = self.getAssembly().getPartGroup().Name + subname[-2] = obj.Part.Name + subname[-1] = str(obj.Index) + subname.append('') + else: + subname[-3] = self.getAssembly().getConstraintGroup().Name + subname[-2] = '' + subname = subname[:-1] + elif not child: + subname = subname.split('.') + subname[-2] = self.getAssembly().getPartGroup().Name + subname[-1] = obj.Part.Name + subname.append('') + elif isTypeOf(child,AsmConstraint): + subname = subname.split('.') + subname[-2] = self.getAssembly().getConstraintGroup().Name + else: + return + return '.'.join(subname) + + def getConstraints(self): + obj = self.Object + if obj.Count or not obj.Part: + return + if isinstance(self.parent,AsmRelation): + part = (obj.Part,obj.Index) + else: + part = obj.Part + group = [] + for cstr in self.getAssembly().getConstraintGroup().Group: + for element in cstr.Group: + info = element.Proxy.getInfo() + if isinstance(info.Part,tuple): + infoPart = info.Part[:2] + else: + infoPart = info.Part + if infoPart==part: + group.append(cstr) + break + obj.Group = group + + @staticmethod + def make(parent,part,name='Relation'): + obj = parent.Document.addObject("App::FeaturePython",name, + AsmRelation(parent),None,True) + ViewProviderAsmRelation(obj.ViewObject) + if isinstance(part,tuple): + obj.setLink(part[0]) + obj.Index = part[1] + obj.Label = str(part[1]) + else: + obj.setLink(part) + obj.Label = part.Label + obj.recompute() + obj.setPropertyStatus('Index','Immutable') + obj.purgeTouched() + return obj + + +class ViewProviderAsmRelation(ViewProviderAsmBase): + + def canDropObjects(self): + return False + + def canDelete(self,_obj): + return False + + def claimChildren(self): + return self.ViewObject.Object.Group + + BuildShapeNone = 'None' BuildShapeCompound = 'Compound' BuildShapeFuse = 'Fuse' @@ -1479,11 +1753,9 @@ class Assembly(AsmGroup): self.partArrays = set() self.constraints = None self.frozen = False + self.deleting = False super(Assembly,self).__init__() - def allowDuplicateLabel(self,_obj): - return False - def getSubObjects(self,_obj,reason): partGroup = self.getPartGroup() return ['{}.{}'.format(partGroup.Name,name) @@ -1544,6 +1816,15 @@ class Assembly(AsmGroup): @classmethod def checkPartChange(cls, obj, prop): + if prop == 'Label': + try: + cls._PartMap.get(obj).getRelationGroup().\ + Proxy.findRelation(obj).\ + Proxy.updateLabel() + except Exception: + pass + return + if not cls.canAutoSolve() or prop in _IgnoredProperties: return assembly = None @@ -1632,10 +1913,12 @@ class Assembly(AsmGroup): elementGroup.cacheChildLabel() for element in old: - old.Document.removeObject(old) + element.Document.removeObject(element.Name) self.Object.setLink({2:newPartGroup}) - partGroup.Document.removeObject(partGroup) + partGroup.Document.removeObject(partGroup.Name) + + elementGroup.recompute(True) def buildShape(self): obj = self.Object @@ -1721,12 +2004,14 @@ class Assembly(AsmGroup): # group, and finally part group. Call getPartGroup below will make sure # all groups exist. The order of the group is important to make sure # correct rendering and picking behavior - self.getPartGroup(True) + self.getRelationGroup(True) self.onChanged(obj,'BuildShape') def onChanged(self, obj, prop): - if not getattr(self,'Object',None) or FreeCAD.isRestoring(): + if obj.Removing or \ + not getattr(self,'Object',None) or \ + FreeCAD.isRestoring(): return if prop == 'BuildShape': self.buildShape() @@ -1735,8 +2020,6 @@ class Assembly(AsmGroup): if obj.Freeze == self.frozen: return self.upgrade() - for o in self.getElementGroup().Group: - self.freeze(o) if obj.BuildShape==BuildShapeNone: self.buildShape() elif obj.Freeze: @@ -1847,6 +2130,30 @@ class Assembly(AsmGroup): obj.setLink({2:ret}) return ret + def getRelationGroup(self,create=False): + obj = self.Object + if create: + # make sure previous group exists + self.getPartGroup(True) + if obj.Freeze: + return None + try: + ret = obj.Group[3] + checkType(ret,AsmRelationGroup) + parent = getattr(ret.Proxy,'parent',None) + if not parent: + ret.Proxy.parent = self + elif parent!=self: + raise RuntimeError( + 'invalid parent of relation group {}'.format(objName(ret))) + return ret + except IndexError: + if not create: + raise RuntimeError('Missing relation group') + ret = AsmRelationGroup.make(obj) + obj.setLink({3:ret}) + return ret + @staticmethod def make(doc=None,name='Assembly',undo=True): if not doc: @@ -1980,12 +2287,27 @@ class ViewProviderAssembly(ViewProviderAsmGroup): vobj.addProperty("App::PropertyBool","ShowParts"," Link") def onDelete(self,vobj,_subs): - for o in vobj.Object.Proxy.getPartGroup().Group: - if o.TypeId == 'App::Origin': + assembly = vobj.Object.Proxy + for o in assembly.getPartGroup().Group: + if o.isDerivedFrom('App::Origin'): o.Document.removeObject(o.Name) break + relationGroup = assembly.getRelationGroup() + relations = relationGroup.Group + relationGroup.Group = [] + for o in relations: + if o.Count: + group = o.Group + o.Group = [] + for child in group: + if isTypeOf(child,AsmRelation): + child.Document.removeObject(child.Name) + o.Document.removeObject(o.Name) return True + def canDelete(self,_obj): + return False + def _convertSubname(self,owner,subname): sub = subname.split('.') if not sub: