assembly: support label based element link

This commit is contained in:
Zheng, Lei 2017-12-01 16:59:13 +08:00
parent 15babab61d
commit 1960144ef2

View File

@ -63,8 +63,6 @@ class AsmBase(object):
def onDocumentRestored(self, obj): def onDocumentRestored(self, obj):
self.linkSetup(obj) self.linkSetup(obj)
def onChanged(self,_obj,_prop):
pass
class ViewProviderAsmBase(object): class ViewProviderAsmBase(object):
def __init__(self,vobj): def __init__(self,vobj):
@ -150,6 +148,9 @@ class AsmPartGroup(AsmGroup):
self.parent = getProxy(parent,Assembly) self.parent = getProxy(parent,Assembly)
super(AsmPartGroup,self).__init__() super(AsmPartGroup,self).__init__()
def getAssembly(self):
return self.parent
def groupSetup(self): def groupSetup(self):
pass pass
@ -183,6 +184,7 @@ class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
class AsmElement(AsmBase): class AsmElement(AsmBase):
def __init__(self,parent): def __init__(self,parent):
self._initializing = True
self.shape = None self.shape = None
self.parent = getProxy(parent,AsmElementGroup) self.parent = getProxy(parent,AsmElementGroup)
super(AsmElement,self).__init__() super(AsmElement,self).__init__()
@ -200,6 +202,21 @@ class AsmElement(AsmBase):
def canLinkProperties(self,_obj): def canLinkProperties(self,_obj):
return False return False
def allowDuplicateLabel(self,_obj):
return True
def onBeforeChangeLabel(self,obj,label):
parent = getattr(self,'parent',None)
if parent and not getattr(self,'_initializing',False):
return parent.onChildLabelChange(obj,label)
def onChanged(self,_obj,prop):
parent = getattr(self,'parent',None)
if parent and \
not getattr(self,'_initializing',False) and \
prop=='Label':
parent.Object.cacheChildLabel()
def execute(self,_obj): def execute(self,_obj):
self.getShape(True) self.getShape(True)
return False return False
@ -229,35 +246,46 @@ class AsmElement(AsmBase):
objName(self.Object))) objName(self.Object)))
return link[1] return link[1]
def setLink(self,owner,subname): def getElementSubname(self):
# subname must be relative to the part group object of the parent '''
# assembly Resolve the geometry element link relative to the parent assembly's part
group
'''
# check old linked object for auto re-label subname = self.getSubName()
obj = self.Object obj = self.Object.getLinkedObject(False)
linked = obj.getLinkedObject(False) if not obj or obj == self.Object:
if linked and linked!=obj: raise RuntimeError('Borken element link')
label = '{}_{}_Element'.format(linked.Label,self.getSubElement()) if not isTypeOf(obj,AsmElement):
else: # If not pointing to another element, then assume we are directly
label = '' # pointing to the geometry element, just return as it is, which is a
# subname relative to the parent assembly part group
return subname
obj.setLink(owner,subname) childElement = obj.Proxy
if obj.Label==obj.Name or obj.Label.startswith(label): # If pointing to another element in the child assembly, first pop two
linked = obj.getLinkedObject(False) # names in the subname reference, i.e. element label and element group
if linked and linked!=obj: # name
obj.Label = '{}_{}_Element'.format( idx = subname.rfind('.',0,subname.rfind('.',0,-1))
linked.Label,self.getSubElement()) subname = subname[:idx+1]
else:
obj.Label = obj.Name
Selection = namedtuple('AsmElementSelection', # append the child assembly part group name, and recursively call into
('Assembly','Element','Subname')) # child element
return subname+childElement.getAssembly().getPartGroup().Name+'.'+\
childElement.getElementSubname()
# 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
# Group: the immediate child object of an assembly (i.e. ConstraintGroup,
# ElementGroup, or PartGroup)
# Subname: the subname reference realtive to 'Group'
Selection = namedtuple('AsmElementSelection',('Element','Group','Subname'))
@staticmethod @staticmethod
def getSelection(): def getSelection():
''' '''
Parse Gui.Selection for making a element Parse Gui.Selection for making an element
If there is only one selection, then the selection must refer to a sub If there is only one selection, then the selection must refer to a sub
element of some part object of an assembly. We shall create a new element of some part object of an assembly. We shall create a new
@ -314,32 +342,102 @@ class AsmElement(AsmBase):
if not isTypeOf(element,AsmElement): if not isTypeOf(element,AsmElement):
raise RuntimeError('The second selection must be an element') raise RuntimeError('The second selection must be an element')
return AsmElement.Selection(Assembly = link.Assembly, return AsmElement.Selection(Element=element, Group=link.Object,
Element = element, Subname=link.Subname+subElement)
Subname = link.Subname+subElement)
@staticmethod @staticmethod
def make(selection=None,name='Element'): def make(selection=None,name='Element'):
'''Add/get/modify an element with the given selected object'''
if not selection: if not selection:
selection = AsmElement.getSelection() selection = AsmElement.getSelection()
if not selection.Subname or selection.Subname[-1]=='.':
raise RuntimeError('Subname must refer to a sub-element') group = selection.Group
assembly = getProxy(selection.Assembly,Assembly) subname = selection.Subname
if isTypeOf(group,AsmElementGroup):
# if the selected object is an element of the owner assembly, simply
# return that element
element = group.getSubObject(subname,1)
if not isTypeOf(element,AsmElement):
raise RuntimeError('Invalid element reference {}.{}'.format(
group.Name,subname))
return element
if isTypeOf(group,AsmConstraintGroup):
# if the selected object is an element link of a constraint of the
# current assembly, then try to import its linked element if it is
# not already imported
link = group.getSubObject(subname,1)
if not isTypeOf(link,AsmElementLink):
raise RuntimeError('Invalid element link {}.{}'.format(
group.Name,subname))
ref = link.LinkedObject
if not isinstance(ref,tuple):
raise RuntimeError('Invalid link reference {}.{}'.format(
group.Name,subname))
if ref[1][0]=='$':
# this means the element is in the current assembly already
element = link.getLinkedObject(False)
if not isTypeOf(element,AsmElement):
raise RuntimeError('broken element link {}.{}'.format(
group.Name,subname))
return element
subname = ref[1]
group = group.getAssembly().getPartGroup()
elif isTypeOf(group,AsmPartGroup):
# If the selection come from the part group, first check for any
# intermediate child assembly
ret = Assembly.find(group,subname)
if not ret:
# If no child assembly in 'subname', simply assign the link as
# it is, after making sure it is referencing an sub-element
if not subname or subname[-1]=='.':
raise RuntimeError(
'Element must reference a geometry element')
else:
# In case there are intermediate assembly inside subname, we'll
# recursively export the element in child assemblies first, and
# then import that element to the current assembly.
sel = AsmElement.Selection(
Element=None, Group=ret.Object, Subname=ret.Subname)
element = AsmElement.make(sel)
# now generate the subname reference
# This give us reference to child assembly's immediate child
# without trailing dot.
prefix = subname[:len(subname)-len(ret.Subname)-1]
# Pop the immediate child name, and replace it with child
# assembly's element group name
prefix = prefix[:prefix.rfind('.')+1] + \
ret.Assembly.Proxy.getElementGroup().Name
subname = '{}.${}.'.format(prefix,element.Label)
else:
raise RuntimeError('Invalid selection {}.{}'.format(
group.Name,subname))
element = selection.Element element = selection.Element
if not element: if not element:
elements = assembly.getElementGroup() elements = group.Proxy.getAssembly().getElementGroup()
# try to search the element group for an existing element # try to search the element group for an existing element
for e in elements.Group: for e in elements.Group:
sub = logger.catch('',e.Proxy.getSubName) sub = logger.catch('',e.Proxy.getSubName)
if sub == selection.Subname: if sub == subname:
return e return e
element = elements.Document.addObject("App::FeaturePython", element = elements.Document.addObject("App::FeaturePython",
name,AsmElement(elements),None,True) name,AsmElement(elements),None,True)
ViewProviderAsmElement(element.ViewObject) ViewProviderAsmElement(element.ViewObject)
elements.setLink({-1:element}) elements.setLink({-1:element})
elements.setElementVisible(element.Name,False)
element.Proxy._initializing = False
elements.cacheChildLabel()
getProxy(element,AsmElement).setLink( element.setLink(group,subname)
assembly.getPartGroup(),selection.Subname)
return element return element
@ -357,17 +455,14 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
return (60.0/255.0,1.0,1.0) return (60.0/255.0,1.0,1.0)
def canDropObjectEx(self,_obj,owner,subname): def canDropObjectEx(self,_obj,owner,subname):
# check if is dropping a sub-element if not subname:
if not subname or subname[-1]=='.':
return False return False
proxy = self.ViewObject.Object.Proxy proxy = self.ViewObject.Object.Proxy
return proxy.getAssembly().getPartGroup()==owner return proxy.getAssembly().getPartGroup()==owner
def dropObjectEx(self,vobj,_obj,_owner,subname): def dropObjectEx(self,vobj,_obj,owner,subname):
obj = vobj.Object AsmElement.make(AsmElement.Selection(Element=vobj.Object,
AsmElement.make(AsmElement.Selection( Group=owner, Subname=subname))
Assembly=obj.Proxy.getAssembly().Object,
Element=obj, Subname=subname))
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part', PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
@ -543,103 +638,64 @@ class AsmElementLink(AsmBase):
self.getInfo(True) self.getInfo(True)
return False return False
def getElement(self):
linked = self.Object.getLinkedObject(False)
if not linked:
raise RuntimeError('Element link broken')
if not isTypeOf(linked,AsmElement):
raise RuntimeError('Invalid element type')
return linked.Proxy
def getAssembly(self): def getAssembly(self):
return self.parent.parent.parent return self.parent.parent.parent
def getSubName(self):
link = self.Object.LinkedObject
if not isinstance(link,tuple):
raise RuntimeError('Invalid element link "{}"'.format(
objName(self.Object)))
return link[1]
def getElementSubname(self): def getElementSubname(self):
'''Resolve element link subname 'Resolve element link subname'
AsmElementLink links to elements of a constraint. It always link to an # AsmElementLink is used by constraint to link to a geometry link. It
AsmElement object belonging to the same parent assembly. AsmElement is # does so by indirectly linking to an AsmElement object belonging to
also a link, which links to an arbitrarily nested part object. This # the same parent assembly. AsmElement is also a link, which again
function is for resolving the AsmElementLink's subname reference to the # links to another AsmElement of a child assembly or the actual
actual part object subname reference relative to the parent assembly's # geometry element of a child feature. This function is for resolving
part group # the AsmElementLink's subname reference to the actual part object
''' # subname reference relative to the parent assembly's part group
element = self.getElement()
linked = self.Object.getLinkedObject(False)
if not linked or linked == self.Object:
raise RuntimeError('Element link broken')
element = getProxy(linked,AsmElement)
assembly = element.getAssembly() assembly = element.getAssembly()
if assembly == self.getAssembly(): if assembly == self.getAssembly():
return element.getSubName() return element.getElementSubname()
# pop two names from back (i.e. element group, element)
subname = self.getSubName()
sub = subname.split('.')[:-3]
sub = '.'.join(sub) + '.' + assembly.getPartGroup().Name + \
'.' + element.getSubName()
logger.debug('shape subname {} -> {}'.format(subname,sub))
return sub
def prepareLink(self,owner,subname,checkOnly=False): # The reference stored inside this ElementLink. We need the sub assembly
if not owner or not subname: # name, which is the name before the first dot. This name may be
raise RuntimeError('no owner or subname') # different from the actual assembly object's name, in case where the
assembly = self.getAssembly() # assembly is accessed through a link
sobj = owner.getSubObject(subname,1) ref = self.Object.LinkedObject[1]
if not sobj: return '{}.{}.{}'.format(ref[0:ref.find('.')],
raise RuntimeError('invalid element link {} broken: {}'.format( assembly.getPartGroup().Name, element.getElementSubname())
objName(owner),subname))
if isTypeOf(sobj,AsmElementLink):
# if it points to another AsElementLink that belongs the same
# assembly, simply return the same link
if sobj.Proxy.getAssembly() == assembly:
return sobj.LinkedObject
# If it is from another assembly (i.e. a nested assembly), convert
# the subname reference by poping three names (constraint group,
# constraint, element link) from the back, and then append with the
# element link's own subname reference
sub = subname.split('.')[:-4]
sub = '.'.join(subname)+'.'+sobj.Proxy.getSubName()
logger.debug('convert element link {} -> {}'.format(subname,sub))
return (owner,sub)
if isTypeOf(sobj,AsmElement):
return (owner,subname)
# try to see if the reference comes from some nested assembly
ret = assembly.find(owner,subname,recursive=True)
if not ret:
# It is from a non assembly child part, then use our own element
# group as the holder for elements
ret = [Assembly.Info(assembly.Object,owner,subname)]
if not isTypeOf(ret[-1].Object,AsmPartGroup):
raise RuntimeError('Invalid element link ' + subname)
if checkOnly:
if not ret[-1].Subname or ret[-1].Subname[-1]=='.':
raise RuntimeError('Subname must refer to a sub-element')
return True
# call AsmElement.make to either create a new element, or an existing
# element if there is one
element = AsmElement.make(AsmElement.Selection(
ret[-1].Assembly,None,ret[-1].Subname))
if ret[-1].Assembly == assembly.Object:
return (assembly.getElementGroup(),element.Name+'.')
elementSub = ret[-1].Object.Name + '.' + ret[-1].Subname
sub = subname[:-(len(elementSub)+1)] + '.' + \
ret[-1].Assembly.Proxy.getElementGroup().Name + '.' + \
element.Name + '.'
logger.debug('generate new element {} -> {}'.format(subname,sub))
return (owner,sub)
def setLink(self,owner,subname): def setLink(self,owner,subname):
obj = self.Object # check if there is any sub assembly in the reference
owner,subname = self.prepareLink(owner,subname) ret = Assembly.find(owner,subname)
if not ret:
# if not, add/get an element in our own element group
sel = AsmElement.Selection(Element=None, Group=owner,
Subname=subname)
element = AsmElement.make(sel)
owner = element.Proxy.parent.Object
subname = '${}.'.format(element.Label)
else:
# if so, add/get an element from the sub assembly
sel = AsmElement.Selection(Element=None, Group=ret.Object,
Subname=ret.Subname)
element = AsmElement.make(sel)
owner = owner.Proxy.getAssembly().getPartGroup()
# This give us reference to child assembly's immediate child
# without trailing dot.
prefix = subname[:len(subname)-len(ret.Subname)-1]
# Pop the immediate child name, and replace it with child
# assembly's element group name
prefix = prefix[:prefix.rfind('.')+1] + \
ret.Assembly.Proxy.getElementGroup().Name
subname = '{}.${}.'.format(prefix, element.Label)
for sibling in self.parent.Object.Group: for sibling in self.parent.Object.Group:
if sibling == self.Object: if sibling == self.Object:
continue continue
@ -648,15 +704,7 @@ class AsmElementLink(AsmBase):
linked[0]==owner and linked[1]==subname: linked[0]==owner and linked[1]==subname:
raise RuntimeError('duplicate element link {} in constraint ' raise RuntimeError('duplicate element link {} in constraint '
'{}'.format(objName(sibling),objName(self.parent.Object))) '{}'.format(objName(sibling),objName(self.parent.Object)))
obj.setLink(owner,subname) self.Object.setLink(owner,subname)
linked = obj.getLinkedObject(False)
if linked and linked!=obj:
label = linked.Label.split('_')
if label[-1].startswith('Element'):
label[-1] = 'Link'
obj.Label = '_'.join(label)
else:
obj.Label = obj.Name
def getInfo(self,refresh=False): def getInfo(self,refresh=False):
if not refresh: if not refresh:
@ -694,12 +742,12 @@ class AsmElementLink(AsmBase):
@staticmethod @staticmethod
def make(info,name='ElementLink'): def make(info,name='ElementLink'):
element = info.Constraint.Document.addObject("App::FeaturePython", link = info.Constraint.Document.addObject("App::FeaturePython",
name,AsmElementLink(info.Constraint),None,True) name,AsmElementLink(info.Constraint),None,True)
ViewProviderAsmElementLink(element.ViewObject) ViewProviderAsmElementLink(link.ViewObject)
info.Constraint.setLink({-1:element}) info.Constraint.setLink({-1:link})
element.Proxy.setLink(info.Owner,info.Subname) link.Proxy.setLink(info.Owner,info.Subname)
return element return link
def setPlacement(part,pla,undoDocs,undoName=None): def setPlacement(part,pla,undoDocs,undoName=None):
AsmElementLink.setPlacement(part,pla,undoDocs,undoName) AsmElementLink.setPlacement(part,pla,undoDocs,undoName)
@ -755,7 +803,6 @@ class AsmConstraint(AsmGroup):
System.getTypeName(assembly))) System.getTypeName(assembly)))
def onChanged(self,obj,prop): def onChanged(self,obj,prop):
super(AsmConstraint,self).onChanged(obj,prop)
if Constraint.onChanged(obj,prop): if Constraint.onChanged(obj,prop):
obj.recompute() obj.recompute()
@ -945,7 +992,7 @@ class AsmConstraint(AsmGroup):
raise raise
class ViewProviderAsmConstraint(ViewProviderAsmGroupOnTop): class ViewProviderAsmConstraint(ViewProviderAsmGroup):
def __init__(self,vobj): def __init__(self,vobj):
vobj.OverrideMaterial = True vobj.OverrideMaterial = True
vobj.ShapeMaterial.DiffuseColor = self.getDefaultColor() vobj.ShapeMaterial.DiffuseColor = self.getDefaultColor()
@ -1002,6 +1049,9 @@ class AsmConstraintGroup(AsmGroup):
self.parent = getProxy(parent,Assembly) self.parent = getProxy(parent,Assembly)
super(AsmConstraintGroup,self).__init__() super(AsmConstraintGroup,self).__init__()
def getAssembly(self):
return self.parent
def linkSetup(self,obj): def linkSetup(self,obj):
super(AsmConstraintGroup,self).linkSetup(obj) super(AsmConstraintGroup,self).linkSetup(obj)
obj.setPropertyStatus('VisibilityList','Output') obj.setPropertyStatus('VisibilityList','Output')
@ -1037,10 +1087,29 @@ class AsmElementGroup(AsmGroup):
obj.setPropertyStatus('VisibilityList','Output') obj.setPropertyStatus('VisibilityList','Output')
for o in obj.Group: for o in obj.Group:
getProxy(o,AsmElement).parent = self getProxy(o,AsmElement).parent = self
obj.cacheChildLabel()
def getAssembly(self): def getAssembly(self):
return self.parent return self.parent
def onChildLabelChange(self,obj,label):
names = set()
for o in self.Object.Group:
if o != obj:
names.add(o.Name)
if label not in names:
return
for i,c in enumerate(reversed(label)):
if not c.isdigit():
label = label[:i+1]
break;
i=0
while True:
i=i+1;
newLabel = '{}{03d}'.format(label,i);
if newLabel!=obj.Label and newLabel not in names:
return newLabel
@staticmethod @staticmethod
def make(parent,name='Elements'): def make(parent,name='Elements'):
obj = parent.Document.addObject("App::FeaturePython",name, obj = parent.Document.addObject("App::FeaturePython",name,
@ -1057,16 +1126,14 @@ class ViewProviderAsmElementGroup(ViewProviderAsmGroupOnTop):
return False return False
def canDropObjectEx(self,_obj,owner,subname): def canDropObjectEx(self,_obj,owner,subname):
# check if is dropping a sub-element if not subname:
if not subname or subname[-1]=='.':
return False return False
proxy = self.ViewObject.Object.Proxy proxy = self.ViewObject.Object.Proxy
return proxy.getAssembly().getPartGroup()==owner return proxy.getAssembly().getPartGroup()==owner
def dropObjectEx(self,vobj,_obj,_owner,subname): def dropObjectEx(self,_vobj,_obj,owner,subname):
AsmElement.make(AsmElement.Selection( AsmElement.make(AsmElement.Selection(
Assembly=vobj.Object.Proxy.getAssembly().Object, Element=None, Group=owner, Subname=subname))
Element=None, Subname=subname))
BuildShapeNone = 'None' BuildShapeNone = 'None'
@ -1163,7 +1230,6 @@ class Assembly(AsmGroup):
obj.setPropertyStatus('Shape','Transient') obj.setPropertyStatus('Shape','Transient')
return return
System.onChanged(obj,prop) System.onChanged(obj,prop)
super(Assembly,self).onChanged(obj,prop)
def getConstraintGroup(self, create=False): def getConstraintGroup(self, create=False):
obj = self.Object obj = self.Object
@ -1401,6 +1467,8 @@ class AsmMovingPart(object):
# in case the shape has no normal, like a vertex, just use an empty # in case the shape has no normal, like a vertex, just use an empty
# rotation, which means having the same rotation has the owner part. # rotation, which means having the same rotation has the owner part.
rot = FreeCAD.Rotation() rot = FreeCAD.Rotation()
hasBound = True
if not utils.isVertex(shape): if not utils.isVertex(shape):
self.bbox = shape.BoundBox self.bbox = shape.BoundBox
else: else:
@ -1411,8 +1479,15 @@ class AsmMovingPart(object):
logger.warn('empty bounding box of part {}'.format( logger.warn('empty bounding box of part {}'.format(
info.PartName)) info.PartName))
self.bbox = FreeCAD.BoundBox(0,0,0,5,5,5) self.bbox = FreeCAD.BoundBox(0,0,0,5,5,5)
hasBound = False
pla = FreeCAD.Placement(utils.getElementPos(shape),rot) pos = utils.getElementPos(shape)
if not pos:
if hasBound:
pos = self.bbox.Center
else:
pos = shape.Placement.Base
pla = FreeCAD.Placement(pos,rot)
self.offset = pla.copy() self.offset = pla.copy()
self.offsetInv = pla.inverse() self.offsetInv = pla.inverse()
@ -1590,6 +1665,9 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
partGroup,owner,subname = info partGroup,owner,subname = info
return partGroup.canDropObject(obj,owner,subname) return partGroup.canDropObject(obj,owner,subname)
def canDragAndDropObject(self,_obj):
return True
def dropObjectEx(self,_vobj,obj,owner,subname): def dropObjectEx(self,_vobj,obj,owner,subname):
info = self._convertSubname(owner,subname) info = self._convertSubname(owner,subname)
if not info: if not info: