assembly: improve constraint mutliplication

It can now auto change any part into link array if necessary.
This commit is contained in:
Zheng, Lei 2019-05-19 11:40:44 +08:00
parent 049eca5bdb
commit ce3193946a
2 changed files with 144 additions and 75 deletions

View File

@ -27,7 +27,7 @@ def getProxy(obj,tp):
def getLinkProperty(obj,name,default=None,writable=False): def getLinkProperty(obj,name,default=None,writable=False):
try: try:
obj = obj.getLinkedObject(True) # obj = obj.getLinkedObject(True)
if not writable: if not writable:
return obj.getLinkExtProperty(name) return obj.getLinkExtProperty(name)
name = obj.getLinkExtPropertyName(name) name = obj.getLinkExtPropertyName(name)
@ -38,7 +38,7 @@ def getLinkProperty(obj,name,default=None,writable=False):
return default return default
def setLinkProperty(obj,name,val): def setLinkProperty(obj,name,val):
obj = obj.getLinkedObject(True) # obj = obj.getLinkedObject(True)
setattr(obj,obj.getLinkExtPropertyName(name),val) setattr(obj,obj.getLinkExtPropertyName(name),val)
def flattenSubname(obj,subname): def flattenSubname(obj,subname):
@ -46,7 +46,7 @@ def flattenSubname(obj,subname):
Falttern any AsmPlainGroups inside subname path. Only the first encountered Falttern any AsmPlainGroups inside subname path. Only the first encountered
assembly along the subname path is considered assembly along the subname path is considered
''' '''
func = getattr(obj,'flattenSubname',None) func = getattr(obj,'flattenSubname',None)
if not func: if not func:
return subname return subname
@ -406,6 +406,19 @@ class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
cvp.ForceMapColors = True cvp.ForceMapColors = True
vobj.DefaultMode = mode vobj.DefaultMode = mode
def replaceObject(self,oldObj,newObj):
res = self.ViewObject.replaceObject(oldObj,newObj)
if res<=0:
return res
for obj in oldObj.InList:
if isTypeOf(obj,AsmElement):
link = obj.LinkedObject
if isinstance(link,tuple):
obj.setLink(newObj,link[1])
else:
obj.setLink(newObj)
return 1
class AsmVersion(object): class AsmVersion(object):
def __init__(self,v=None): def __init__(self,v=None):
@ -877,7 +890,7 @@ class AsmElement(AsmBase):
# it is, after making sure it is referencing an sub-element # it is, after making sure it is referencing an sub-element
if not utils.isElement((group,subname)): if not utils.isElement((group,subname)):
raise RuntimeError( 'Element must reference a geometry ' raise RuntimeError( 'Element must reference a geometry '
'element {} {}'.format(objName(group),subname)) 'element {}.{}'.format(objName(group),subname))
else: else:
# In case there are intermediate assembly inside subname, we'll # In case there are intermediate assembly inside subname, we'll
# recursively export the element in child assemblies first, and # recursively export the element in child assemblies first, and
@ -1236,41 +1249,42 @@ def getElementInfo(parent,subname,
# special treatment of link array (i.e. when ElementCount!=0), we # special treatment of link array (i.e. when ElementCount!=0), we
# allow the array element to be moveable by the solver # allow the array element to be moveable by the solver
if getLinkProperty(part,'ElementCount'): if getLinkProperty(part,'ElementCount'):
# Handle old element reference before this link is expanded to
# array.
if not names[1]: if not names[1]:
names[1] = '0' names[1] = '0'
names.append('') names.append('')
elif len(names) == 2:
names.insert(1,'0')
# store both the part (i.e. the link array), and the array # store both the part (i.e. the link array), and the array
# element object # element object
part = (part,part.getSubObject(names[1]+'.',1)) part = (part,part.getSubObject(names[1]+'.',1))
if not part[1]:
raise RuntimeError('Cannot find part array element {}.{}.',
part.Name,names[1])
# trim the subname to be after the array element # trim the subname to be after the array element
subname = '.'.join(names[2:]) subname = '.'.join(names[2:])
if not shape:
shape=utils.getElementShape((part[1],subname))
# There are two states of an link array. # There are two states of an link array.
if getLinkProperty(part[0],'ElementList'): if getLinkProperty(part[0],'ElementList'):
# a) The elements are expanded as individual objects, i.e # a) The elements are expanded as individual objects, i.e
# when ElementList has members, then the moveable Placement # when ElementList has members, then the moveable Placement
# is a property of the array element. So we obtain the shape # is a property of the array element.
# before 'Placement' by setting 'transform' set to False.
if not shape:
shape=utils.getElementShape(
(part[1],subname),transform=False)
pla = part[0].Placement.multiply(part[1].Placement) pla = part[0].Placement.multiply(part[1].Placement)
obj = part[1].getLinkedObject(False) obj = part[1].getLinkedObject(False)
partName = part[1].Name partName = objName(part[1])
idx = int(partName.split('_i')[-1]) idx = int(partName.split('_i')[-1])
part = (part[0],idx,part[1],False) part = (part[0],idx,part[1],False)
else: else:
plaList = getLinkProperty(part[0],'PlacementList',None,True) plaList = getLinkProperty(part[0],'PlacementList',None,True)
if plaList: if plaList:
# b) The elements are collapsed. Then the moveable Placement # b) The elements are collapsed. Then the moveable Placement
# is stored inside link object's PlacementList property. So, # is stored inside link object's PlacementList property.
# the shape obtained below is already before 'Placement',
# i.e. must set 'transform' to True.
if not shape:
shape=utils.getElementShape(
(part[1],subname),transform=True)
obj = part[1] obj = part[1]
try: try:
if names[1] == part[1].Name: if names[1] == part[1].Name:
@ -1287,7 +1301,7 @@ def getElementInfo(parent,subname,
raise RuntimeError('invalid array subname of element ' raise RuntimeError('invalid array subname of element '
'{}: {}'.format(objName(parent),subnameRef)) '{}: {}'.format(objName(parent),subnameRef))
partName = '{}.{}.'.format(part[0].Name,idx) partName = '{}.{}.'.format(objName(part[0]),idx)
if not obj: if not obj:
part = partSaved part = partSaved
@ -1438,26 +1452,26 @@ class AsmElementLink(AsmBase):
not getattr(self,'parent',None) or \ not getattr(self,'parent',None) or \
FreeCAD.isRestoring(): FreeCAD.isRestoring():
return return
if obj.Document and getattr(obj.Document,'Transacting',False): elif obj.Document and getattr(obj.Document,'Transacting',False):
self.infos *= 0 # clear the list self.infos *= 0 # clear the list
self.info = None self.info = None
return return
if prop == 'Count': elif prop == 'Count':
self.infos *= 0 # clear the list self.infos *= 0 # clear the list
self.info = None self.info = None
return return
if prop == 'Offset': elif prop == 'Offset':
self.getInfo(True) self.getInfo(True)
return return
if prop == 'NoExpand': elif prop == 'NoExpand':
cstr = self.parent.Object cstr = self.parent.Object
if obj!=flattenGroup(cstr)[0] \ if obj!=cstr.Group[0] \
and cstr.Multiply \ and cstr.Multiply \
and obj.LinkedObject: and obj.LinkedObject:
self.setLink(self.getAssembly().getPartGroup(), self.setLink(self.getAssembly().getPartGroup(),
self.getElementSubname(True)) self.getElementSubname(True))
return return
if prop == 'Label': elif prop == 'Label':
if obj.Document and getattr(obj.Document,'Transacting',False): if obj.Document and getattr(obj.Document,'Transacting',False):
return return
link = getattr(obj,'LinkedObject',None) link = getattr(obj,'LinkedObject',None)
@ -1471,6 +1485,9 @@ class AsmElementLink(AsmBase):
# re-lable it. # re-lable it.
obj.Label = linked.Label obj.Label = linked.Label
return return
elif prop == 'AutoCount':
if obj.AutoCount and hasattr(obj,'ShowElement'):
self.parent.checkMultiply()
if prop not in self._MyIgnoredProperties and \ if prop not in self._MyIgnoredProperties and \
not Constraint.isDisabled(self.parent.Object): not Constraint.isDisabled(self.parent.Object):
Assembly.autoSolve(obj,prop) Assembly.autoSolve(obj,prop)
@ -1634,7 +1651,7 @@ class AsmElementLink(AsmBase):
return self.infos if expand else self.info return self.infos if expand else self.info
self.multiply = True self.multiply = True
if obj == flattenGroup(parent)[0]: if obj == parent.Group[0]:
if not isinstance(info.Part,tuple) or \ if not isinstance(info.Part,tuple) or \
getLinkProperty(info.Part[0],'ElementCount')!=obj.Count: getLinkProperty(info.Part[0],'ElementCount')!=obj.Count:
self.infos.append(info) self.infos.append(info)
@ -1653,10 +1670,11 @@ class AsmElementLink(AsmBase):
part = (part[0],i,sobj,part[3]) part = (part[0],i,sobj,part[3])
pla = part[0].Placement.multiply(pla) pla = part[0].Placement.multiply(pla)
plaList.append(pla.multiply(offset)) plaList.append(pla.multiply(offset))
infos.append(ElementInfo(Parent = info.Parent, infos.append(ElementInfo(
Parent = info.Parent,
SubnameRef = info.SubnameRef, SubnameRef = info.SubnameRef,
Part=part, Part=part,
PartName = '{}.{}'.format(part[0].Name,i), PartName = '{}.{}'.format(objName(part[0]),i),
Placement = pla, Placement = pla,
Object = info.Object, Object = info.Object,
Subname = info.Subname, Subname = info.Subname,
@ -1778,7 +1796,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
class AsmConstraint(AsmGroup): class AsmConstraint(AsmGroup):
def __init__(self,parent): def __init__(self,parent):
self.prevOrder = None self.prevOrder = []
self.version = None self.version = None
self._initializing = True self._initializing = True
self.elements = None self.elements = None
@ -1813,6 +1831,7 @@ class AsmConstraint(AsmGroup):
def onChanged(self,obj,prop): def onChanged(self,obj,prop):
if obj.Document and getattr(obj.Document,'Transacting',False): if obj.Document and getattr(obj.Document,'Transacting',False):
Constraint.onChanged(obj,prop)
return return
if not obj.Removing and prop not in _IgnoredProperties: if not obj.Removing and prop not in _IgnoredProperties:
if prop == Constraint.propMultiply() and not FreeCAD.isRestoring(): if prop == Constraint.propMultiply() and not FreeCAD.isRestoring():
@ -1844,7 +1863,11 @@ class AsmConstraint(AsmGroup):
obj = self.Object obj = self.Object
if not obj.Multiply: if not obj.Multiply:
return return
children = flattenGroup(obj)
if getattr(obj,'Cascade',False):
obj.Cascade = False
children = obj.Group
if len(children)<=1: if len(children)<=1:
return return
count = 0 count = 0
@ -1896,13 +1919,13 @@ class AsmConstraint(AsmGroup):
firstChild = children[0] firstChild = children[0]
info = firstChild.Proxy.getInfo() info = firstChild.Proxy.getInfo()
if not isinstance(info.Part,tuple): if not isinstance(info.Part,tuple):
raise RuntimeError('Expect part {} to be an array for' raise RuntimeError('Expect part {} to be an array for '
'constraint multiplication'.format(info.PartName)) 'constraint multiplication'.format(info.PartName))
touched = 'Touched' in firstChild.State touched = 'Touched' in firstChild.State
if not hasattr(firstChild,'Count'): if not hasattr(firstChild,'Count'):
firstChild.addProperty("App::PropertyInteger","Count",'','') firstChild.addProperty("App::PropertyInteger","Count",'','')
firstChild.setPropertyStatus('Count',('ReadOnly','Output')) firstChild.setPropertyStatus('Count','ReadOnly')
firstChild.addProperty("App::PropertyBool","AutoCount",'', firstChild.addProperty("App::PropertyBool","AutoCount",'',
'Auto change part count to match constraining elements') 'Auto change part count to match constraining elements')
firstChild.AutoCount = True firstChild.AutoCount = True
@ -1931,7 +1954,7 @@ class AsmConstraint(AsmGroup):
if firstChild.Count != count: if firstChild.Count != count:
firstChild.Count = count firstChild.Count = count
firstChild.Proxy.getInfo(True) firstChild.recompute()
if not touched and 'Touched' in firstChild.State: if not touched and 'Touched' in firstChild.State:
# purge touched to avoid recomputation multi-pass # purge touched to avoid recomputation multi-pass
@ -2016,6 +2039,7 @@ class AsmConstraint(AsmGroup):
# initial placement # initial placement
partGroup = self.getAssembly().getPartGroup() partGroup = self.getAssembly().getPartGroup()
touched = False
for i,info0 in enumerate(infos0): for i,info0 in enumerate(infos0):
if not distances[i]: if not distances[i]:
continue continue
@ -2042,10 +2066,13 @@ class AsmConstraint(AsmGroup):
else: else:
pla = info0.Placement.multiply(pla.multiply(pla0.inverse())) pla = info0.Placement.multiply(pla.multiply(pla0.inverse()))
showPart(partGroup,info0.Part) showPart(partGroup,info0.Part)
info0.Placement.Rotation = pla.Rotation touched = True
info0.Placement.Base = pla.Base
setPlacement(info0.Part,pla,True) setPlacement(info0.Part,pla,True)
if touched:
firstChild.Proxy.getInfo(True)
firstChild.purgeTouched()
def execute(self,obj): def execute(self,obj):
if not getattr(self,'_initializing',False) and\ if not getattr(self,'_initializing',False) and\
getattr(self,'parent',None): getattr(self,'parent',None):
@ -2085,7 +2112,6 @@ class AsmConstraint(AsmGroup):
elements.append(o) elements.append(o)
if count <= len(infos): if count <= len(infos):
infos = infos[:count] infos = infos[:count]
o.Proxy.infos = infos
elementInfo += infos elementInfo += infos
break break
elementInfo += infos elementInfo += infos
@ -2280,37 +2306,93 @@ class AsmConstraint(AsmGroup):
@staticmethod @staticmethod
def makeMultiply(checkOnly=False): def makeMultiply(checkOnly=False):
sels = FreeCADGui.Selection.getSelection() sel = FreeCADGui.Selection.getSelectionEx('*',0)
if not len(sels)==1 or not isTypeOf(sels[0],AsmConstraint): if len(sel)!=1 or len(sel[0].SubElementNames)!=1:
raise RuntimeError('Too many selections')
sel = sel[0]
cstr = sel.Object.getSubObject(sel.SubElementNames[0],1)
if not isTypeOf(cstr,AsmConstraint):
raise RuntimeError('Must select a constraint') raise RuntimeError('Must select a constraint')
cstr = sels[0]
multiplied = Constraint.canMultiply(cstr) multiplied = Constraint.canMultiply(cstr)
if multiplied is None: if multiplied is None:
raise RuntimeError('Constraint do not support multiplication') raise RuntimeError('Constraint do not support multiplication')
elements = cstr.Proxy.getElements() elements = cstr.Proxy.getElements()
if len(elements)<=1: if len(elements)<2:
raise RuntimeError('Constraint must have more than one element') raise RuntimeError('Constraint must have more than one element')
info = elements[0].Proxy.getInfo() if checkOnly:
if not isinstance(info.Part,tuple) or info.Part[1]!=0: return True
raise RuntimeError('Constraint multiplication requires the first '
'element to be from the first element of a link array')
try: try:
if not checkOnly: FreeCAD.setActiveTransaction("Assembly constraint multiply")
FreeCAD.setActiveTransaction("Assembly constraint multiply")
info = elements[0].Proxy.getInfo()
if not isinstance(info.Part,tuple):
# The first element must be an link array in order to get
# multiplied.
#First, check if it is a link (with element count)
if getLinkProperty(info.Part,'ElementCount') is None:
# No. So we replace it with a link with command
# Std_LinkReplace, which requires a select of the object
# to be replaced first. So construct the selection path
# by replacing the last two subnames (i.e.
# Constraints.Constraint) with PartGroup.PartName
subs = flattenSubname(sel.Object,sel.SubElementNames[0])
subs = subs.split('.')
# The last entry is for sub-element name (e.g. Edge1,
# Face2), which should be empty
subs[-1] = ''
subs[-2] = info.Part.Name
subs[-3] = '2'
# remember last selection
FreeCADGui.Selection.pushSelStack()
FreeCADGui.Selection.clearSelection()
FreeCADGui.Selection.addSelection(
sel.Object,'.'.join(subs))
FreeCADGui.Selection.pushSelStack()
FreeCADGui.runCommand('Std_LinkReplace')
# restore the last selection
FreeCADGui.runCommand('Std_SelBack')
info = elements[0].Proxy.getInfo(True)
# make sure the replace command works
if getLinkProperty(info.Part,'ElementCount') is None:
raise RuntimeError('Failed to replace part with a link')
# Let's first make an single element array without showing
# its element object, which will make the linked object
# grouped under the link rather than floating under tree
# view root
setLinkProperty(info.Part,'ShowElement',False)
try:
setLinkProperty(info.Part,'ElementCount',1)
except Exception:
raise RuntimeError('Failed to change element count of '
'{}'.format(info.PartName))
partGroup = cstr.Proxy.getAssembly().getPartGroup() partGroup = cstr.Proxy.getAssembly().getPartGroup()
if multiplied: cstr.recompute(True)
cstr.recompute(True)
if not multiplied:
for elementLink in elements[1:]:
subname = elementLink.Proxy.getElementSubname(True)
elementLink.Proxy.setLink(
partGroup,subname,checkOnly,multiply=True)
cstr.Multiply = True
else:
# Here means the constraint is already multiplied, expand it to
# multiple individual constraints
elements = cstr.Proxy.getElements() elements = cstr.Proxy.getElements()
subs = elements[0].Proxy.getElementSubname(True).split('.') infos0 = [(partGroup,'{}.{}.{}'.format(info.Part[0].Name,
infos0 = [] info.Part[1],
for info0 in elements[0].Proxy.getInfo(expand=True): info.Subname)) \
subs[1] = str(info0.Part[1]) for info in elements[0].Proxy.getInfo(expand=True)]
infos0.append((partGroup,'.'.join(subs)))
infos = [] infos = []
for element in elements[1:]: for element in elements[1:]:
if element.NoExpand: if element.NoExpand:
@ -2334,8 +2416,6 @@ class AsmConstraint(AsmGroup):
if subs[1] == subs[2]: if subs[1] == subs[2]:
subs[1] = '' subs[1] = ''
infos.append((partGroup,Part.joinSubname(*subs))) infos.append((partGroup,Part.joinSubname(*subs)))
if checkOnly:
return True
assembly = cstr.Proxy.getAssembly().Object assembly = cstr.Proxy.getAssembly().Object
typeid = Constraint.getTypeID(cstr) typeid = Constraint.getTypeID(cstr)
for info in zip(infos0,infos[:len(infos0)]): for info in zip(infos0,infos[:len(infos0)]):
@ -2346,23 +2426,14 @@ class AsmConstraint(AsmGroup):
Elements = info) Elements = info)
newCstr = AsmConstraint.make(typeid,sel,undo=False) newCstr = AsmConstraint.make(typeid,sel,undo=False)
Constraint.copy(cstr,newCstr) Constraint.copy(cstr,newCstr)
for element,target in zip(elements,flattenGroup(newCstr)): for element,target in zip(elements,newCstr.Group):
target.Offset = element.Offset target.Offset = element.Offset
cstr.Document.removeObject(cstr.Name) cstr.Document.removeObject(cstr.Name)
FreeCAD.closeActiveTransaction()
return True
for elementLink in elements[1:]: FreeCAD.closeActiveTransaction()
subname = elementLink.Proxy.getElementSubname(True)
elementLink.Proxy.setLink(
partGroup,subname,checkOnly,multiply=True)
if not checkOnly:
cstr.Multiply = True
FreeCAD.closeActiveTransaction()
return True return True
except Exception: except Exception:
if not checkOnly: FreeCAD.closeActiveTransaction(True)
FreeCAD.closeActiveTransaction(True)
raise raise
@ -2464,7 +2535,6 @@ class AsmConstraintGroup(AsmGroup):
if obj.Document and getattr(obj.Document,'Transacting',False): if obj.Document and getattr(obj.Document,'Transacting',False):
return return
if prop not in _IgnoredProperties: if prop not in _IgnoredProperties:
System.onChanged(obj,prop)
Assembly.autoSolve(obj,prop) Assembly.autoSolve(obj,prop)
@staticmethod @staticmethod
@ -3008,8 +3078,8 @@ class AsmRelation(AsmBase):
else: else:
part = obj.Part part = obj.Part
group = [] group = []
for cstr in self.getAssembly().getConstraintGroup().LinkedChildren: for cstr in flattenGroup(self.getAssembly().getConstraintGroup()):
for element in flattenGroup(cstr): for element in cstr.Group:
info = element.Proxy.getInfo() info = element.Proxy.getInfo()
if isinstance(info.Part,tuple): if isinstance(info.Part,tuple):
infoPart = info.Part[:2] infoPart = info.Part[:2]
@ -3501,7 +3571,7 @@ class Assembly(AsmGroup):
continue continue
if not System.isConstraintSupported(self.Object, if not System.isConstraintSupported(self.Object,
Constraint.getTypeName(o)): Constraint.getTypeName(o)):
logger.debug('skip unsupported constraint ' logger.warn('skip unsupported constraint '
'{}',cstrName(o)) '{}',cstrName(o))
continue continue
ret.append(o) ret.append(o)

11
gui.py
View File

@ -1038,12 +1038,11 @@ class AsmCmdMultiply(AsmCmdBase):
_id = 18 _id = 18
_menuText = 'Multiply constraint' _menuText = 'Multiply constraint'
_tooltip = 'Mutiply the part owner of the first element to constrain\n'\ _tooltip = 'Mutiply the part owner of the first element to constrain\n'\
'against the rest of the elements.\n\n'\ 'against the rest of the elements. It will auto replace the\n'\
'To activate this function, the FIRST part must be of the\n'\ 'first part owner with a link array when necessary.\n\n'\
'FIRST element of a link array. In will optionally expand\n'\ 'It will also optionally expand colplanar circular edges with\n'\
'colplanar circular edges with the same radius in the second\n'\ 'the same radius in the second element on wards. To disable\n'\
'element on wards. To disable auto expansion, use NoExpand\n'\ 'auto expansion, use NoExpand property in the element link.'
'property in the element link.'
_iconName = 'Assembly_ConstraintMultiply.svg' _iconName = 'Assembly_ConstraintMultiply.svg'
@classmethod @classmethod