Add support for constraint multiplication
This commit is contained in:
parent
4c9cc3033e
commit
ad35683c87
458
assembly.py
458
assembly.py
|
@ -266,10 +266,10 @@ class AsmElement(AsmBase):
|
||||||
obj.addProperty("App::PropertyBool","LinkTransform"," Link",'')
|
obj.addProperty("App::PropertyBool","LinkTransform"," Link",'')
|
||||||
obj.LinkTransform = True
|
obj.LinkTransform = True
|
||||||
if not hasattr(obj,'Detach'):
|
if not hasattr(obj,'Detach'):
|
||||||
obj.addProperty('App::PropertyLink','Detach', ' Link','')
|
obj.addProperty('App::PropertyBool','Detach', ' Link','')
|
||||||
obj.setPropertyStatus('LinkTransform',['Immutable','Hidden'])
|
obj.setPropertyStatus('LinkTransform',['Immutable','Hidden'])
|
||||||
obj.configLinkProperty('LinkedObject','Placement','LinkTransform')
|
|
||||||
obj.setPropertyStatus('LinkedObject','ReadOnly')
|
obj.setPropertyStatus('LinkedObject','ReadOnly')
|
||||||
|
obj.configLinkProperty('LinkedObject','Placement','LinkTransform')
|
||||||
|
|
||||||
def attach(self,obj):
|
def attach(self,obj):
|
||||||
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
|
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
|
||||||
|
@ -299,15 +299,33 @@ class AsmElement(AsmBase):
|
||||||
parent.Object.cacheChildLabel()
|
parent.Object.cacheChildLabel()
|
||||||
if prop not in _IgnoredProperties and \
|
if prop not in _IgnoredProperties and \
|
||||||
not Constraint.isDisabled(parent.Object):
|
not Constraint.isDisabled(parent.Object):
|
||||||
Assembly.autoSolve()
|
Assembly.autoSolve(obj,prop)
|
||||||
|
|
||||||
def execute(self,obj):
|
def execute(self,obj):
|
||||||
info = None
|
info = None
|
||||||
if not obj.Detach and hasattr(obj,'Shape'):
|
if not obj.Detach and hasattr(obj,'Shape'):
|
||||||
info = getElementInfo(self.getAssembly().getPartGroup(),
|
info = getElementInfo(self.getAssembly().getPartGroup(),
|
||||||
self.getElementSubname())
|
self.getElementSubname())
|
||||||
shape = info.Shape
|
mat = info.Placement.toMatrix()
|
||||||
shape.transformShape(info.Placement.toMatrix(),True)
|
if not getattr(obj,'Radius',None):
|
||||||
|
shape = Part.Shape(info.Shape)
|
||||||
|
shape.transformShape(mat,True)
|
||||||
|
else:
|
||||||
|
if isinstance(info.Part,tuple):
|
||||||
|
parentShape = Part.getShape(info.Part[2], info.Subname,
|
||||||
|
transform=info.Part[3], needSubElement=False)
|
||||||
|
else:
|
||||||
|
parentShape = Part.getShape(info.Part, info.Subname,
|
||||||
|
transform=False, needSubElement=False)
|
||||||
|
shapes = []
|
||||||
|
for edge in parentShape.Edges:
|
||||||
|
if info.Shape.isCoplanar(edge) and \
|
||||||
|
utils.isSameValue(
|
||||||
|
utils.getElementCircular(edge,True),obj.Radius):
|
||||||
|
edge.transformShape(mat,True)
|
||||||
|
shapes.append(edge)
|
||||||
|
shape = shapes
|
||||||
|
|
||||||
# make a compound to keep the shape's transformation
|
# make a compound to keep the shape's transformation
|
||||||
shape = Part.makeCompound(shape)
|
shape = Part.makeCompound(shape)
|
||||||
shape.ElementMap = info.Shape.ElementMap
|
shape.ElementMap = info.Shape.ElementMap
|
||||||
|
@ -347,8 +365,37 @@ class AsmElement(AsmBase):
|
||||||
objName(self.Object)))
|
objName(self.Object)))
|
||||||
return link[1]
|
return link[1]
|
||||||
|
|
||||||
def getElementSubname(self):
|
def getElementSubname(self,recursive=False):
|
||||||
return self.getSubName()
|
'''
|
||||||
|
Recursively resolve the geometry element link relative to the parent
|
||||||
|
assembly's part group
|
||||||
|
'''
|
||||||
|
|
||||||
|
subname = self.getSubName()
|
||||||
|
if not recursive:
|
||||||
|
return subname
|
||||||
|
|
||||||
|
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(True)
|
||||||
|
|
||||||
# Element: optional, if none, then a new element will be created if no
|
# 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
|
# pre-existing. Or else, it shall be the element to be amended
|
||||||
|
@ -398,6 +445,8 @@ class AsmElement(AsmBase):
|
||||||
subElement = utils.deduceSelectedElement(sel.Object,subs[0])
|
subElement = utils.deduceSelectedElement(sel.Object,subs[0])
|
||||||
if subElement:
|
if subElement:
|
||||||
subs[0] += subElement
|
subs[0] += subElement
|
||||||
|
else:
|
||||||
|
subElement = ''
|
||||||
|
|
||||||
link = Assembly.findPartGroup(sel.Object,subs[0])
|
link = Assembly.findPartGroup(sel.Object,subs[0])
|
||||||
if not link:
|
if not link:
|
||||||
|
@ -431,7 +480,7 @@ class AsmElement(AsmBase):
|
||||||
return element
|
return element
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def make(selection=None,name='Element',undo=False):
|
def make(selection=None,name='Element',undo=False,radius=None):
|
||||||
'''Add/get/modify an element with the given selected object'''
|
'''Add/get/modify an element with the given selected object'''
|
||||||
if not selection:
|
if not selection:
|
||||||
selection = AsmElement.getSelection()
|
selection = AsmElement.getSelection()
|
||||||
|
@ -487,7 +536,8 @@ class AsmElement(AsmBase):
|
||||||
# then import that element to the current assembly.
|
# then import that element to the current assembly.
|
||||||
sel = AsmElement.Selection(Element=None,
|
sel = AsmElement.Selection(Element=None,
|
||||||
Group=ret.Object, Subname=ret.Subname)
|
Group=ret.Object, Subname=ret.Subname)
|
||||||
element = AsmElement.make(sel)
|
element = AsmElement.make(sel,radius=radius)
|
||||||
|
radius=None
|
||||||
|
|
||||||
# now generate the subname reference
|
# now generate the subname reference
|
||||||
|
|
||||||
|
@ -526,9 +576,15 @@ class AsmElement(AsmBase):
|
||||||
if not e.Offset.isIdentity():
|
if not e.Offset.isIdentity():
|
||||||
continue
|
continue
|
||||||
sub = logger.catch('',e.Proxy.getSubName)
|
sub = logger.catch('',e.Proxy.getSubName)
|
||||||
if sub == subname:
|
if sub!=subname:
|
||||||
|
continue
|
||||||
|
r = getattr(e,'Radius',None)
|
||||||
|
if (not radius and not r) or radius==r:
|
||||||
return e
|
return e
|
||||||
element = AsmElement.create(name,elements)
|
element = AsmElement.create(name,elements)
|
||||||
|
if radius:
|
||||||
|
element.addProperty('App::PropertyFloat','Radius','','')
|
||||||
|
element.Radius = radius
|
||||||
elements.setLink({idx:element})
|
elements.setLink({idx:element})
|
||||||
elements.setElementVisible(element.Name,False)
|
elements.setElementVisible(element.Name,False)
|
||||||
element.Proxy._initializing = False
|
element.Proxy._initializing = False
|
||||||
|
@ -641,7 +697,8 @@ class ViewProviderAsmElementSketch(ViewProviderAsmElement):
|
||||||
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
|
ElementInfo = namedtuple('AsmElementInfo', ('Parent','SubnameRef','Part',
|
||||||
'PartName','Placement','Object','Subname','Shape'))
|
'PartName','Placement','Object','Subname','Shape'))
|
||||||
|
|
||||||
def getElementInfo(parent,subname,checkPlacement=False,shape=None):
|
def getElementInfo(parent,subname,
|
||||||
|
checkPlacement=False,shape=None,recursive=False):
|
||||||
'''Return a named tuple containing the part object element information
|
'''Return a named tuple containing the part object element information
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
@ -662,8 +719,8 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None):
|
||||||
|
|
||||||
SubnameRef: set to the input subname reference
|
SubnameRef: set to the input subname reference
|
||||||
|
|
||||||
Part: either the part object, or a tuple(obj, idx) to refer to an element in
|
Part: either the part object, or a tuple(array,idx,element,collapsed) to
|
||||||
an link array,
|
refer to an element in an link array,
|
||||||
|
|
||||||
PartName: a string name for the part
|
PartName: a string name for the part
|
||||||
|
|
||||||
|
@ -694,7 +751,7 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None):
|
||||||
objName(parent), subname))
|
objName(parent), subname))
|
||||||
if not isTypeOf(child,(AsmElement,AsmElementLink)):
|
if not isTypeOf(child,(AsmElement,AsmElementLink)):
|
||||||
raise RuntimeError('{} cannot be moved'.format(objName(child)))
|
raise RuntimeError('{} cannot be moved'.format(objName(child)))
|
||||||
subname = child.Proxy.getElementSubname()
|
subname = child.Proxy.getElementSubname(recursive)
|
||||||
names = subname.split('.')
|
names = subname.split('.')
|
||||||
partGroup = parent.Proxy.getPartGroup()
|
partGroup = parent.Proxy.getPartGroup()
|
||||||
|
|
||||||
|
@ -749,7 +806,7 @@ def getElementInfo(parent,subname,checkPlacement=False,shape=None):
|
||||||
shape=utils.getElementShape(
|
shape=utils.getElementShape(
|
||||||
(part[1],subname),transform=False)
|
(part[1],subname),transform=False)
|
||||||
pla = part[1].Placement
|
pla = part[1].Placement
|
||||||
obj = part[0].getLinkedObject(False)
|
obj = part[1].getLinkedObject(False)
|
||||||
partName = part[1].Name
|
partName = part[1].Name
|
||||||
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)
|
||||||
|
@ -811,14 +868,19 @@ class AsmElementLink(AsmBase):
|
||||||
def __init__(self,parent):
|
def __init__(self,parent):
|
||||||
super(AsmElementLink,self).__init__()
|
super(AsmElementLink,self).__init__()
|
||||||
self.info = None
|
self.info = None
|
||||||
|
self.infos = []
|
||||||
self.part = None
|
self.part = None
|
||||||
self.parent = getProxy(parent,AsmConstraint)
|
self.parent = getProxy(parent,AsmConstraint)
|
||||||
|
|
||||||
def linkSetup(self,obj):
|
def linkSetup(self,obj):
|
||||||
super(AsmElementLink,self).linkSetup(obj)
|
super(AsmElementLink,self).linkSetup(obj)
|
||||||
obj.configLinkProperty('LinkedObject')
|
|
||||||
obj.setPropertyStatus('LinkedObject','ReadOnly')
|
obj.setPropertyStatus('LinkedObject','ReadOnly')
|
||||||
|
obj.configLinkProperty('LinkedObject')
|
||||||
|
if hasattr(obj,'Count'):
|
||||||
|
obj.configLinkProperty('PlacementList',
|
||||||
|
'ShowElement',ElementCount='Count')
|
||||||
self.info = None
|
self.info = None
|
||||||
|
self.infos = []
|
||||||
self.part = None
|
self.part = None
|
||||||
|
|
||||||
def attach(self,obj):
|
def attach(self,obj):
|
||||||
|
@ -838,19 +900,32 @@ class AsmElementLink(AsmBase):
|
||||||
self.parent.Object,oldPart,info.Part,info.PartName)
|
self.parent.Object,oldPart,info.Part,info.PartName)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
_MyIgnoredProperties = _IgnoredProperties | \
|
||||||
|
set(('AcountCount','PlacementList'))
|
||||||
|
|
||||||
def onChanged(self,obj,prop):
|
def onChanged(self,obj,prop):
|
||||||
if obj.Removing or \
|
if obj.Removing or \
|
||||||
not getattr(self,'parent',None) or \
|
not getattr(self,'parent',None) or \
|
||||||
FreeCAD.isRestoring():
|
FreeCAD.isRestoring():
|
||||||
return
|
return
|
||||||
if prop not in _IgnoredProperties and \
|
if prop == 'Count':
|
||||||
|
self.infos *= 0 # clear the list
|
||||||
|
self.info = None
|
||||||
|
return
|
||||||
|
if prop == 'NoExpand':
|
||||||
|
cstr = self.parent.Object
|
||||||
|
if obj!=cstr.Group[0] and cstr.Multiply and obj.LinkedObject:
|
||||||
|
self.setLink(self.getAssembly().getPartGroup(),
|
||||||
|
self.getElementSubname(True))
|
||||||
|
return
|
||||||
|
if prop not in self._MyIgnoredProperties and \
|
||||||
not Constraint.isDisabled(self.parent.Object):
|
not Constraint.isDisabled(self.parent.Object):
|
||||||
Assembly.autoSolve()
|
Assembly.autoSolve(obj,prop)
|
||||||
|
|
||||||
def getAssembly(self):
|
def getAssembly(self):
|
||||||
return self.parent.parent.parent
|
return self.parent.parent.parent
|
||||||
|
|
||||||
def getElementSubname(self):
|
def getElementSubname(self,recursive=False):
|
||||||
'Resolve element link subname'
|
'Resolve element link subname'
|
||||||
|
|
||||||
# AsmElementLink is used by constraint to link to a geometry link. It
|
# AsmElementLink is used by constraint to link to a geometry link. It
|
||||||
|
@ -861,13 +936,14 @@ class AsmElementLink(AsmBase):
|
||||||
# the AsmElementLink's subname reference to the actual part object
|
# the AsmElementLink's subname reference to the actual part object
|
||||||
# subname reference relative to the parent assembly's part group
|
# subname reference relative to the parent assembly's part group
|
||||||
|
|
||||||
linked = self.Object.getLinkedObject(False)
|
link = self.Object.LinkedObject
|
||||||
if not linked or linked == self.Object:
|
linked = link[0].getSubObject(link[1],retType=1)
|
||||||
|
if not linked:
|
||||||
raise RuntimeError('Element link broken')
|
raise RuntimeError('Element link broken')
|
||||||
element = getProxy(linked,AsmElement)
|
element = getProxy(linked,AsmElement)
|
||||||
assembly = element.getAssembly()
|
assembly = element.getAssembly()
|
||||||
if assembly == self.getAssembly():
|
if assembly == self.getAssembly():
|
||||||
return element.getElementSubname()
|
return element.getElementSubname(recursive)
|
||||||
|
|
||||||
# The reference stored inside this ElementLink. We need the sub-assembly
|
# The reference stored inside this ElementLink. We need the sub-assembly
|
||||||
# name, which is the name before the first dot. This name may be
|
# name, which is the name before the first dot. This name may be
|
||||||
|
@ -879,23 +955,66 @@ class AsmElementLink(AsmBase):
|
||||||
ref = self.Object.LinkedObject[1]
|
ref = self.Object.LinkedObject[1]
|
||||||
prefix = ref[0:ref.rfind('.',0,ref.rfind('.',0,-1))]
|
prefix = ref[0:ref.rfind('.',0,ref.rfind('.',0,-1))]
|
||||||
return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name,
|
return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name,
|
||||||
element.getElementSubname())
|
element.getElementSubname(recursive))
|
||||||
|
|
||||||
|
def setLink(self,owner,subname,checkOnly=False,multiply=False):
|
||||||
|
obj = self.Object
|
||||||
|
cstr = self.parent.Object
|
||||||
|
elements = cstr.Group
|
||||||
|
radius = None
|
||||||
|
if (multiply or Constraint.canMultiply(cstr)) and \
|
||||||
|
obj!=elements[0] and \
|
||||||
|
not getattr(obj,'NoExpand',None):
|
||||||
|
|
||||||
|
info = getElementInfo(owner,subname)
|
||||||
|
|
||||||
|
radius = utils.getElementCircular(info.Shape,True)
|
||||||
|
if radius and not checkOnly and not hasattr(obj,'NoExpand'):
|
||||||
|
touched = 'Touched' in obj.State
|
||||||
|
obj.addProperty('App::PropertyBool','NoExpand','',
|
||||||
|
'Disable auto inclusion of coplanar edges '\
|
||||||
|
'with the same radius')
|
||||||
|
if len(elements)>2 and getattr(elements[-2],'NoExpand',None):
|
||||||
|
obj.NoExpand = True
|
||||||
|
radius = None
|
||||||
|
if not touched:
|
||||||
|
obj.purgeTouched()
|
||||||
|
if radius:
|
||||||
|
if isinstance(info.Part,tuple):
|
||||||
|
parentShape = Part.getShape(info.Part[2], info.Subname,
|
||||||
|
transform=info.Part[3], needSubElement=False)
|
||||||
|
else:
|
||||||
|
parentShape = Part.getShape(info.Part, info.Subname,
|
||||||
|
transform=False, needSubElement=False)
|
||||||
|
count = 0
|
||||||
|
for edge in parentShape.Edges:
|
||||||
|
if not info.Shape.isCoplanar(edge) or \
|
||||||
|
not utils.isSameValue(
|
||||||
|
utils.getElementCircular(edge,True),radius):
|
||||||
|
continue
|
||||||
|
count += 1
|
||||||
|
if count > 1:
|
||||||
|
break
|
||||||
|
if count<=1:
|
||||||
|
radius = None
|
||||||
|
|
||||||
|
if checkOnly:
|
||||||
|
return True
|
||||||
|
|
||||||
def setLink(self,owner,subname,checkOnly=False):
|
|
||||||
# check if there is any sub-assembly in the reference
|
# check if there is any sub-assembly in the reference
|
||||||
ret = Assembly.find(owner,subname)
|
ret = Assembly.find(owner,subname)
|
||||||
if not ret:
|
if not ret:
|
||||||
# if not, add/get an element in our own element group
|
# if not, add/get an element in our own element group
|
||||||
sel = AsmElement.Selection(Element=None, Group=owner,
|
sel = AsmElement.Selection(Element=None, Group=owner,
|
||||||
Subname=subname)
|
Subname=subname)
|
||||||
element = AsmElement.make(sel)
|
element = AsmElement.make(sel,radius=radius)
|
||||||
owner = element.Proxy.parent.Object
|
owner = element.Proxy.parent.Object
|
||||||
subname = '${}.'.format(element.Label)
|
subname = '${}.'.format(element.Label)
|
||||||
else:
|
else:
|
||||||
# if so, add/get an element from the sub-assembly
|
# if so, add/get an element from the sub-assembly
|
||||||
sel = AsmElement.Selection(Element=None, Group=ret.Object,
|
sel = AsmElement.Selection(Element=None, Group=ret.Object,
|
||||||
Subname=ret.Subname)
|
Subname=ret.Subname)
|
||||||
element = AsmElement.make(sel)
|
element = AsmElement.make(sel,radius=radius)
|
||||||
owner = owner.Proxy.getAssembly().getPartGroup()
|
owner = owner.Proxy.getAssembly().getPartGroup()
|
||||||
|
|
||||||
# This give us reference to child assembly's immediate child
|
# This give us reference to child assembly's immediate child
|
||||||
|
@ -909,29 +1028,84 @@ class AsmElementLink(AsmBase):
|
||||||
|
|
||||||
subname = '{}.${}.'.format(prefix, element.Label)
|
subname = '{}.${}.'.format(prefix, element.Label)
|
||||||
|
|
||||||
for sibling in self.parent.Object.Group:
|
for sibling in elements:
|
||||||
if sibling == self.Object:
|
if sibling == obj:
|
||||||
continue
|
continue
|
||||||
linked = sibling.LinkedObject
|
linked = sibling.LinkedObject
|
||||||
if isinstance(linked,tuple) and \
|
if isinstance(linked,tuple) and \
|
||||||
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(cstr)))
|
||||||
if not checkOnly:
|
obj.setLink(owner,subname)
|
||||||
self.Object.setLink(owner,subname)
|
|
||||||
|
|
||||||
def getInfo(self,refresh=False):
|
def getInfo(self,refresh=False,expand=False):
|
||||||
if not refresh:
|
if not refresh:
|
||||||
ret = getattr(self,'info',None)
|
if expand:
|
||||||
if ret:
|
info = getattr(self,'infos',None)
|
||||||
return ret
|
if info:
|
||||||
|
return info
|
||||||
|
info = getattr(self,'info',None)
|
||||||
|
if info:
|
||||||
|
return [info] if expand else info
|
||||||
|
|
||||||
self.info = None
|
self.info = None
|
||||||
|
self.infos *= 0 # clear the list
|
||||||
obj = getattr(self,'Object',None)
|
obj = getattr(self,'Object',None)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
shape = obj.LinkedObject[0].getSubObject(obj.LinkedObject[1])
|
||||||
self.info = getElementInfo(self.getAssembly().getPartGroup(),
|
self.info = getElementInfo(self.getAssembly().getPartGroup(),
|
||||||
self.getElementSubname(),shape=obj.getSubObject(''))
|
self.getElementSubname(),shape=shape)
|
||||||
return self.info
|
info = self.info
|
||||||
|
|
||||||
|
parent = self.parent.Object
|
||||||
|
if not Constraint.canMultiply(parent):
|
||||||
|
self.infos.append(info)
|
||||||
|
return self.infos if expand else self.info
|
||||||
|
|
||||||
|
if obj == parent.Group[0]:
|
||||||
|
if not isinstance(info.Part,tuple) or \
|
||||||
|
getLinkProperty(info.Part[0],'ElementCount')!=obj.Count:
|
||||||
|
self.infos.append(info)
|
||||||
|
return self.infos if expand else self.info
|
||||||
|
infos = []
|
||||||
|
offset = info.Placement.inverse()
|
||||||
|
plaList = []
|
||||||
|
for i in xrange(obj.Count):
|
||||||
|
part = info.Part
|
||||||
|
if part[3]:
|
||||||
|
pla = getLinkProperty(part[0],'PlacementList')[i]
|
||||||
|
part = (part[0],i,part[2],part[3])
|
||||||
|
else:
|
||||||
|
sobj = part[0].getSubObject(str(i)+'.',retType=1)
|
||||||
|
pla = sobj.Placement
|
||||||
|
part = (part[0],i,sobj,part[3])
|
||||||
|
plaList.append(pla.multiply(offset))
|
||||||
|
infos.append(ElementInfo(Parent = info.Parent,
|
||||||
|
SubnameRef = info.SubnameRef,
|
||||||
|
Part=part,
|
||||||
|
PartName = '{}.{}'.format(part[0].Name,i),
|
||||||
|
Placement = pla.copy(),
|
||||||
|
Object = info.Object,
|
||||||
|
Subname = info.Subname,
|
||||||
|
Shape = shape))
|
||||||
|
obj.PlacementList = plaList
|
||||||
|
self.infos = infos
|
||||||
|
return infos if expand else info
|
||||||
|
|
||||||
|
for i,edge in enumerate(info.Shape.Edges):
|
||||||
|
self.infos.append(ElementInfo(
|
||||||
|
Parent = info.Parent,
|
||||||
|
SubnameRef = info.SubnameRef,
|
||||||
|
Part = info.Part,
|
||||||
|
PartName = info.PartName,
|
||||||
|
Placement = info.Placement,
|
||||||
|
Object = info.Object,
|
||||||
|
Subname = '{}_{}'.format(info.Subname,i),
|
||||||
|
Shape = edge))
|
||||||
|
|
||||||
|
return self.infos if expand else self.info
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def setPlacement(part,pla):
|
def setPlacement(part,pla):
|
||||||
|
@ -979,6 +1153,9 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
|
||||||
super(ViewProviderAsmElementLink,self).attach(vobj)
|
super(ViewProviderAsmElementLink,self).attach(vobj)
|
||||||
vobj.OnTopWhenSelected = 2
|
vobj.OnTopWhenSelected = 2
|
||||||
|
|
||||||
|
def claimChildren(self):
|
||||||
|
return []
|
||||||
|
|
||||||
def getDefaultColor(self):
|
def getDefaultColor(self):
|
||||||
return (1.0,60.0/255.0,60.0/255.0)
|
return (1.0,60.0/255.0,60.0/255.0)
|
||||||
|
|
||||||
|
@ -987,7 +1164,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
|
||||||
return mover.movePart()
|
return mover.movePart()
|
||||||
|
|
||||||
def canDropObjectEx(self,_obj,owner,subname,elements):
|
def canDropObjectEx(self,_obj,owner,subname,elements):
|
||||||
if len(elements)>1:
|
if len(elements)>1 or not owner:
|
||||||
return False
|
return False
|
||||||
elif elements:
|
elif elements:
|
||||||
subname += elements[0]
|
subname += elements[0]
|
||||||
|
@ -1034,14 +1211,17 @@ class AsmConstraint(AsmGroup):
|
||||||
if not assembly or \
|
if not assembly or \
|
||||||
System.isConstraintSupported(assembly,Constraint.getTypeName(obj)):
|
System.isConstraintSupported(assembly,Constraint.getTypeName(obj)):
|
||||||
return
|
return
|
||||||
raise RuntimeError('Constraint type "{}" is not supported by '
|
logger.err('Constraint type "{}" is not supported by '
|
||||||
'solver "{}"'.format(Constraint.getTypeName(obj),
|
'solver "{}"'.format(Constraint.getTypeName(obj),
|
||||||
System.getTypeName(assembly)))
|
System.getTypeName(assembly)))
|
||||||
|
Constraint.setDisable(obj)
|
||||||
|
|
||||||
def onChanged(self,obj,prop):
|
def onChanged(self,obj,prop):
|
||||||
if not obj.Removing and prop not in _IgnoredProperties:
|
if not obj.Removing and prop not in _IgnoredProperties:
|
||||||
Constraint.onChanged(obj,prop)
|
if prop == Constraint.propMultiply() and not FreeCAD.isRestoring():
|
||||||
Assembly.autoSolve()
|
self.checkMultiply()
|
||||||
|
Constraint.onChanged(obj,prop)
|
||||||
|
Assembly.autoSolve(obj,prop)
|
||||||
|
|
||||||
def linkSetup(self,obj):
|
def linkSetup(self,obj):
|
||||||
self.elements = None
|
self.elements = None
|
||||||
|
@ -1057,10 +1237,65 @@ class AsmConstraint(AsmGroup):
|
||||||
Constraint.attach(obj)
|
Constraint.attach(obj)
|
||||||
obj.recompute()
|
obj.recompute()
|
||||||
|
|
||||||
def execute(self,_obj):
|
def checkMultiply(self):
|
||||||
|
obj = self.Object
|
||||||
|
if not obj.Multiply:
|
||||||
|
return
|
||||||
|
children = obj.Group
|
||||||
|
if len(children)<=1:
|
||||||
|
return
|
||||||
|
count = 0
|
||||||
|
for e in children[1:]:
|
||||||
|
info = e.Proxy.getInfo(True)
|
||||||
|
count += info.Shape.countElement('Edge')
|
||||||
|
|
||||||
|
firstChild = children[0]
|
||||||
|
info = firstChild.Proxy.getInfo()
|
||||||
|
if not isinstance(info.Part,tuple):
|
||||||
|
raise RuntimeError('Expect part {} to be an array for'
|
||||||
|
'constraint multiplication'.format(info.PartName))
|
||||||
|
|
||||||
|
touched = 'Touched' in firstChild.State
|
||||||
|
if not hasattr(firstChild,'Count'):
|
||||||
|
firstChild.addProperty("App::PropertyInteger","Count",'','')
|
||||||
|
firstChild.setPropertyStatus('Count',('ReadOnly','Output'))
|
||||||
|
firstChild.addProperty("App::PropertyBool","AutoCount",'',
|
||||||
|
'Auto change part count to match constraining elements')
|
||||||
|
firstChild.AutoCount = True
|
||||||
|
firstChild.addProperty("App::PropertyPlacementList",
|
||||||
|
"PlacementList",'','')
|
||||||
|
firstChild.setPropertyStatus('PlacementList','Output')
|
||||||
|
firstChild.addProperty("App::PropertyBool","ShowElement",'','')
|
||||||
|
firstChild.setPropertyStatus('ShowElement',('Hidden','Immutable'))
|
||||||
|
firstChild.configLinkProperty('PlacementList',
|
||||||
|
'ShowElement',ElementCount='Count')
|
||||||
|
|
||||||
|
if firstChild.AutoCount:
|
||||||
|
if getLinkProperty(info.Part[0],'ElementCount',None,True) is None:
|
||||||
|
firstChild.AutoCount = False
|
||||||
|
else:
|
||||||
|
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')
|
||||||
|
|
||||||
|
if firstChild.Count != count:
|
||||||
|
firstChild.Count = count
|
||||||
|
|
||||||
|
if not touched and 'Touched' in firstChild.State:
|
||||||
|
# purge touched to avoid recomputation multi-pass
|
||||||
|
firstChild.purgeTouched()
|
||||||
|
firstChild.Proxy.getInfo(True)
|
||||||
|
|
||||||
|
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):
|
||||||
self.checkSupport()
|
self.checkSupport()
|
||||||
|
if Constraint.canMultiply(obj):
|
||||||
|
self.checkMultiply()
|
||||||
self.getElements(True)
|
self.getElements(True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -1073,16 +1308,32 @@ class AsmConstraint(AsmGroup):
|
||||||
ret = getattr(self,'elements',None)
|
ret = getattr(self,'elements',None)
|
||||||
if ret or Constraint.isDisabled(obj):
|
if ret or Constraint.isDisabled(obj):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
elementInfo = []
|
elementInfo = []
|
||||||
elements = []
|
elements = []
|
||||||
for o in obj.Group:
|
group = obj.Group
|
||||||
checkType(o,AsmElementLink)
|
if Constraint.canMultiply(obj):
|
||||||
info = o.Proxy.getInfo()
|
firstInfo = group[0].Proxy.getInfo(expand=True)
|
||||||
if not info:
|
if not firstInfo:
|
||||||
return
|
raise RuntimeError('invalid first element')
|
||||||
elementInfo.append(info)
|
elements.append(group[0])
|
||||||
elements.append(o)
|
for o in group[1:]:
|
||||||
Constraint.check(obj,elementInfo,True)
|
info = o.Proxy.getInfo(expand=True)
|
||||||
|
if not info:
|
||||||
|
continue
|
||||||
|
elementInfo += info
|
||||||
|
elements.append(o)
|
||||||
|
for info in zip(firstInfo,elementInfo[:len(firstInfo)]):
|
||||||
|
Constraint.check(obj,info,True)
|
||||||
|
else:
|
||||||
|
for o in group:
|
||||||
|
checkType(o,AsmElementLink)
|
||||||
|
info = o.Proxy.getInfo()
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
elementInfo.append(info)
|
||||||
|
elements.append(o)
|
||||||
|
Constraint.check(obj,elementInfo,True)
|
||||||
self.elements = elements
|
self.elements = elements
|
||||||
return self.elements
|
return self.elements
|
||||||
|
|
||||||
|
@ -1185,7 +1436,7 @@ class AsmConstraint(AsmGroup):
|
||||||
elementInfo.append(getElementInfo(
|
elementInfo.append(getElementInfo(
|
||||||
assembly,found.Object.Name+'.'+sub))
|
assembly,found.Object.Name+'.'+sub))
|
||||||
|
|
||||||
if not Constraint.isDisabled(cstr):
|
if not Constraint.isDisabled(cstr) and not Constraint.canMultiply(cstr):
|
||||||
if cstr:
|
if cstr:
|
||||||
typeid = Constraint.getTypeID(cstr)
|
typeid = Constraint.getTypeID(cstr)
|
||||||
check = []
|
check = []
|
||||||
|
@ -1224,7 +1475,14 @@ class AsmConstraint(AsmGroup):
|
||||||
for e in sel.Elements:
|
for e in sel.Elements:
|
||||||
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
|
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
|
||||||
logger.catchDebug('init constraint', Constraint.init,cstr)
|
logger.catchDebug('init constraint', Constraint.init,cstr)
|
||||||
|
|
||||||
|
if gui.AsmCmdManager.AutoElementVis:
|
||||||
|
cstr.setPropertyStatus('VisibilityList','-Immutable')
|
||||||
|
cstr.VisibilityList = [False]*len(cstr.Group)
|
||||||
|
cstr.setPropertyStatus('VisibilityList','Immutable')
|
||||||
|
|
||||||
cstr.Proxy._initializing = False
|
cstr.Proxy._initializing = False
|
||||||
|
|
||||||
if undo:
|
if undo:
|
||||||
FreeCAD.closeActiveTransaction()
|
FreeCAD.closeActiveTransaction()
|
||||||
undo = False
|
undo = False
|
||||||
|
@ -1241,11 +1499,97 @@ class AsmConstraint(AsmGroup):
|
||||||
FreeCADGui.runCommand('Std_TreeSelection')
|
FreeCADGui.runCommand('Std_TreeSelection')
|
||||||
return cstr
|
return cstr
|
||||||
|
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.debug('failed to make constraint: {}'.format(e))
|
||||||
if undo:
|
if undo:
|
||||||
FreeCAD.closeActiveTransaction(True)
|
FreeCAD.closeActiveTransaction(True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def makeMultiply(checkOnly=False):
|
||||||
|
sels = FreeCADGui.Selection.getSelection()
|
||||||
|
if not len(sels)==1 or not isTypeOf(sels[0],AsmConstraint):
|
||||||
|
raise RuntimeError('Must select a constraint')
|
||||||
|
cstr = sels[0]
|
||||||
|
multiplied = Constraint.canMultiply(cstr)
|
||||||
|
if multiplied is None:
|
||||||
|
raise RuntimeError('Constraint do not support multiplication')
|
||||||
|
|
||||||
|
elements = cstr.Proxy.getElements()
|
||||||
|
if len(elements)<=1:
|
||||||
|
raise RuntimeError('Constraint must have more than one element')
|
||||||
|
|
||||||
|
info = elements[0].Proxy.getInfo()
|
||||||
|
if not isinstance(info.Part,tuple) or info.Part[1]!=0:
|
||||||
|
raise RuntimeError('Constraint multiplication requires the first '
|
||||||
|
'element to be from the first element of a link array')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not checkOnly:
|
||||||
|
FreeCAD.setActiveTransaction("Assembly constraint multiply")
|
||||||
|
|
||||||
|
partGroup = cstr.Proxy.getAssembly().getPartGroup()
|
||||||
|
|
||||||
|
if multiplied:
|
||||||
|
subs = elements[0].Proxy.getElementSubname(True).split('.')
|
||||||
|
infos0 = []
|
||||||
|
for i in xrange(elements[0].Count):
|
||||||
|
subs[1] = str(i)
|
||||||
|
infos0.append((partGroup,'.'.join(subs)))
|
||||||
|
infos = []
|
||||||
|
for element in elements[1:]:
|
||||||
|
if element.NoExpand:
|
||||||
|
infos.append(element.LinkedObject)
|
||||||
|
continue
|
||||||
|
info = element.Proxy.getInfo()
|
||||||
|
subs = Part.splitSubname(
|
||||||
|
element.Proxy.getElementSubname(True))
|
||||||
|
if isinstance(info.Part,tuple):
|
||||||
|
subs[0] = '{}.{}'.format(info.Part[1],subs[0])
|
||||||
|
parentShape = Part.getShape(
|
||||||
|
partGroup,subs[0],noElementMap=True)
|
||||||
|
subShape = parentShape.getElement(subs[2])
|
||||||
|
radius = utils.getElementCircular(subShape,True)
|
||||||
|
for i,edge in enumerate(parentShape.Edges):
|
||||||
|
if subShape.isCoplanar(edge) and \
|
||||||
|
utils.isSameValue(
|
||||||
|
utils.getElementCircular(edge,True),radius):
|
||||||
|
subs[2] = 'Edge{}'.format(i+1)
|
||||||
|
subs[1] = parentShape.getElementName(subs[2])
|
||||||
|
if subs[1] == subs[2]:
|
||||||
|
subs[1] = ''
|
||||||
|
infos.append((partGroup,Part.joinSubname(*subs)))
|
||||||
|
if checkOnly:
|
||||||
|
return True
|
||||||
|
assembly = cstr.Proxy.getAssembly().Object
|
||||||
|
typeid = Constraint.getTypeID(cstr)
|
||||||
|
for info in zip(infos0,infos[:len(infos0)]):
|
||||||
|
sel = AsmConstraint.Selection(SelObject=None,
|
||||||
|
SelSubname=None,
|
||||||
|
Assembly = assembly,
|
||||||
|
Constraint = None,
|
||||||
|
Elements = info)
|
||||||
|
AsmConstraint.make(typeid,sel,undo=False)
|
||||||
|
cstr.Document.removeObject(cstr.Name)
|
||||||
|
FreeCAD.closeActiveTransaction()
|
||||||
|
return True
|
||||||
|
|
||||||
|
for elementLink in elements[1:]:
|
||||||
|
subname = elementLink.Proxy.getElementSubname(True)
|
||||||
|
elementLink.Proxy.setLink(
|
||||||
|
partGroup,subname,checkOnly,multiply=True)
|
||||||
|
if not checkOnly:
|
||||||
|
cstr.Multiply = True
|
||||||
|
if elements[0].AutoCount and \
|
||||||
|
getLinkProperty(info.Part[0],'ShowElement',None,True):
|
||||||
|
setLinkProperty(info.Part[0],'ShowElement',False)
|
||||||
|
FreeCAD.closeActiveTransaction()
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
if not checkOnly:
|
||||||
|
FreeCAD.closeActiveTransaction(True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
class ViewProviderAsmConstraint(ViewProviderAsmGroup):
|
class ViewProviderAsmConstraint(ViewProviderAsmGroup):
|
||||||
def attach(self,vobj):
|
def attach(self,vobj):
|
||||||
|
@ -1930,19 +2274,21 @@ class Assembly(AsmGroup):
|
||||||
except Exception:
|
except Exception:
|
||||||
del partMap[obj]
|
del partMap[obj]
|
||||||
else:
|
else:
|
||||||
cls.autoSolve(True)
|
cls.autoSolve(obj,prop,True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def autoSolve(cls,force=False):
|
def autoSolve(cls,obj,prop,force=False):
|
||||||
if force or cls.canAutoSolve():
|
if force or cls.canAutoSolve():
|
||||||
if not cls._Timer.isSingleShot():
|
if not cls._Timer.isSingleShot():
|
||||||
cls._Timer.setSingleShot(True)
|
cls._Timer.setSingleShot(True)
|
||||||
cls._Timer.timeout.connect(Assembly.onSolverTimer)
|
cls._Timer.timeout.connect(Assembly.onSolverTimer)
|
||||||
logger.debug('auto solve scheduled',frame=1)
|
logger.debug('auto solve scheduled on change of {}.{}'.format(
|
||||||
|
objName(obj),prop),frame=1)
|
||||||
cls._Timer.start(300)
|
cls._Timer.start(300)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def cancelAutoSolve(cls):
|
def cancelAutoSolve(cls):
|
||||||
|
logger.debug('cancel auto solve',frame=1)
|
||||||
cls._Timer.stop()
|
cls._Timer.stop()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -2131,7 +2477,7 @@ class Assembly(AsmGroup):
|
||||||
return
|
return
|
||||||
if prop!='Group' and prop not in _IgnoredProperties:
|
if prop!='Group' and prop not in _IgnoredProperties:
|
||||||
System.onChanged(obj,prop)
|
System.onChanged(obj,prop)
|
||||||
Assembly.autoSolve()
|
Assembly.autoSolve(obj,prop)
|
||||||
|
|
||||||
def onDocumentRestored(self,obj):
|
def onDocumentRestored(self,obj):
|
||||||
super(Assembly,self).onDocumentRestored(obj)
|
super(Assembly,self).onDocumentRestored(obj)
|
||||||
|
|
|
@ -96,7 +96,7 @@ def _p(solver,partInfo,subname,shape,retAll=False):
|
||||||
system.NameTag = nameTag + 't'
|
system.NameTag = nameTag + 't'
|
||||||
h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
|
h = system.addTransform(e,*partInfo.Params,group=partInfo.Group)
|
||||||
h = PointInfo(entity=h, params=partInfo.Params,vector=v)
|
h = PointInfo(entity=h, params=partInfo.Params,vector=v)
|
||||||
system.log('{}: {},{}'.format(key,h,partInfo.Group))
|
system.log('{}: {},{}'.format(system.NameTag,h,partInfo.Group))
|
||||||
|
|
||||||
partInfo.EntityMap[key] = h
|
partInfo.EntityMap[key] = h
|
||||||
return h if retAll else h.entity
|
return h if retAll else h.entity
|
||||||
|
@ -142,7 +142,7 @@ def _n(solver,partInfo,subname,shape,retAll=False):
|
||||||
h = NormalInfo(entity=nz,rot=rot,
|
h = NormalInfo(entity=nz,rot=rot,
|
||||||
params=partInfo.Params, p0=p0.entity, ln=ln)
|
params=partInfo.Params, p0=p0.entity, ln=ln)
|
||||||
|
|
||||||
system.log('{}: {},{}'.format(key,h,partInfo.Group))
|
system.log('{}: {},{}'.format(system.NameTag,h,partInfo.Group))
|
||||||
partInfo.EntityMap[key] = h
|
partInfo.EntityMap[key] = h
|
||||||
return h if retAll else h.entity
|
return h if retAll else h.entity
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ def _l(solver,partInfo,subname,shape,retAll=False):
|
||||||
system.NameTag = nameTag
|
system.NameTag = nameTag
|
||||||
h = system.addLineSegment(tp0,tp1,group=partInfo.Group)
|
h = system.addLineSegment(tp0,tp1,group=partInfo.Group)
|
||||||
h = LineInfo(entity=h,p0=tp0,p1=tp1)
|
h = LineInfo(entity=h,p0=tp0,p1=tp1)
|
||||||
system.log('{}: {},{}'.format(key,h,partInfo.Group))
|
system.log('{}: {},{}'.format(system.NameTag,h,partInfo.Group))
|
||||||
partInfo.EntityMap[key] = h
|
partInfo.EntityMap[key] = h
|
||||||
|
|
||||||
return h if retAll else h.entity
|
return h if retAll else h.entity
|
||||||
|
@ -256,7 +256,7 @@ def _w(solver,partInfo,subname,shape,retAll=False):
|
||||||
system.NameTag = partInfo.PartName + '.' + key
|
system.NameTag = partInfo.PartName + '.' + key
|
||||||
w = system.addWorkplane(p.entity,n.entity,group=partInfo.Group)
|
w = system.addWorkplane(p.entity,n.entity,group=partInfo.Group)
|
||||||
h = PlaneInfo(entity=w,origin=p,normal=n)
|
h = PlaneInfo(entity=w,origin=p,normal=n)
|
||||||
system.log('{}: {},{}'.format(key,h,partInfo.Group))
|
system.log('{}: {},{}'.format(system.NameTag,h,partInfo.Group))
|
||||||
return h if retAll else h.entity
|
return h if retAll else h.entity
|
||||||
|
|
||||||
def _wa(solver,partInfo,subname,shape,retAll=False):
|
def _wa(solver,partInfo,subname,shape,retAll=False):
|
||||||
|
@ -311,7 +311,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
|
||||||
e = system.addCircle(pln.origin.entity, pln.normal.entity,
|
e = system.addCircle(pln.origin.entity, pln.normal.entity,
|
||||||
system.addDistance(r), group=g)
|
system.addDistance(r), group=g)
|
||||||
h = CircleInfo(entity=e,radius=r,p0=p0)
|
h = CircleInfo(entity=e,radius=r,p0=p0)
|
||||||
system.log('{}: add draft circle {}, {}'.format(key,h,g))
|
system.log('{}: add draft circle {}, {}'.format(nameTag,h,g))
|
||||||
else:
|
else:
|
||||||
system.NameTag = nameTag + '.c'
|
system.NameTag = nameTag + '.c'
|
||||||
center = system.addPoint2d(pln.entity,solver.v0,solver.v0,group=g)
|
center = system.addPoint2d(pln.entity,solver.v0,solver.v0,group=g)
|
||||||
|
@ -328,7 +328,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
|
||||||
system.NameTag = nameTag
|
system.NameTag = nameTag
|
||||||
e = system.addArcOfCircle(pln.entity,center,*points,group=g)
|
e = system.addArcOfCircle(pln.entity,center,*points,group=g)
|
||||||
h = ArcInfo(entity=e,p1=points[1],p0=points[0],params=params)
|
h = ArcInfo(entity=e,p1=points[1],p0=points[0],params=params)
|
||||||
system.log('{}: add draft arc {}, {}'.format(key,h,g))
|
system.log('{}: add draft arc {}, {}'.format(nameTag,h,g))
|
||||||
|
|
||||||
# exhaust all possible keys from a draft circle to save
|
# exhaust all possible keys from a draft circle to save
|
||||||
# recomputation
|
# recomputation
|
||||||
|
@ -352,7 +352,7 @@ def _c(solver,partInfo,subname,shape,requireArc=False,retAll=False):
|
||||||
h = system.addCircle(
|
h = system.addCircle(
|
||||||
pln.origin.entity, pln.normal.entity, hr, group=g)
|
pln.origin.entity, pln.normal.entity, hr, group=g)
|
||||||
h = CircleInfo(entity=h,radius=hr,p0=None)
|
h = CircleInfo(entity=h,radius=hr,p0=None)
|
||||||
system.log('{}: {},{}'.format(key,h,g))
|
system.log('{}: {},{}'.format(nameTag,h,g))
|
||||||
|
|
||||||
partInfo.EntityMap[key] = h
|
partInfo.EntityMap[key] = h
|
||||||
|
|
||||||
|
@ -461,6 +461,18 @@ class Constraint(ProxyType):
|
||||||
def isDisabled(mcs,obj):
|
def isDisabled(mcs,obj):
|
||||||
return getattr(obj,mcs._disabled,False)
|
return getattr(obj,mcs._disabled,False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def propMultiply(mcs):
|
||||||
|
return 'Multiply'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def canMultiply(mcs,obj):
|
||||||
|
return getattr(obj,mcs.propMultiply(),None)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setDisable(mcs,obj):
|
||||||
|
return setattr(obj,mcs._disabled,True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def check(mcs,tp,elements,checkCount=False):
|
def check(mcs,tp,elements,checkCount=False):
|
||||||
mcs.getType(tp).check(elements,checkCount)
|
mcs.getType(tp).check(elements,checkCount)
|
||||||
|
@ -552,6 +564,7 @@ _makeProp('Offset','App::PropertyDistance',getter=propGetValue)
|
||||||
_makeProp('OffsetX','App::PropertyDistance',getter=propGetValue)
|
_makeProp('OffsetX','App::PropertyDistance',getter=propGetValue)
|
||||||
_makeProp('OffsetY','App::PropertyDistance',getter=propGetValue)
|
_makeProp('OffsetY','App::PropertyDistance',getter=propGetValue)
|
||||||
_makeProp('Cascade','App::PropertyBool',internal=True)
|
_makeProp('Cascade','App::PropertyBool',internal=True)
|
||||||
|
_makeProp('Multiply','App::PropertyBool',internal=True)
|
||||||
_makeProp('Angle','App::PropertyAngle',getter=propGetValue)
|
_makeProp('Angle','App::PropertyAngle',getter=propGetValue)
|
||||||
|
|
||||||
_AngleProps = [
|
_AngleProps = [
|
||||||
|
@ -846,7 +859,7 @@ class BaseMulti(Base):
|
||||||
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
|
msg = cls._entityDef[0](None,info.Part,info.Subname,info.Shape)
|
||||||
if msg:
|
if msg:
|
||||||
raise RuntimeError('Constraint "{}" requires all the element '
|
raise RuntimeError('Constraint "{}" requires all the element '
|
||||||
'to be of {}'.format(cls.getName()))
|
'to be of {}'.format(cls.getName(),msg))
|
||||||
return
|
return
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -855,10 +868,45 @@ class BaseMulti(Base):
|
||||||
if not func:
|
if not func:
|
||||||
logger.warn('{} no constraint func'.format(cstrName(obj)))
|
logger.warn('{} no constraint func'.format(cstrName(obj)))
|
||||||
return
|
return
|
||||||
|
props = cls.getPropertyValues(obj)
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
if cls.canMultiply(obj):
|
||||||
|
elements = obj.Proxy.getElements()
|
||||||
|
if len(elements)<=1:
|
||||||
|
logger.warn('{} not enough elements'.format(cstrName(obj)))
|
||||||
|
return
|
||||||
|
|
||||||
|
firstInfo = elements[0].Proxy.getInfo(expand=True)
|
||||||
|
count = len(firstInfo)
|
||||||
|
if not count:
|
||||||
|
logger.warn('{} no first part shape'.format(cstrName(obj)))
|
||||||
|
return
|
||||||
|
idx = 0
|
||||||
|
for element in elements[1:]:
|
||||||
|
for info in element.Proxy.getInfo(expand=True):
|
||||||
|
info0 = firstInfo[idx]
|
||||||
|
partInfo0 = solver.getPartInfo(info0)
|
||||||
|
partInfo = solver.getPartInfo(info)
|
||||||
|
e0 = cls._entityDef[0](
|
||||||
|
solver,partInfo0,info0.Subname,info0.Shape)
|
||||||
|
e = cls._entityDef[0](
|
||||||
|
solver,partInfo,info.Subname,info.Shape)
|
||||||
|
params = props + [e0,e]
|
||||||
|
solver.system.checkRedundancy(obj,partInfo0,partInfo)
|
||||||
|
h = func(*params,group=solver.group)
|
||||||
|
if isinstance(h,(list,tuple)):
|
||||||
|
ret += list(h)
|
||||||
|
else:
|
||||||
|
ret.append(h)
|
||||||
|
idx += 1
|
||||||
|
if idx >= count:
|
||||||
|
return ret
|
||||||
|
return ret
|
||||||
|
|
||||||
parts = set()
|
parts = set()
|
||||||
ref = None
|
ref = None
|
||||||
elements = []
|
elements = []
|
||||||
props = cls.getPropertyValues(obj)
|
|
||||||
|
|
||||||
for e in obj.Proxy.getElements():
|
for e in obj.Proxy.getElements():
|
||||||
info = e.Proxy.getInfo()
|
info = e.Proxy.getInfo()
|
||||||
|
@ -880,7 +928,6 @@ class BaseMulti(Base):
|
||||||
logger.warn('{} has no effective constraint'.format(cstrName(obj)))
|
logger.warn('{} has no effective constraint'.format(cstrName(obj)))
|
||||||
return
|
return
|
||||||
e0 = None
|
e0 = None
|
||||||
ret = []
|
|
||||||
firstInfo = None
|
firstInfo = None
|
||||||
for e in elements:
|
for e in elements:
|
||||||
info = e.Proxy.getInfo()
|
info = e.Proxy.getInfo()
|
||||||
|
@ -899,7 +946,6 @@ class BaseMulti(Base):
|
||||||
ret.append(h)
|
ret.append(h)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class BaseCascade(BaseMulti):
|
class BaseCascade(BaseMulti):
|
||||||
@classmethod
|
@classmethod
|
||||||
def prepare(cls,obj,solver):
|
def prepare(cls,obj,solver):
|
||||||
|
@ -941,7 +987,7 @@ class BaseCascade(BaseMulti):
|
||||||
class PlaneCoincident(BaseCascade):
|
class PlaneCoincident(BaseCascade):
|
||||||
_id = 35
|
_id = 35
|
||||||
_iconName = 'Assembly_ConstraintCoincidence.svg'
|
_iconName = 'Assembly_ConstraintCoincidence.svg'
|
||||||
_props = ['Cascade','Offset','OffsetX','OffsetY'] + _AngleProps
|
_props = ['Multiply','Cascade','Offset','OffsetX','OffsetY'] + _AngleProps
|
||||||
_tooltip = \
|
_tooltip = \
|
||||||
'Add a "{}" constraint to conincide planes of two or more parts.\n'\
|
'Add a "{}" constraint to conincide planes of two or more parts.\n'\
|
||||||
'The planes are coincided at their centers with an optional distance.'
|
'The planes are coincided at their centers with an optional distance.'
|
||||||
|
@ -958,7 +1004,7 @@ class AxialAlignment(BaseMulti):
|
||||||
_id = 36
|
_id = 36
|
||||||
_entityDef = (_lna,)
|
_entityDef = (_lna,)
|
||||||
_iconName = 'Assembly_ConstraintAxial.svg'
|
_iconName = 'Assembly_ConstraintAxial.svg'
|
||||||
_props = _AngleProps
|
_props = ['Multiply'] + _AngleProps
|
||||||
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
|
_tooltip = 'Add a "{}" constraint to align planes of two or more parts.\n'\
|
||||||
'The planes are aligned at the direction of their surface normal axis.'
|
'The planes are aligned at the direction of their surface normal axis.'
|
||||||
|
|
||||||
|
|
32
gui.py
32
gui.py
|
@ -258,7 +258,7 @@ class AsmCmdMove(AsmCmdBase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def checkActive(cls):
|
def checkActive(cls):
|
||||||
cls._active = logger.catchTrace('',cls.canMove)
|
cls._active = True if logger.catchTrace('',cls.canMove) else False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def onClearSelection(cls):
|
def onClearSelection(cls):
|
||||||
|
@ -628,3 +628,33 @@ class AsmCmdDown(AsmCmdUp):
|
||||||
@classmethod
|
@classmethod
|
||||||
def Activated(cls):
|
def Activated(cls):
|
||||||
cls.move(1)
|
cls.move(1)
|
||||||
|
|
||||||
|
|
||||||
|
class ASmCmdMultiply(AsmCmdBase):
|
||||||
|
_id = 18
|
||||||
|
_menuText = 'Multiply constraint'
|
||||||
|
_tooltip = 'Mutiply the part owner of the first element to constrain\n'\
|
||||||
|
'against the rest of the elements.\n\n'\
|
||||||
|
'To activate this function, the FIRST part must be of the\n'\
|
||||||
|
'FIRST element of a link array. In will optionally expand\n'\
|
||||||
|
'colplanar circular edges with the same radius in the second\n'\
|
||||||
|
'element on wards. To disable auto expansion, use NoExpand\n'\
|
||||||
|
'property in the element link.'
|
||||||
|
_iconName = 'Assembly_ConstraintMultiply.svg'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def checkActive(cls):
|
||||||
|
from .assembly import AsmConstraint
|
||||||
|
if logger.catchTrace('',AsmConstraint.makeMultiply,True):
|
||||||
|
cls._active = True
|
||||||
|
else:
|
||||||
|
cls._active = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def Activated(cls):
|
||||||
|
from .assembly import AsmConstraint
|
||||||
|
AsmConstraint.makeMultiply()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def onClearSelection(cls):
|
||||||
|
cls._active = False
|
||||||
|
|
10
utils.py
10
utils.py
|
@ -90,7 +90,7 @@ def getElementShape(obj,tp=None,transform=False,noElementMap=True):
|
||||||
logger.trace('no sub object {}'.format(obj))
|
logger.trace('no sub object {}'.format(obj))
|
||||||
return
|
return
|
||||||
if shape.isNull():
|
if shape.isNull():
|
||||||
if sobj.TypeId == 'App::Line':
|
if sobj.isDerivedFrom('App::Line'):
|
||||||
if tp not in (None,Part.Shape,Part.Edge):
|
if tp not in (None,Part.Shape,Part.Edge):
|
||||||
logger.trace('wrong type of shape {}'.format(obj))
|
logger.trace('wrong type of shape {}'.format(obj))
|
||||||
return
|
return
|
||||||
|
@ -99,7 +99,7 @@ def getElementShape(obj,tp=None,transform=False,noElementMap=True):
|
||||||
FreeCAD.Vector(size,0,0))
|
FreeCAD.Vector(size,0,0))
|
||||||
shape.transformShape(mat,False,True)
|
shape.transformShape(mat,False,True)
|
||||||
return shape
|
return shape
|
||||||
elif sobj.TypeId == 'App::Plane':
|
elif sobj.isDerivedFrom('App::Plane'):
|
||||||
if tp not in (None, Part.Shape, Part.Face):
|
if tp not in (None, Part.Shape, Part.Face):
|
||||||
logger.trace('wrong type of shape {}'.format(obj))
|
logger.trace('wrong type of shape {}'.format(obj))
|
||||||
return
|
return
|
||||||
|
@ -418,7 +418,7 @@ def getElementsAngle(o1,o2,pla1=None,pla2=None):
|
||||||
v2 = getElementDirection(o2,pla2)
|
v2 = getElementDirection(o2,pla2)
|
||||||
return math.degrees(v1.getAngle(v2))
|
return math.degrees(v1.getAngle(v2))
|
||||||
|
|
||||||
def getElementCircular(obj):
|
def getElementCircular(obj,radius=False):
|
||||||
'return radius if it is closed, or a list of two endpoints'
|
'return radius if it is closed, or a list of two endpoints'
|
||||||
edge = getElementShape(obj,Part.Edge)
|
edge = getElementShape(obj,Part.Edge)
|
||||||
if not edge:
|
if not edge:
|
||||||
|
@ -427,7 +427,7 @@ def getElementCircular(obj):
|
||||||
return
|
return
|
||||||
c = edge.Curve
|
c = edge.Curve
|
||||||
if hasattr( c, 'Radius' ):
|
if hasattr( c, 'Radius' ):
|
||||||
if edge.Closed:
|
if radius or edge.Closed:
|
||||||
return c.Radius
|
return c.Radius
|
||||||
elif isLine(edge.Curve):
|
elif isLine(edge.Curve):
|
||||||
return
|
return
|
||||||
|
@ -437,7 +437,7 @@ def getElementCircular(obj):
|
||||||
arc = BSpline.toBiArcs(10**-6)[0]
|
arc = BSpline.toBiArcs(10**-6)[0]
|
||||||
except Exception: #FreeCAD exception thrown ()
|
except Exception: #FreeCAD exception thrown ()
|
||||||
return
|
return
|
||||||
if edge.Closed:
|
if radius or edge.Closed:
|
||||||
return arc[0].Radius
|
return arc[0].Radius
|
||||||
return [v.Point for v in edge.Vertexes]
|
return [v.Point for v in edge.Vertexes]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user