Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bc2d5d611f | ||
![]() |
e785510c68 | ||
![]() |
b9c7af40fd | ||
![]() |
b590b57793 | ||
![]() |
6c68f0f901 | ||
![]() |
caad48e927 | ||
![]() |
70d161f336 | ||
![]() |
c6f6c481d2 | ||
![]() |
fe6a792202 | ||
![]() |
da050987f5 | ||
![]() |
6fbded4460 | ||
![]() |
6bb16b5550 | ||
![]() |
c3380e602d | ||
![]() |
9f4fa40b8f | ||
![]() |
4edc0fbca6 | ||
![]() |
490da82590 | ||
![]() |
e44f47073b | ||
![]() |
0c1f659fde | ||
![]() |
5da0bd5554 | ||
![]() |
14dffc7b28 | ||
![]() |
652ea7af6c | ||
![]() |
c8cf0a1e32 | ||
![]() |
19163afe88 | ||
![]() |
0acaa5e9c3 | ||
![]() |
9e34ecd1c0 | ||
![]() |
3bd99b64d2 | ||
![]() |
9f904ede3f | ||
![]() |
72f64e9ed5 | ||
![]() |
91c34b2fcb | ||
![]() |
1cdf1fe5c4 | ||
![]() |
edac36d4df |
|
@ -102,6 +102,13 @@ def editGroup(obj,children,notouch=None):
|
||||||
change = '-Immutable'
|
change = '-Immutable'
|
||||||
revert = 'Immutable'
|
revert = 'Immutable'
|
||||||
|
|
||||||
|
if isTypeOf(obj,(AsmConstraintGroup,AsmConstraint)):
|
||||||
|
# the order inside constraint group actually matters, so do not
|
||||||
|
# engage no touch
|
||||||
|
parent = None
|
||||||
|
block = False
|
||||||
|
notouch = False
|
||||||
|
else:
|
||||||
parent = getattr(obj,'_Parent',None)
|
parent = getattr(obj,'_Parent',None)
|
||||||
if parent and 'Touched' in parent.State:
|
if parent and 'Touched' in parent.State:
|
||||||
parent = None
|
parent = None
|
||||||
|
@ -109,12 +116,6 @@ def editGroup(obj,children,notouch=None):
|
||||||
if not hasProperty(obj,'NoTouch'):
|
if not hasProperty(obj,'NoTouch'):
|
||||||
notouch = False
|
notouch = False
|
||||||
elif notouch is None:
|
elif notouch is None:
|
||||||
if (isTypeOf(parent,AsmConstraintGroup) or \
|
|
||||||
isTypeOf(obj,AsmConstraintGroup)):
|
|
||||||
# the order inside constraint group actually matters, so do not
|
|
||||||
# engage no touch
|
|
||||||
parent = None
|
|
||||||
else:
|
|
||||||
notouch = not obj.NoTouch
|
notouch = not obj.NoTouch
|
||||||
|
|
||||||
if notouch:
|
if notouch:
|
||||||
|
@ -122,6 +123,7 @@ def editGroup(obj,children,notouch=None):
|
||||||
block = gui.AsmCmdManager.AutoRecompute
|
block = gui.AsmCmdManager.AutoRecompute
|
||||||
if block:
|
if block:
|
||||||
gui.AsmCmdManager.AutoRecompute = False
|
gui.AsmCmdManager.AutoRecompute = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if change:
|
if change:
|
||||||
obj.setPropertyStatus('Group',change)
|
obj.setPropertyStatus('Group',change)
|
||||||
|
@ -137,10 +139,10 @@ def editGroup(obj,children,notouch=None):
|
||||||
parent.purgeTouched()
|
parent.purgeTouched()
|
||||||
|
|
||||||
def setupSortMenu(menu,func,func2):
|
def setupSortMenu(menu,func,func2):
|
||||||
action = QtGui.QAction(QtGui.QIcon(),"Sort A~Z",menu)
|
action = QtGui.QAction(QtGui.QIcon(),"Sort element A~Z",menu)
|
||||||
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),func)
|
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),func)
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
action = QtGui.QAction(QtGui.QIcon(),"Sort Z~A",menu)
|
action = QtGui.QAction(QtGui.QIcon(),"Sort element Z~A",menu)
|
||||||
QtCore.QObject.connect(
|
QtCore.QObject.connect(
|
||||||
action,QtCore.SIGNAL("triggered()"),func2)
|
action,QtCore.SIGNAL("triggered()"),func2)
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
|
@ -217,7 +219,10 @@ class ViewProviderAsmBase(object):
|
||||||
vobj.Proxy = self
|
vobj.Proxy = self
|
||||||
self.attach(vobj)
|
self.attach(vobj)
|
||||||
|
|
||||||
def replaceObject(self,_new,_old):
|
def canReplaceObject(self, _old, _new):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def replaceObject(self,_old,_new):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def canAddToSceneGraph(self):
|
def canAddToSceneGraph(self):
|
||||||
|
@ -285,7 +290,7 @@ class AsmGroup(AsmBase):
|
||||||
|
|
||||||
class ViewProviderAsmGroup(ViewProviderAsmBase):
|
class ViewProviderAsmGroup(ViewProviderAsmBase):
|
||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
return self.ViewObject.Object.Group
|
return getattr(self.ViewObject.Object, 'Group', [])
|
||||||
|
|
||||||
def doubleClicked(self, _vobj):
|
def doubleClicked(self, _vobj):
|
||||||
return False
|
return False
|
||||||
|
@ -293,6 +298,21 @@ class ViewProviderAsmGroup(ViewProviderAsmBase):
|
||||||
def canDropObject(self,_child):
|
def canDropObject(self,_child):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def canReplaceObject(self, _oldObj, newObj):
|
||||||
|
return newObj in self.ViewObject.Object.Group
|
||||||
|
|
||||||
|
def replaceObject(self, oldObj, newObj):
|
||||||
|
try:
|
||||||
|
children = self.ViewObject.Object.Group
|
||||||
|
old_idx = children.index(oldObj)
|
||||||
|
new_idx = children.index(newObj)
|
||||||
|
del children[new_idx]
|
||||||
|
children.insert(old_idx, newObj)
|
||||||
|
editGroup(self.ViewObject.Object, children)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ViewProviderAsmGroupOnTop(ViewProviderAsmGroup):
|
class ViewProviderAsmGroupOnTop(ViewProviderAsmGroup):
|
||||||
def __init__(self,vobj):
|
def __init__(self,vobj):
|
||||||
|
@ -309,6 +329,8 @@ class AsmPartGroup(AsmGroup):
|
||||||
def getSubObjects(self,obj,_reason):
|
def getSubObjects(self,obj,_reason):
|
||||||
# Deletion order problem may cause exception here. Just silence it
|
# Deletion order problem may cause exception here. Just silence it
|
||||||
try:
|
try:
|
||||||
|
if not getattr(obj.Document,'Partial',False) \
|
||||||
|
or not self.getAssembly().Object.Freeze:
|
||||||
return [ '{}.'.format(o.Name) for o in flattenGroup(obj) ]
|
return [ '{}.'.format(o.Name) for o in flattenGroup(obj) ]
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
@ -317,6 +339,10 @@ class AsmPartGroup(AsmGroup):
|
||||||
super(AsmPartGroup,self).linkSetup(obj)
|
super(AsmPartGroup,self).linkSetup(obj)
|
||||||
if not hasProperty(obj,'DerivedFrom'):
|
if not hasProperty(obj,'DerivedFrom'):
|
||||||
obj.addProperty('App::PropertyLink','DerivedFrom','Base','')
|
obj.addProperty('App::PropertyLink','DerivedFrom','Base','')
|
||||||
|
try:
|
||||||
|
obj.setPropertyStatus('Shape','-Output')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self.derivedParts = None
|
self.derivedParts = None
|
||||||
|
|
||||||
def checkDerivedParts(self):
|
def checkDerivedParts(self):
|
||||||
|
@ -374,7 +400,6 @@ class AsmPartGroup(AsmGroup):
|
||||||
obj = parent.Document.addObject("Part::FeaturePython",name,
|
obj = parent.Document.addObject("Part::FeaturePython",name,
|
||||||
AsmPartGroup(parent),None,True)
|
AsmPartGroup(parent),None,True)
|
||||||
obj.setPropertyStatus('Placement',('Output','Hidden'))
|
obj.setPropertyStatus('Placement',('Output','Hidden'))
|
||||||
obj.setPropertyStatus('Shape','Output')
|
|
||||||
ViewProviderAsmPartGroup(obj.ViewObject)
|
ViewProviderAsmPartGroup(obj.ViewObject)
|
||||||
obj.purgeTouched()
|
obj.purgeTouched()
|
||||||
return obj
|
return obj
|
||||||
|
@ -383,9 +408,6 @@ class AsmPartGroup(AsmGroup):
|
||||||
class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
|
class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
|
||||||
_iconName = 'Assembly_Assembly_Part_Tree.svg'
|
_iconName = 'Assembly_Assembly_Part_Tree.svg'
|
||||||
|
|
||||||
def replaceObject(self,new,old):
|
|
||||||
return self.Object.replaceObject(new,old)
|
|
||||||
|
|
||||||
def canDropObjectEx(self,obj,_owner,_subname,_elements):
|
def canDropObjectEx(self,obj,_owner,_subname,_elements):
|
||||||
return isTypeOf(obj,Assembly, True) or not isTypeOf(obj,AsmBase)
|
return isTypeOf(obj,Assembly, True) or not isTypeOf(obj,AsmBase)
|
||||||
|
|
||||||
|
@ -445,6 +467,9 @@ class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
|
||||||
pass
|
pass
|
||||||
vobj.DefaultMode = mode
|
vobj.DefaultMode = mode
|
||||||
|
|
||||||
|
def canReplaceObject(self, _old, _new):
|
||||||
|
return True
|
||||||
|
|
||||||
def replaceObject(self,oldObj,newObj):
|
def replaceObject(self,oldObj,newObj):
|
||||||
res = self.ViewObject.replaceObject(oldObj,newObj)
|
res = self.ViewObject.replaceObject(oldObj,newObj)
|
||||||
if res<=0:
|
if res<=0:
|
||||||
|
@ -620,12 +645,7 @@ class AsmElement(AsmBase):
|
||||||
if not obj:
|
if not obj:
|
||||||
return False # broken beyond fix
|
return False # broken beyond fix
|
||||||
|
|
||||||
subs = Part.splitSubname(subname)
|
if not utils.getElement(linked, subname):
|
||||||
if not subs[1]:
|
|
||||||
return False # no mapped element name
|
|
||||||
|
|
||||||
shape = linked.getSubObject(subs[0])
|
|
||||||
if not utils.getElement(shape, subs[1]):
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def fix(self):
|
def fix(self):
|
||||||
|
@ -640,7 +660,7 @@ class AsmElement(AsmBase):
|
||||||
if not subs[1]:
|
if not subs[1]:
|
||||||
raise RuntimeError('No mapped sub-element found')
|
raise RuntimeError('No mapped sub-element found')
|
||||||
|
|
||||||
shape = linked.getSubObject(subs[0])
|
shape = Part.getShape(linked,subs[0])
|
||||||
if utils.getElement(shape, subs[1]):
|
if utils.getElement(shape, subs[1]):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -685,6 +705,10 @@ class AsmElement(AsmBase):
|
||||||
self.version.value += 1
|
self.version.value += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if self.getAssembly().Object.Freeze:
|
||||||
|
logger.warn('Skip recomputing frozen element {}', objName(obj))
|
||||||
|
return True
|
||||||
|
|
||||||
if obj.Detach:
|
if obj.Detach:
|
||||||
self.updatePlacement()
|
self.updatePlacement()
|
||||||
return True
|
return True
|
||||||
|
@ -692,7 +716,9 @@ class AsmElement(AsmBase):
|
||||||
info = None
|
info = None
|
||||||
try:
|
try:
|
||||||
info = self.getInfo(False)
|
info = self.getInfo(False)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
|
logger.warn(str(e))
|
||||||
|
|
||||||
self.updatePlacement()
|
self.updatePlacement()
|
||||||
|
|
||||||
if not gui.AsmCmdManager.AutoFixElement:
|
if not gui.AsmCmdManager.AutoFixElement:
|
||||||
|
@ -809,10 +835,13 @@ class AsmElement(AsmBase):
|
||||||
|
|
||||||
link = self.Object.LinkedObject
|
link = self.Object.LinkedObject
|
||||||
if not isinstance(link,tuple):
|
if not isinstance(link,tuple):
|
||||||
raise RuntimeError('Borken element link')
|
raise RuntimeError('Broken element link')
|
||||||
obj = link[0].getSubObject(link[1],1)
|
obj = link[0].getSubObject(link[1],1)
|
||||||
if not obj:
|
if not obj:
|
||||||
raise RuntimeError('Borken element link')
|
if self.getAssembly().Object.Freeze:
|
||||||
|
raise RuntimeError('Unable to resolve element on frozen assembly %s'\
|
||||||
|
% objName(self.getAssembly().Object))
|
||||||
|
raise RuntimeError('Broken element link %s.%s'%(objName(link[0]), link[1]))
|
||||||
if not isTypeOf(obj,AsmElement):
|
if not isTypeOf(obj,AsmElement):
|
||||||
# If not pointing to another element, then assume we are directly
|
# If not pointing to another element, then assume we are directly
|
||||||
# pointing to the geometry element, just return as it is, which is a
|
# pointing to the geometry element, just return as it is, which is a
|
||||||
|
@ -1183,7 +1212,8 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
|
||||||
if not vobj or hasProperty(vobj.Object,'Radius'):
|
if not vobj or hasProperty(vobj.Object,'Radius'):
|
||||||
return
|
return
|
||||||
if getattr(vobj,'ShowCS',False) or\
|
if getattr(vobj,'ShowCS',False) or\
|
||||||
gui.AsmCmdManager.ShowElementCS:
|
gui.AsmCmdManager.ShowElementCS or\
|
||||||
|
not hasattr(vobj.Object,'Shape'):
|
||||||
return True
|
return True
|
||||||
return utils.isInfinite(vobj.Object.Shape)
|
return utils.isInfinite(vobj.Object.Shape)
|
||||||
|
|
||||||
|
@ -1356,18 +1386,29 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
|
||||||
rot = FreeCAD.Rotation(FreeCAD.Vector(1,0,0),180)
|
rot = FreeCAD.Rotation(FreeCAD.Vector(1,0,0),180)
|
||||||
rot = FreeCAD.Placement(FreeCAD.Vector(), rot)
|
rot = FreeCAD.Placement(FreeCAD.Vector(), rot)
|
||||||
|
|
||||||
FreeCAD.setActiveTransaction(
|
title = 'Flip element' if flipElement else 'Flip part'
|
||||||
'Flip element' if flipElement else 'Flip part')
|
FreeCAD.setActiveTransaction(title)
|
||||||
try:
|
try:
|
||||||
if flipElement:
|
if flipElement:
|
||||||
obj.Offset = rot.multiply(obj.Offset)
|
obj.Offset = rot.multiply(obj.Offset)
|
||||||
|
else:
|
||||||
|
if hasProperty(obj,'Count'):
|
||||||
|
# for multiplied elements, we shall flip the first part of
|
||||||
|
# the first pairing elements. Note that constraint
|
||||||
|
# multiplication algorithm will sort the element pairs based
|
||||||
|
# on their proximity to stablize index change
|
||||||
|
info = obj.Proxy.getInfo(expand=True)[0]
|
||||||
|
shape = Part.getShape(obj, '%d.' % info.Part[1], transform=False)
|
||||||
|
offset = utils.getElementPlacement(shape)
|
||||||
else:
|
else:
|
||||||
offset = utils.getElementPlacement(obj.getSubObject(''))
|
offset = utils.getElementPlacement(obj.getSubObject(''))
|
||||||
offset = offset.multiply(rot).multiply(offset.inverse())
|
offset = offset.multiply(rot).multiply(offset.inverse())
|
||||||
setPlacement(info.Part, offset.multiply(info.Placement))
|
pla = offset.multiply(info.Placement)
|
||||||
|
setPlacement(info.Part, pla)
|
||||||
obj.recompute(True)
|
obj.recompute(True)
|
||||||
FreeCAD.closeActiveTransaction()
|
FreeCAD.closeActiveTransaction()
|
||||||
except Exception:
|
except Exception:
|
||||||
|
QtGui.QMessageBox.critical(None, 'Flip', title + ' failed')
|
||||||
FreeCAD.closeActiveTransaction(True)
|
FreeCAD.closeActiveTransaction(True)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -1379,6 +1420,20 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
|
||||||
obj = self.ViewObject.Object
|
obj = self.ViewObject.Object
|
||||||
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(), False)
|
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(), False)
|
||||||
|
|
||||||
|
def getLinkedViewProvider(self, recursive):
|
||||||
|
obj = self.ViewObject.Object
|
||||||
|
try:
|
||||||
|
sub = obj.Proxy.getElementSubname(recursive)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
linked = obj.Proxy.getAssembly().getPartGroup().getSubObject(sub, retType=1)
|
||||||
|
if not linked:
|
||||||
|
return
|
||||||
|
subs = Part.splitSubname(sub)
|
||||||
|
if subs[1] or subs[2]:
|
||||||
|
return (linked.ViewObject, Part.joinSubname('', subs[1], subs[2]))
|
||||||
|
return linked.ViewObject
|
||||||
|
|
||||||
class AsmElementSketch(AsmElement):
|
class AsmElementSketch(AsmElement):
|
||||||
def __init__(self,obj,parent):
|
def __init__(self,obj,parent):
|
||||||
super(AsmElementSketch,self).__init__(parent)
|
super(AsmElementSketch,self).__init__(parent)
|
||||||
|
@ -1570,7 +1625,7 @@ def getElementInfo(parent,subname,
|
||||||
# no element object here.
|
# no element object here.
|
||||||
part = (part[0],idx,part[1],True)
|
part = (part[0],idx,part[1],True)
|
||||||
pla = part[0].Placement.multiply(plaList[idx])
|
pla = part[0].Placement.multiply(plaList[idx])
|
||||||
except ValueError:
|
except Exception:
|
||||||
raise RuntimeError('invalid array subname of element '
|
raise RuntimeError('invalid array subname of element '
|
||||||
'{}: {}'.format(objName(parent),subnameRef))
|
'{}: {}'.format(objName(parent),subnameRef))
|
||||||
|
|
||||||
|
@ -1592,7 +1647,7 @@ def getElementInfo(parent,subname,
|
||||||
'{}.{}'.format(objName(part),subname))
|
'{}.{}'.format(objName(part),subname))
|
||||||
pla = getattr(part,'Placement',FreeCAD.Placement())
|
pla = getattr(part,'Placement',FreeCAD.Placement())
|
||||||
obj = part.getLinkedObject(False)
|
obj = part.getLinkedObject(False)
|
||||||
partName = part.Name
|
partName = objName(part)
|
||||||
|
|
||||||
if transformShape:
|
if transformShape:
|
||||||
# Copy and transform shape. We have to copy the shape here to work
|
# Copy and transform shape. We have to copy the shape here to work
|
||||||
|
@ -1648,6 +1703,11 @@ class AsmElementLink(AsmBase):
|
||||||
|
|
||||||
self.version = AsmVersion()
|
self.version = AsmVersion()
|
||||||
|
|
||||||
|
# Suppress link array (when ShowElement=True) getSubObjects, so that view
|
||||||
|
# provider getBoundingBox can work.
|
||||||
|
def getSubObjects(self, _obj, _reason):
|
||||||
|
return
|
||||||
|
|
||||||
def migrate(self,obj):
|
def migrate(self,obj):
|
||||||
link = obj.LinkedObject
|
link = obj.LinkedObject
|
||||||
if not isinstance(link,tuple):
|
if not isinstance(link,tuple):
|
||||||
|
@ -1771,6 +1831,17 @@ class AsmElementLink(AsmBase):
|
||||||
def getAssembly(self):
|
def getAssembly(self):
|
||||||
return self.parent.parent.parent
|
return self.parent.parent.parent
|
||||||
|
|
||||||
|
def getLinkedElement(self):
|
||||||
|
'Get linked AsmElement object'
|
||||||
|
link = self.Object.LinkedObject
|
||||||
|
if not isinstance(link,tuple):
|
||||||
|
linked = link
|
||||||
|
else:
|
||||||
|
linked = link[0].getSubObject(link[1],1)
|
||||||
|
if not linked:
|
||||||
|
raise RuntimeError('broken link')
|
||||||
|
return linked
|
||||||
|
|
||||||
def getElementSubname(self,recursive=False):
|
def getElementSubname(self,recursive=False):
|
||||||
'Resolve element link subname'
|
'Resolve element link subname'
|
||||||
|
|
||||||
|
@ -1896,8 +1967,7 @@ class AsmElementLink(AsmBase):
|
||||||
info = self.info
|
info = self.info
|
||||||
|
|
||||||
if obj.Offset.isIdentity():
|
if obj.Offset.isIdentity():
|
||||||
if not obj.Placement.isIdentity():
|
pla = FreeCAD.Placement()
|
||||||
obj.Placement = FreeCAD.Placement()
|
|
||||||
else:
|
else:
|
||||||
# obj.Offset is in the element shape's coordinate system, we need to
|
# obj.Offset is in the element shape's coordinate system, we need to
|
||||||
# transform it to the assembly coordinate system
|
# transform it to the assembly coordinate system
|
||||||
|
@ -1905,8 +1975,6 @@ class AsmElementLink(AsmBase):
|
||||||
mOffset = obj.Offset.toMatrix()
|
mOffset = obj.Offset.toMatrix()
|
||||||
mat = info.Placement.toMatrix()*mShape
|
mat = info.Placement.toMatrix()*mShape
|
||||||
pla = FreeCAD.Placement(mat*mOffset*mat.inverse())
|
pla = FreeCAD.Placement(mat*mOffset*mat.inverse())
|
||||||
if not utils.isSamePlacement(obj.Placement,pla):
|
|
||||||
obj.Placement = pla
|
|
||||||
info.Shape.transformShape(mShape*mOffset*mShape.inverse())
|
info.Shape.transformShape(mShape*mOffset*mShape.inverse())
|
||||||
|
|
||||||
info = ElementInfo(Parent = info.Parent,
|
info = ElementInfo(Parent = info.Parent,
|
||||||
|
@ -1922,6 +1990,9 @@ class AsmElementLink(AsmBase):
|
||||||
|
|
||||||
parent = self.parent.Object
|
parent = self.parent.Object
|
||||||
if not Constraint.canMultiply(parent):
|
if not Constraint.canMultiply(parent):
|
||||||
|
# adjust placement calculated based on obj.Offset
|
||||||
|
if not utils.isSamePlacement(obj.Placement,pla):
|
||||||
|
obj.Placement = pla
|
||||||
self.multiply = False
|
self.multiply = False
|
||||||
self.infos.append(info)
|
self.infos.append(info)
|
||||||
return self.infos if expand else self.info
|
return self.infos if expand else self.info
|
||||||
|
@ -1933,8 +2004,18 @@ class AsmElementLink(AsmBase):
|
||||||
self.infos.append(info)
|
self.infos.append(info)
|
||||||
return self.infos if expand else self.info
|
return self.infos if expand else self.info
|
||||||
infos = []
|
infos = []
|
||||||
offset = info.Placement.inverse()
|
|
||||||
plaList = []
|
plaList = []
|
||||||
|
|
||||||
|
# We change this AsmElementLink into a LinkArray to visually display
|
||||||
|
# the multipled element (i.e. the first element in the parent
|
||||||
|
# constraint). Because of this, we shall encode the
|
||||||
|
# AsmElementLink.Offset of the element into each individual
|
||||||
|
# placement in AsmElementLink.PlacementList. So reset
|
||||||
|
# AsmElementLink.Placement here first, and then add the extra offset
|
||||||
|
# 'pla'.
|
||||||
|
obj.Placement = FreeCAD.Placement()
|
||||||
|
offset = info.Placement.inverse() * pla
|
||||||
|
|
||||||
for i in range(obj.Count):
|
for i in range(obj.Count):
|
||||||
part = info.Part
|
part = info.Part
|
||||||
if part[3]:
|
if part[3]:
|
||||||
|
@ -1945,7 +2026,9 @@ class AsmElementLink(AsmBase):
|
||||||
pla = sobj.Placement
|
pla = sobj.Placement
|
||||||
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(
|
infos.append(ElementInfo(
|
||||||
Parent = info.Parent,
|
Parent = info.Parent,
|
||||||
SubnameRef = info.SubnameRef,
|
SubnameRef = info.SubnameRef,
|
||||||
|
@ -1959,6 +2042,10 @@ class AsmElementLink(AsmBase):
|
||||||
self.infos = infos
|
self.infos = infos
|
||||||
return infos if expand else info
|
return infos if expand else info
|
||||||
|
|
||||||
|
# adjust placement calculated based on obj.Offset
|
||||||
|
if not utils.isSamePlacement(obj.Placement,pla):
|
||||||
|
obj.Placement = pla
|
||||||
|
|
||||||
for i,edge in enumerate(info.Shape.Edges):
|
for i,edge in enumerate(info.Shape.Edges):
|
||||||
self.infos.append(ElementInfo(
|
self.infos.append(ElementInfo(
|
||||||
Parent = info.Parent,
|
Parent = info.Parent,
|
||||||
|
@ -2093,6 +2180,22 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
|
||||||
obj = self.ViewObject.Object
|
obj = self.ViewObject.Object
|
||||||
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(), False)
|
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(), False)
|
||||||
|
|
||||||
|
def getLinkedViewProvider(self, recursive):
|
||||||
|
obj = self.ViewObject.Object
|
||||||
|
if not recursive:
|
||||||
|
return obj.LinkedObject.ViewObject
|
||||||
|
try:
|
||||||
|
sub = obj.Proxy.getElementSubname(True)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
linked = obj.Proxy.getAssembly().getPartGroup().getSubObject(sub, retType=1)
|
||||||
|
if not linked:
|
||||||
|
return
|
||||||
|
subs = Part.splitSubname(sub)
|
||||||
|
if subs[1] or subs[2]:
|
||||||
|
return (linked.ViewObject, Part.joinSubname('',subs[1], subs[2]))
|
||||||
|
return linked.ViewObject
|
||||||
|
|
||||||
|
|
||||||
class AsmConstraint(AsmGroup):
|
class AsmConstraint(AsmGroup):
|
||||||
|
|
||||||
|
@ -2277,7 +2380,7 @@ class AsmConstraint(AsmGroup):
|
||||||
# element shape
|
# element shape
|
||||||
|
|
||||||
offset = FreeCAD.Vector(getattr(obj,'OffsetX',0),
|
offset = FreeCAD.Vector(getattr(obj,'OffsetX',0),
|
||||||
getattr(obj,'Offset&',0),
|
getattr(obj,'OffsetY',0),
|
||||||
getattr(obj,'Offset',0))
|
getattr(obj,'Offset',0))
|
||||||
poses = poses[:count]
|
poses = poses[:count]
|
||||||
infos0 = firstChild.Proxy.getInfo(expand=True)[:count]
|
infos0 = firstChild.Proxy.getInfo(expand=True)[:count]
|
||||||
|
@ -2296,7 +2399,7 @@ class AsmConstraint(AsmGroup):
|
||||||
if i<len(prev) and prev[i]<count:
|
if i<len(prev) and prev[i]<count:
|
||||||
j = prev[i]
|
j = prev[i]
|
||||||
if used[i]<0 and not order[j] and \
|
if used[i]<0 and not order[j] and \
|
||||||
pos0.distanceToPoint(poses[j]) < 1e-7:
|
pos0.distanceToPoint(poses[j]) < 1e-6:
|
||||||
distances[i] = 0
|
distances[i] = 0
|
||||||
if not elements[i]._refPla:
|
if not elements[i]._refPla:
|
||||||
pla = infos[j].Placement.multiply(
|
pla = infos[j].Placement.multiply(
|
||||||
|
@ -2313,7 +2416,7 @@ class AsmConstraint(AsmGroup):
|
||||||
if order[j]:
|
if order[j]:
|
||||||
continue
|
continue
|
||||||
d = pos0.distanceToPoint(pos)
|
d = pos0.distanceToPoint(pos)
|
||||||
if used[i]<0 and d < 1e-7:
|
if used[i]<0 and d < 1e-6:
|
||||||
distances[i] = 0
|
distances[i] = 0
|
||||||
if not elements[i]._refPla:
|
if not elements[i]._refPla:
|
||||||
pla = infos[j].Placement.multiply(
|
pla = infos[j].Placement.multiply(
|
||||||
|
@ -2347,6 +2450,10 @@ class AsmConstraint(AsmGroup):
|
||||||
firstChild.Proxy.infos = order
|
firstChild.Proxy.infos = order
|
||||||
self.prevOrder = used
|
self.prevOrder = used
|
||||||
|
|
||||||
|
from . import solver
|
||||||
|
if solver.isBusy():
|
||||||
|
return
|
||||||
|
|
||||||
# now for those instances that are 'out of place', lets assign some
|
# now for those instances that are 'out of place', lets assign some
|
||||||
# initial placement
|
# initial placement
|
||||||
|
|
||||||
|
@ -2376,14 +2483,21 @@ class AsmConstraint(AsmGroup):
|
||||||
if ref:
|
if ref:
|
||||||
pla = pla.multiply(ref)
|
pla = pla.multiply(ref)
|
||||||
else:
|
else:
|
||||||
pla = info0.Placement.multiply(pla.multiply(pla0.inverse()))
|
pla = pla.multiply(p0.inverse())
|
||||||
showPart(partGroup,info0.Part)
|
showPart(partGroup,info0.Part)
|
||||||
touched = True
|
touched = True
|
||||||
setPlacement(info0.Part,pla,True)
|
|
||||||
|
|
||||||
if touched:
|
# DO NOT purgeTouched here. We shall leave it as touched and
|
||||||
firstChild.Proxy.getInfo(True)
|
# trigger a second pass of recomputation to properly update the
|
||||||
firstChild.purgeTouched()
|
# associated element of this part.
|
||||||
|
#
|
||||||
|
# setPlacement(info0.Part,pla,purgeTouched=True)
|
||||||
|
#
|
||||||
|
setPlacement(info0.Part,pla)
|
||||||
|
|
||||||
|
# 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\
|
||||||
|
@ -2688,7 +2802,17 @@ class AsmConstraint(AsmGroup):
|
||||||
setLinkProperty(info.Part,'ShowElement',False)
|
setLinkProperty(info.Part,'ShowElement',False)
|
||||||
try:
|
try:
|
||||||
setLinkProperty(info.Part,'ElementCount',1)
|
setLinkProperty(info.Part,'ElementCount',1)
|
||||||
|
|
||||||
|
# adjust the first element to point to the first array
|
||||||
|
# element. 'elements[0]' is an AsmElementLink, so follow its
|
||||||
|
# link first to obtain the AsmElement, and then change its
|
||||||
|
# subname reference
|
||||||
|
element = elements[0].Proxy.getLinkedElement()
|
||||||
|
link = element.LinkedObject
|
||||||
|
if isinstance(link, tuple):
|
||||||
|
element.setLink(link[0], '0.' + link[1])
|
||||||
except Exception:
|
except Exception:
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
raise RuntimeError('Failed to change element count of '
|
raise RuntimeError('Failed to change element count of '
|
||||||
'{}'.format(info.PartName))
|
'{}'.format(info.PartName))
|
||||||
|
|
||||||
|
@ -2759,7 +2883,7 @@ class ViewProviderAsmConstraint(ViewProviderAsmGroup):
|
||||||
def setupContextMenu(self,vobj,menu):
|
def setupContextMenu(self,vobj,menu):
|
||||||
obj = vobj.Object
|
obj = vobj.Object
|
||||||
action = QtGui.QAction(QtGui.QIcon(),
|
action = QtGui.QAction(QtGui.QIcon(),
|
||||||
"Enable" if obj.Disabled else "Disable", menu)
|
"Enable constraint" if obj.Disabled else "Disable constraint", menu)
|
||||||
QtCore.QObject.connect(
|
QtCore.QObject.connect(
|
||||||
action,QtCore.SIGNAL("triggered()"),self.toggleDisable)
|
action,QtCore.SIGNAL("triggered()"),self.toggleDisable)
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
|
@ -3008,15 +3132,13 @@ class AsmRelationGroup(AsmBase):
|
||||||
super(AsmRelationGroup,self).__init__()
|
super(AsmRelationGroup,self).__init__()
|
||||||
|
|
||||||
def attach(self,obj):
|
def attach(self,obj):
|
||||||
# AsmRelationGroup do not install LinkBaseExtension
|
|
||||||
# obj.addExtension('App::LinkBaseExtensionPython', None)
|
|
||||||
|
|
||||||
obj.addProperty('App::PropertyLinkList','Group','')
|
obj.addProperty('App::PropertyLinkList','Group','')
|
||||||
obj.setPropertyStatus('Group','Hidden')
|
obj.setPropertyStatus('Group','Hidden')
|
||||||
obj.addProperty('App::PropertyLink','Constraints','')
|
obj.addProperty('App::PropertyLink','Constraints','')
|
||||||
# this is to make sure relations are recomputed after all constraints
|
# this is to make sure relations are recomputed after all constraints
|
||||||
obj.Constraints = self.parent.getConstraintGroup()
|
obj.Constraints = self.parent.getConstraintGroup()
|
||||||
obj.setPropertyStatus('Constraints',('Hidden','Immutable'))
|
obj.setPropertyStatus('Constraints',('Hidden','Immutable'))
|
||||||
|
|
||||||
self.linkSetup(obj)
|
self.linkSetup(obj)
|
||||||
|
|
||||||
def getViewProviderName(self,_obj):
|
def getViewProviderName(self,_obj):
|
||||||
|
@ -3024,6 +3146,20 @@ class AsmRelationGroup(AsmBase):
|
||||||
|
|
||||||
def linkSetup(self,obj):
|
def linkSetup(self,obj):
|
||||||
super(AsmRelationGroup,self).linkSetup(obj)
|
super(AsmRelationGroup,self).linkSetup(obj)
|
||||||
|
|
||||||
|
# AsmRelationGroup used to not having the LinkBaseExtension for
|
||||||
|
# the sake of simplicity. It is added now to make it a LinkGroup so that
|
||||||
|
# its children can be auto deleted by setting GroupMode to 1
|
||||||
|
if not obj.hasExtension('App::LinkBaseExtensionPython'):
|
||||||
|
obj.addExtension('App::LinkBaseExtensionPython', None)
|
||||||
|
obj.addProperty("App::PropertyEnumeration","GroupMode","Base",'')
|
||||||
|
obj.configLinkProperty(ElementList='Group', LinkMode='GroupMode')
|
||||||
|
obj.GroupMode = 1 # auto delete children
|
||||||
|
obj.setPropertyStatus('GroupMode',
|
||||||
|
('Hidden','Immutable','Transient'))
|
||||||
|
else:
|
||||||
|
obj.configLinkProperty(ElementList='Group', LinkMode='GroupMode')
|
||||||
|
|
||||||
for o in obj.Group:
|
for o in obj.Group:
|
||||||
o.Proxy.parent = self
|
o.Proxy.parent = self
|
||||||
if o.Count:
|
if o.Count:
|
||||||
|
@ -3440,15 +3576,26 @@ class ViewProviderAsmRelation(ViewProviderAsmBase):
|
||||||
def canDropObjects(self):
|
def canDropObjects(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def onDelete(self,_vobj,_subs):
|
|
||||||
return False
|
|
||||||
|
|
||||||
def canDelete(self,_obj):
|
def canDelete(self,_obj):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def claimChildren(self):
|
def claimChildren(self):
|
||||||
return self.ViewObject.Object.Group
|
return self.ViewObject.Object.Group
|
||||||
|
|
||||||
|
def getDetailPath(self,subname,path,append):
|
||||||
|
vobj = self.ViewObject
|
||||||
|
idx = subname.find('.')
|
||||||
|
if idx > 0:
|
||||||
|
obj = vobj.Object
|
||||||
|
sobj = obj.getSubObject(subname[:idx+1], retType=1)
|
||||||
|
# checking of relation of part that is a link array element
|
||||||
|
if sobj != obj:
|
||||||
|
if isTypeOf(sobj, AsmRelation):
|
||||||
|
subname = str(sobj.Index) + subname[idx:]
|
||||||
|
else:
|
||||||
|
subname = ''
|
||||||
|
return vobj.getDetailPath(subname,path,append)
|
||||||
|
|
||||||
|
|
||||||
BuildShapeNone = 'None'
|
BuildShapeNone = 'None'
|
||||||
BuildShapeCompound = 'Compound'
|
BuildShapeCompound = 'Compound'
|
||||||
|
@ -4203,11 +4350,12 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
def __init__(self,vobj):
|
def __init__(self,vobj):
|
||||||
self._movingPart = None
|
self._movingPart = None
|
||||||
super(ViewProviderAssembly,self).__init__(vobj)
|
super(ViewProviderAssembly,self).__init__(vobj)
|
||||||
|
self.showParts()
|
||||||
|
|
||||||
def setupContextMenu(self,vobj,menu):
|
def setupContextMenu(self,vobj,menu):
|
||||||
obj = vobj.Object
|
obj = vobj.Object
|
||||||
action = QtGui.QAction(QtGui.QIcon(),
|
action = QtGui.QAction(QtGui.QIcon(),
|
||||||
"Unfreeze" if obj.Freeze else "Freeze", menu)
|
"Unfreeze assembly" if obj.Freeze else "Freeze assembly", menu)
|
||||||
QtCore.QObject.connect(
|
QtCore.QObject.connect(
|
||||||
action,QtCore.SIGNAL("triggered()"),self.toggleFreeze)
|
action,QtCore.SIGNAL("triggered()"),self.toggleFreeze)
|
||||||
menu.addAction(action)
|
menu.addAction(action)
|
||||||
|
@ -4218,6 +4366,7 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
'Unfreeze assembly' if obj.Freeze else 'Freeze assembly')
|
'Unfreeze assembly' if obj.Freeze else 'Freeze assembly')
|
||||||
try:
|
try:
|
||||||
obj.Freeze = not obj.Freeze
|
obj.Freeze = not obj.Freeze
|
||||||
|
obj.recompute(True)
|
||||||
FreeCAD.closeActiveTransaction()
|
FreeCAD.closeActiveTransaction()
|
||||||
except Exception:
|
except Exception:
|
||||||
FreeCAD.closeActiveTransaction(True)
|
FreeCAD.closeActiveTransaction(True)
|
||||||
|
@ -4237,6 +4386,12 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
def canDelete(self,obj):
|
def canDelete(self,obj):
|
||||||
return isTypeOf(obj,AsmRelationGroup)
|
return isTypeOf(obj,AsmRelationGroup)
|
||||||
|
|
||||||
|
def canReplaceObject(self, _old, _new):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def replaceObject(self,_old,_new):
|
||||||
|
return False
|
||||||
|
|
||||||
def _convertSubname(self,owner,subname):
|
def _convertSubname(self,owner,subname):
|
||||||
sub = subname.split('.')
|
sub = subname.split('.')
|
||||||
if not sub:
|
if not sub:
|
||||||
|
@ -4345,6 +4500,9 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def showParts(self):
|
def showParts(self):
|
||||||
|
if not hasProperty(self.ViewObject,'ShowParts'):
|
||||||
|
self.ViewObject.addProperty("App::PropertyBool","ShowParts"," Link")
|
||||||
|
return
|
||||||
proxy = self.ViewObject.Object.Proxy
|
proxy = self.ViewObject.Object.Proxy
|
||||||
if proxy:
|
if proxy:
|
||||||
proxy.getPartGroup().ViewObject.Proxy.showParts()
|
proxy.getPartGroup().ViewObject.Proxy.showParts()
|
||||||
|
@ -4365,9 +4523,6 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
self.showParts()
|
self.showParts()
|
||||||
|
|
||||||
def finishRestoring(self):
|
def finishRestoring(self):
|
||||||
if not hasProperty(self.ViewObject,'ShowParts'):
|
|
||||||
self.ViewObject.addProperty("App::PropertyBool","ShowParts"," Link")
|
|
||||||
else:
|
|
||||||
self.showParts()
|
self.showParts()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -4489,10 +4644,10 @@ class AsmWorkPlane(object):
|
||||||
else:
|
else:
|
||||||
if tp==1:
|
if tp==1:
|
||||||
pla = FreeCAD.Placement(info.Placement.Base,
|
pla = FreeCAD.Placement(info.Placement.Base,
|
||||||
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90))
|
FreeCAD.Rotation(FreeCAD.Vector(1,0,0),90))
|
||||||
elif tp==2:
|
elif tp==2:
|
||||||
pla = FreeCAD.Placement(info.Placement.Base,
|
pla = FreeCAD.Placement(info.Placement.Base,
|
||||||
FreeCAD.Rotation(FreeCAD.Vector(1,0,0),90))
|
FreeCAD.Rotation(FreeCAD.Vector(0,1,0),-90))
|
||||||
else:
|
else:
|
||||||
pla = info.Placement
|
pla = info.Placement
|
||||||
|
|
||||||
|
|
|
@ -245,11 +245,15 @@ def _lw(solver,partInfo,subname,shape,retAll=False):
|
||||||
'return a handle for either a line or a plane depending on the shape'
|
'return a handle for either a line or a plane depending on the shape'
|
||||||
_ = retAll
|
_ = retAll
|
||||||
if not solver:
|
if not solver:
|
||||||
if utils.isLinearEdge(shape) or utils.isPlanar(shape):
|
if utils.isLinearEdge(shape) or \
|
||||||
|
utils.isPlanar(shape) or \
|
||||||
|
utils.isCylindricalPlane(shape):
|
||||||
return
|
return
|
||||||
return 'a linear edge or edge/face with planar surface'
|
return 'a linear edge or edge/face with planar or cylindrical surface'
|
||||||
if utils.isLinearEdge(shape):
|
if utils.isLinearEdge(shape):
|
||||||
return _l(solver,partInfo,subname,shape,False)
|
return _l(solver,partInfo,subname,shape,False)
|
||||||
|
if utils.isCylindricalPlane(shape):
|
||||||
|
return _n(solver,partInfo,subname,shape,False)
|
||||||
return _wa(solver,partInfo,subname,shape)
|
return _wa(solver,partInfo,subname,shape)
|
||||||
|
|
||||||
def _w(solver,partInfo,subname,shape,retAll=False,noCheck=False):
|
def _w(solver,partInfo,subname,shape,retAll=False,noCheck=False):
|
||||||
|
@ -1171,7 +1175,8 @@ class BaseMulti(Base):
|
||||||
e = cls._entityDef[0](
|
e = cls._entityDef[0](
|
||||||
solver,partInfo,info.Subname,info.Shape)
|
solver,partInfo,info.Subname,info.Shape)
|
||||||
params = props + [e0,e]
|
params = props + [e0,e]
|
||||||
solver.system.checkRedundancy(obj,partInfo0,partInfo)
|
solver.system.checkRedundancy(
|
||||||
|
obj,partInfo0,partInfo,info0.SubnameRef,info.SubnameRef)
|
||||||
h = func(*params,group=solver.group)
|
h = func(*params,group=solver.group)
|
||||||
if isinstance(h,(list,tuple)):
|
if isinstance(h,(list,tuple)):
|
||||||
ret += list(h)
|
ret += list(h)
|
||||||
|
@ -1224,16 +1229,19 @@ class BaseMulti(Base):
|
||||||
if i==idx0:
|
if i==idx0:
|
||||||
e0 = cls._entityDef[idx0](
|
e0 = cls._entityDef[idx0](
|
||||||
solver,partInfo,info.Subname,info.Shape)
|
solver,partInfo,info.Subname,info.Shape)
|
||||||
|
subname0 = info.SubnameRef
|
||||||
info0 = partInfo
|
info0 = partInfo
|
||||||
else:
|
else:
|
||||||
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
|
e = cls._entityDef[0](solver,partInfo,info.Subname,info.Shape)
|
||||||
if e0 and e:
|
if e0 and e:
|
||||||
if idx0:
|
if idx0:
|
||||||
params = props + [e,e0]
|
params = props + [e,e0]
|
||||||
solver.system.checkRedundancy(obj,partInfo,info0)
|
solver.system.checkRedundancy(
|
||||||
|
obj,partInfo,info0,info.SubnameRef,subname0)
|
||||||
else:
|
else:
|
||||||
params = props + [e0,e]
|
params = props + [e0,e]
|
||||||
solver.system.checkRedundancy(obj,info0,partInfo)
|
solver.system.checkRedundancy(
|
||||||
|
obj,info0,partInfo,subname0,info.SubnameRef)
|
||||||
h = func(*params,group=solver.group)
|
h = func(*params,group=solver.group)
|
||||||
if isinstance(h,(list,tuple)):
|
if isinstance(h,(list,tuple)):
|
||||||
ret += list(h)
|
ret += list(h)
|
||||||
|
@ -1266,7 +1274,8 @@ class BaseCascade(BaseMulti):
|
||||||
params = props + [e1,e2]
|
params = props + [e1,e2]
|
||||||
else:
|
else:
|
||||||
params = props + [e2,e1]
|
params = props + [e2,e1]
|
||||||
solver.system.checkRedundancy(obj,prevInfo,partInfo)
|
solver.system.checkRedundancy(
|
||||||
|
obj,prevInfo,partInfo,prev.SubnameRef,info.SubnameRef)
|
||||||
h = func(*params,group=solver.group)
|
h = func(*params,group=solver.group)
|
||||||
if isinstance(h,(list,tuple)):
|
if isinstance(h,(list,tuple)):
|
||||||
ret += list(h)
|
ret += list(h)
|
||||||
|
|
|
@ -10,6 +10,16 @@ from .utils import getElementPos,objName,addIconToFCAD,guilogger as logger
|
||||||
from .proxy import ProxyType
|
from .proxy import ProxyType
|
||||||
from .FCADLogger import FCADLogger
|
from .FCADLogger import FCADLogger
|
||||||
|
|
||||||
|
def _isCommandActive(cmd):
|
||||||
|
try:
|
||||||
|
return FreeCADGui.Command.isActive(FreeCADGui.Command.get(cmd))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return FreeCADGui.isCommandActive(cmd)
|
||||||
|
except Exception:
|
||||||
|
return True
|
||||||
|
|
||||||
class SelectionObserver:
|
class SelectionObserver:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._attached = False
|
self._attached = False
|
||||||
|
@ -38,9 +48,13 @@ class SelectionObserver:
|
||||||
res = sobj.Proxy.parent.Object.isElementVisible(sobj.Name)
|
res = sobj.Proxy.parent.Object.isElementVisible(sobj.Name)
|
||||||
if res and vis:
|
if res and vis:
|
||||||
return False
|
return False
|
||||||
|
if not res and not vis:
|
||||||
|
return;
|
||||||
sobj.Proxy.parent.Object.setElementVisible(sobj.Name,vis)
|
sobj.Proxy.parent.Object.setElementVisible(sobj.Name,vis)
|
||||||
elif isTypeOf(sobj,AsmConstraint):
|
elif isTypeOf(sobj,AsmConstraint):
|
||||||
vis = [vis] * len(flattenGroup(sobj))
|
vis = [vis] * len(flattenGroup(sobj))
|
||||||
|
if sobj.VisibilityList == tuple(vis):
|
||||||
|
return
|
||||||
sobj.setPropertyStatus('VisibilityList','-Immutable')
|
sobj.setPropertyStatus('VisibilityList','-Immutable')
|
||||||
sobj.VisibilityList = vis
|
sobj.VisibilityList = vis
|
||||||
sobj.setPropertyStatus('VisibilityList','Immutable')
|
sobj.setPropertyStatus('VisibilityList','Immutable')
|
||||||
|
@ -103,7 +117,9 @@ class SelectionObserver:
|
||||||
hasSelection = FreeCADGui.Selection.hasSelection()
|
hasSelection = FreeCADGui.Selection.hasSelection()
|
||||||
for cmd in self.cmds:
|
for cmd in self.cmds:
|
||||||
cmd.onSelectionChange(hasSelection)
|
cmd.onSelectionChange(hasSelection)
|
||||||
FreeCADGui.updateCommands()
|
update = getattr(FreeCADGui, 'updateCommands', None)
|
||||||
|
if update:
|
||||||
|
update()
|
||||||
|
|
||||||
def addSelection(self,docname,objname,subname,_pos):
|
def addSelection(self,docname,objname,subname,_pos):
|
||||||
self.onChange()
|
self.onChange()
|
||||||
|
@ -250,6 +266,7 @@ class AsmCmdBase(with_metaclass(AsmCmdManager, object)):
|
||||||
_contextMenuName = 'Assembly'
|
_contextMenuName = 'Assembly'
|
||||||
_accel = None
|
_accel = None
|
||||||
_cmdType = None
|
_cmdType = None
|
||||||
|
_iconName = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def checkActive(cls):
|
def checkActive(cls):
|
||||||
|
@ -257,15 +274,18 @@ class AsmCmdBase(with_metaclass(AsmCmdManager, object)):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getIconName(cls):
|
def getIconName(cls):
|
||||||
|
if cls._iconName:
|
||||||
return addIconToFCAD(cls._iconName)
|
return addIconToFCAD(cls._iconName)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def GetResources(cls):
|
def GetResources(cls):
|
||||||
ret = {
|
ret = {
|
||||||
'Pixmap':cls.getIconName(),
|
|
||||||
'MenuText':cls.getMenuText(),
|
'MenuText':cls.getMenuText(),
|
||||||
'ToolTip':cls.getToolTip()
|
'ToolTip':cls.getToolTip()
|
||||||
}
|
}
|
||||||
|
name = cls.getIconName()
|
||||||
|
if name:
|
||||||
|
ret['Pixmap'] = name
|
||||||
if cls._accel:
|
if cls._accel:
|
||||||
ret['Accel'] = cls._accel
|
ret['Accel'] = cls._accel
|
||||||
if cls._cmdType is not None:
|
if cls._cmdType is not None:
|
||||||
|
@ -853,6 +873,7 @@ class AsmCmdGotoRelation(AsmCmdBase):
|
||||||
_iconName = 'Assembly_GotoRelation.svg'
|
_iconName = 'Assembly_GotoRelation.svg'
|
||||||
_accel = 'A, R'
|
_accel = 'A, R'
|
||||||
_toolbarName = ''
|
_toolbarName = ''
|
||||||
|
_cmdType = 'NoTransaction'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def Activated(cls):
|
def Activated(cls):
|
||||||
|
@ -950,7 +971,7 @@ class AsmCmdGotoLinked(AsmCmdBase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def IsActive(cls):
|
def IsActive(cls):
|
||||||
return FreeCADGui.isCommandActive('Std_LinkSelectLinked')
|
return _isCommandActive('Std_LinkSelectLinked')
|
||||||
|
|
||||||
class AsmCmdGotoLinkedFinal(AsmCmdBase):
|
class AsmCmdGotoLinkedFinal(AsmCmdBase):
|
||||||
_id = 23
|
_id = 23
|
||||||
|
@ -1008,7 +1029,7 @@ class AsmCmdGotoLinkedFinal(AsmCmdBase):
|
||||||
obj = sels[0].Object.getSubObject(sels[0].SubElementNames[0],1)
|
obj = sels[0].Object.getSubObject(sels[0].SubElementNames[0],1)
|
||||||
if isTypeOf(obj, (AsmElementLink,AsmElement)):
|
if isTypeOf(obj, (AsmElementLink,AsmElement)):
|
||||||
return True
|
return True
|
||||||
return FreeCADGui.isCommandActive('Std_LinkSelectLinkedFinal')
|
return _isCommandActive('Std_LinkSelectLinkedFinal')
|
||||||
|
|
||||||
class AsmCmdUp(AsmCmdBase):
|
class AsmCmdUp(AsmCmdBase):
|
||||||
_id = 6
|
_id = 6
|
||||||
|
@ -1096,3 +1117,38 @@ class AsmCmdMultiply(AsmCmdBase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def onSelectionChange(cls,hasSelection):
|
def onSelectionChange(cls,hasSelection):
|
||||||
cls._active = None if hasSelection else False
|
cls._active = None if hasSelection else False
|
||||||
|
|
||||||
|
class AsmCmdToggleConstraint(AsmCmdBase):
|
||||||
|
_id = 32
|
||||||
|
_menuText = 'Toggle constraints'
|
||||||
|
_toolbarName = None
|
||||||
|
_menuGroupName = None
|
||||||
|
_contextMenuName = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def checkActive(cls):
|
||||||
|
from .assembly import isTypeOf, AsmConstraint
|
||||||
|
cls._active = False
|
||||||
|
count = 0
|
||||||
|
for obj in FreeCADGui.Selection.getSelection('*'):
|
||||||
|
if not isTypeOf(obj, AsmConstraint):
|
||||||
|
return
|
||||||
|
count += 1
|
||||||
|
cls._active = count > 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def onSelectionChange(cls,hasSelection):
|
||||||
|
cls._active = None if hasSelection else False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def Activated(cls):
|
||||||
|
from .assembly import isTypeOf, AsmConstraint
|
||||||
|
FreeCAD.setActiveTransaction('Toggle constraints')
|
||||||
|
try:
|
||||||
|
for obj in FreeCADGui.Selection.getSelection('*'):
|
||||||
|
if isTypeOf(obj, AsmConstraint):
|
||||||
|
obj.Disabled = not obj.Disabled
|
||||||
|
FreeCAD.closeActiveTransaction()
|
||||||
|
except Exception:
|
||||||
|
FreeCAD.closeActiveTransaction(True)
|
||||||
|
raise
|
||||||
|
|
|
@ -43,17 +43,15 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
||||||
from .gui import AsmCmdManager,AsmCmdGotoRelation,\
|
from .gui import AsmCmdManager,AsmCmdGotoRelation,\
|
||||||
AsmCmdGotoLinked, AsmCmdGotoLinkedFinal
|
AsmCmdGotoLinked, AsmCmdGotoLinkedFinal
|
||||||
AsmCmdManager.init()
|
AsmCmdManager.init()
|
||||||
cmdSet = set()
|
|
||||||
for name,cmds in AsmCmdManager.Toolbars.items():
|
for name,cmds in AsmCmdManager.Toolbars.items():
|
||||||
cmdSet.update(cmds)
|
|
||||||
self.appendToolbar(name,[cmd.getName() for cmd in cmds])
|
self.appendToolbar(name,[cmd.getName() for cmd in cmds])
|
||||||
self.appendToolbar('Assembly3 Navigation', [
|
self.appendToolbar('Assembly3 Navigation', [
|
||||||
AsmCmdGotoRelation.getName(), AsmCmdGotoLinked.getName(),
|
AsmCmdGotoRelation.getName(), AsmCmdGotoLinked.getName(),
|
||||||
AsmCmdGotoLinkedFinal.getName()])
|
AsmCmdGotoLinkedFinal.getName()])
|
||||||
for name,cmds in AsmCmdManager.Menus.items():
|
for name,cmds in AsmCmdManager.Menus.items():
|
||||||
cmdSet.update(cmds)
|
|
||||||
self.appendMenu(name,[cmd.getName() for cmd in cmds])
|
self.appendMenu(name,[cmd.getName() for cmd in cmds])
|
||||||
self._observer.setCommands(cmdSet)
|
|
||||||
|
self._observer.setCommands(AsmCmdManager.getInfo().Types)
|
||||||
# FreeCADGui.addPreferencePage(
|
# FreeCADGui.addPreferencePage(
|
||||||
# ':/assembly3/ui/assembly3_prefs.ui','Assembly3')
|
# ':/assembly3/ui/assembly3_prefs.ui','Assembly3')
|
||||||
|
|
||||||
|
@ -68,7 +66,12 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
||||||
for name,cmds in menus.items():
|
for name,cmds in menus.items():
|
||||||
self.appendContextMenu(name,cmds)
|
self.appendContextMenu(name,cmds)
|
||||||
|
|
||||||
def ContextMenu(self, _recipient):
|
def ContextMenu(self, recipient):
|
||||||
|
if recipient == 'Tree':
|
||||||
|
from .gui import AsmCmdToggleConstraint
|
||||||
|
if AsmCmdToggleConstraint.IsActive():
|
||||||
|
self.appendContextMenu([],AsmCmdToggleConstraint.getName())
|
||||||
|
|
||||||
logger.catch('',self._contextMenu)
|
logger.catch('',self._contextMenu)
|
||||||
|
|
||||||
FreeCADGui.addWorkbench(Assembly3Workbench)
|
FreeCADGui.addWorkbench(Assembly3Workbench)
|
||||||
|
|
|
@ -15,7 +15,7 @@ class AsmMovingPart(object):
|
||||||
def __init__(self, moveInfo, element, moveElement):
|
def __init__(self, moveInfo, element, moveElement):
|
||||||
hierarchy = moveInfo.HierarchyList
|
hierarchy = moveInfo.HierarchyList
|
||||||
info = moveInfo.ElementInfo
|
info = moveInfo.ElementInfo
|
||||||
self.objs = [h.Assembly for h in reversed(hierarchy)]
|
self.objs = [h.Assembly.getLinkedObject(True) for h in reversed(hierarchy)]
|
||||||
self.assembly = resolveAssembly(info.Parent)
|
self.assembly = resolveAssembly(info.Parent)
|
||||||
self.viewObject = self.assembly.Object.ViewObject
|
self.viewObject = self.assembly.Object.ViewObject
|
||||||
self.info = info
|
self.info = info
|
||||||
|
@ -235,6 +235,9 @@ class AsmMovingPart(object):
|
||||||
setPlacement(info.Part,pla)
|
setPlacement(info.Part,pla)
|
||||||
rollback.append((info.PartName,info.Part,info.Placement.copy()))
|
rollback.append((info.PartName,info.Part,info.Placement.copy()))
|
||||||
|
|
||||||
|
if QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ShiftModifier:
|
||||||
|
return
|
||||||
|
|
||||||
if not gui.AsmCmdManager.AutoRecompute or \
|
if not gui.AsmCmdManager.AutoRecompute or \
|
||||||
QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ControlModifier:
|
QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ControlModifier:
|
||||||
# AsmCmdManager.AutoRecompute means auto re-solve the system. The
|
# AsmCmdManager.AutoRecompute means auto re-solve the system. The
|
||||||
|
|
|
@ -18,15 +18,12 @@ from .system import System
|
||||||
# plane of the part.
|
# plane of the part.
|
||||||
# EntityMap: string -> entity handle map, for caching
|
# EntityMap: string -> entity handle map, for caching
|
||||||
# Group: transforming entity group handle
|
# Group: transforming entity group handle
|
||||||
# CstrMap: map from other part to the constrain between this and the other part.
|
|
||||||
# This is for auto constraint DOF reduction. Only some composite
|
|
||||||
# constraints will be mapped.
|
|
||||||
# Update: in case the constraint uses the `Multiplication` feature, only the
|
# Update: in case the constraint uses the `Multiplication` feature, only the
|
||||||
# first element of all the coplanar edges will be actually constrainted.
|
# first element of all the coplanar edges will be actually constrainted.
|
||||||
# The rest ElementInfo will be stored here for later update by matrix
|
# The rest ElementInfo will be stored here for later update by matrix
|
||||||
# transformation.
|
# transformation.
|
||||||
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
|
PartInfo = namedtuple('SolverPartInfo', ('Part','PartName','Placement',
|
||||||
'Params','Workplane','EntityMap','Group','CstrMap','Update'))
|
'Params','Workplane','EntityMap','Group','Update'))
|
||||||
|
|
||||||
class Solver(object):
|
class Solver(object):
|
||||||
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
|
def __init__(self,assembly,reportFailed,dragPart,recompute,rollback):
|
||||||
|
@ -315,7 +312,6 @@ class Solver(object):
|
||||||
Workplane = h,
|
Workplane = h,
|
||||||
EntityMap = {},
|
EntityMap = {},
|
||||||
Group = group if group else g,
|
Group = group if group else g,
|
||||||
CstrMap = {},
|
|
||||||
Update = [])
|
Update = [])
|
||||||
|
|
||||||
self.system.log('{}, {}',partInfo,g)
|
self.system.log('{}, {}',partInfo,g)
|
||||||
|
|
|
@ -115,6 +115,14 @@ class SystemBase(with_metaclass(System, object)):
|
||||||
self.verbose = obj.Verbose
|
self.verbose = obj.Verbose
|
||||||
self.log = logger.info if obj.Verbose else logger.debug
|
self.log = logger.info if obj.Verbose else logger.debug
|
||||||
|
|
||||||
|
def _cstrKey(cstrType, firstPart, secondPart):
|
||||||
|
if firstPart > secondPart:
|
||||||
|
return (cstrType, secondPart, firstPart)
|
||||||
|
else:
|
||||||
|
return (cstrType, firstPart, secondPart)
|
||||||
|
|
||||||
|
# For skipping invalid constraints
|
||||||
|
_DummyCstrList = [None] * 6
|
||||||
|
|
||||||
class SystemExtension(object):
|
class SystemExtension(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -125,9 +133,17 @@ class SystemExtension(object):
|
||||||
self.firstInfo = None
|
self.firstInfo = None
|
||||||
self.secondInfo = None
|
self.secondInfo = None
|
||||||
self.relax = False
|
self.relax = False
|
||||||
|
self.coincidences = {}
|
||||||
|
self.cstrMap = {}
|
||||||
|
self.elementCstrMap = {}
|
||||||
|
self.elementMap = {}
|
||||||
|
self.firstElement = None
|
||||||
|
self.secondElement = None
|
||||||
|
|
||||||
def checkRedundancy(self,obj,firstInfo,secondInfo):
|
def checkRedundancy(self,obj,firstInfo,secondInfo,firstElement,secondElement):
|
||||||
self.cstrObj,self.firstInfo,self.secondInfo=obj,firstInfo,secondInfo
|
self.cstrObj,self.firstInfo,self.secondInfo=obj,firstInfo,secondInfo
|
||||||
|
self.firstElement = firstElement
|
||||||
|
self.secondElement = secondElement
|
||||||
|
|
||||||
def addSketchPlane(self,*args,**kargs):
|
def addSketchPlane(self,*args,**kargs):
|
||||||
_ = kargs
|
_ = kargs
|
||||||
|
@ -147,34 +163,121 @@ class SystemExtension(object):
|
||||||
h.append(self.addSameOrientation(n1.entity,n,group=group))
|
h.append(self.addSameOrientation(n1.entity,n,group=group))
|
||||||
return h
|
return h
|
||||||
|
|
||||||
def reportRedundancy(self,warn=False):
|
def reportRedundancy(self,firstPart=None,secondPart=None,count=0,limit=0,implicit=False):
|
||||||
msg = '{} between {} and {}'.format(cstrName(self.cstrObj),
|
msg = '{} between {} and {}'.format(cstrName(self.cstrObj),
|
||||||
self.firstInfo.PartName, self.secondInfo.PartName)
|
firstPart if firstPart else self.firstInfo.PartName,
|
||||||
if warn:
|
secondPart if secondPart else self.secondInfo.PartName)
|
||||||
logger.warn('skip redundant {}', msg, frame=1)
|
if implicit:
|
||||||
|
logger.msg('redundant implicit constraint {}, {}', msg, count, frame=1)
|
||||||
|
elif count > limit:
|
||||||
|
logger.warn('skip redundant {}, {}', msg, count, frame=1)
|
||||||
else:
|
else:
|
||||||
logger.debug('auto relax {}', msg, frame=1)
|
logger.msg('auto relax {}, {}', msg, count, frame=1)
|
||||||
|
|
||||||
def _countConstraints(self,increment,limit,*names):
|
def _populateConstraintMap(
|
||||||
first,second = self.firstInfo,self.secondInfo
|
self,cstrType,firstElement,secondElement,increment,limit,item,implicit):
|
||||||
if not first or not second:
|
|
||||||
return []
|
firstPart = self.elementMap[firstElement]
|
||||||
for name in names:
|
secondPart = self.elementMap[secondElement]
|
||||||
cstrs = first.CstrMap.get(second.Part,{}).get(name,None)
|
if firstPart == secondPart:
|
||||||
if not cstrs:
|
return _DummyCstrList
|
||||||
if increment:
|
|
||||||
cstrs = second.CstrMap.setdefault(
|
# A constraint may contain elements belong to more than two parts. For
|
||||||
first.Part,{}).setdefault(name,[])
|
# exmaple, for a constraint with elements from part A, B, C, we'll
|
||||||
else:
|
# expand it into two constraints for parts AB and AC. However, we must
|
||||||
cstrs = second.CstrMap.get(first.Part,{}).get(name,[])
|
# also count the implicit constraint between B and C.
|
||||||
cstrs += [None]*increment
|
#
|
||||||
|
# self.cstrMap is a map for counting constraints of the same type
|
||||||
|
# between pairs of parts. The count is used for checking redundancy and
|
||||||
|
# auto relaxing. The map is keyed using
|
||||||
|
#
|
||||||
|
# tuple(cstrType, firstPartName, secondPartName)
|
||||||
|
#
|
||||||
|
# and the value is a list. The item of this list is constraint defined
|
||||||
|
# (e.g. PlaineAilgnment stores a plane entity as item for auto
|
||||||
|
# relaxing) , the length of this list is use as the constraint count to
|
||||||
|
# be used later to decide how to auto relax the constraint.
|
||||||
|
#
|
||||||
|
# See the following link for difficulties on auto relaxing with implicit
|
||||||
|
# constraints. Right now there is no search performed. So the auto relax
|
||||||
|
# may fail. And the user is required to manually reorder constraints and
|
||||||
|
# the elements within to help the solver.
|
||||||
|
#
|
||||||
|
# https://github.com/realthunder/FreeCAD_assembly3/issues/403#issuecomment-757400349
|
||||||
|
|
||||||
|
key = _cstrKey(cstrType,firstPart,secondPart)
|
||||||
|
cstrs = self.cstrMap.setdefault(key, [])
|
||||||
|
cstrs += [item]*increment
|
||||||
count = len(cstrs)
|
count = len(cstrs)
|
||||||
if limit and count>=limit:
|
if increment and count>=limit:
|
||||||
self.reportRedundancy(count>limit)
|
self.reportRedundancy(firstPart, secondPart, count, limit, implicit)
|
||||||
return cstrs
|
return cstrs
|
||||||
|
|
||||||
def countConstraints(self,increment,limit,*names):
|
def _countConstraints(self,increment,limit,cstrType,item=None):
|
||||||
count = len(self._countConstraints(increment,limit,*names))
|
first, second = self.firstInfo, self.secondInfo
|
||||||
|
if not first or not second:
|
||||||
|
return []
|
||||||
|
|
||||||
|
firstElement, secondElement = self.firstElement, self.secondElement
|
||||||
|
|
||||||
|
if firstElement == secondElement:
|
||||||
|
return _DummyCstrList
|
||||||
|
|
||||||
|
self.elementMap[firstElement] = first.PartName
|
||||||
|
self.elementMap[secondElement] = second.PartName
|
||||||
|
|
||||||
|
# When counting implicit constraints (see comments in
|
||||||
|
# _populateConstraintMap() above), we must also make sure to count them
|
||||||
|
# if and only if they are originated from the same element, i.e. both
|
||||||
|
# AB and AC involving the same element of A. This will be ture if the
|
||||||
|
# those constraints are expanded by us, but may not be so if the user
|
||||||
|
# created them.
|
||||||
|
#
|
||||||
|
# self.elementCstrMap is a map keyed using tuple(cstrType, elementName),
|
||||||
|
# with value of a set of all element names that is involved with the the
|
||||||
|
# same type of constraint. This set is shared by all element entries in
|
||||||
|
# the map.
|
||||||
|
|
||||||
|
firstSet = self.elementCstrMap.setdefault((cstrType, firstElement), set())
|
||||||
|
if not firstSet:
|
||||||
|
firstSet.add(firstElement)
|
||||||
|
secondSet = self.elementCstrMap.setdefault((cstrType, secondElement),firstSet)
|
||||||
|
|
||||||
|
res = _DummyCstrList
|
||||||
|
|
||||||
|
if firstSet is not secondSet:
|
||||||
|
# If the secondSet is different, we shall merge them, and count the
|
||||||
|
# implicit constraints between the elements of first and second set.
|
||||||
|
for element in secondSet:
|
||||||
|
self.elementCstrMap[(cstrType, element)] = firstSet
|
||||||
|
is_second = element == secondElement
|
||||||
|
for e in firstSet:
|
||||||
|
implicit = not is_second or e != firstElement
|
||||||
|
cstrs = self._populateConstraintMap(
|
||||||
|
cstrType,e,element,increment,limit,item,implicit)
|
||||||
|
if not implicit:
|
||||||
|
# save the result (i.e. the explicit constraint pair of
|
||||||
|
# the give first and second element) for return
|
||||||
|
res = cstrs
|
||||||
|
firstSet |= secondSet
|
||||||
|
elif secondElement not in firstSet:
|
||||||
|
# Here means the entry of the secondElement is newly created, count
|
||||||
|
# the implicit constraints between all elements in the set to the
|
||||||
|
# secondElement.
|
||||||
|
for e in firstSet:
|
||||||
|
implicit = e != firstElement
|
||||||
|
cstrs = self._populateConstraintMap(
|
||||||
|
cstrType,e,secondElement,increment,limit,item,implicit)
|
||||||
|
if not implicit:
|
||||||
|
res = cstrs
|
||||||
|
firstSet.add(secondElement)
|
||||||
|
|
||||||
|
if res is _DummyCstrList:
|
||||||
|
self.reportRedundancy(count=len(res), limit=limit)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def countConstraints(self,increment,limit,name):
|
||||||
|
count = len(self._countConstraints(increment,limit,name))
|
||||||
if count>limit:
|
if count>limit:
|
||||||
return -1
|
return -1
|
||||||
return count
|
return count
|
||||||
|
@ -189,6 +292,10 @@ class SystemExtension(object):
|
||||||
if count < 0:
|
if count < 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if count == 1:
|
||||||
|
self.coincidences[(self.firstInfo.Part, self.secondInfo.Part)] = pln1
|
||||||
|
self.coincidences[(self.secondInfo.Part, self.firstInfo.Part)] = pln2
|
||||||
|
|
||||||
if d or dx or dy:
|
if d or dx or dy:
|
||||||
dx,dy,d = pln2.normal.rot.multVec(FreeCAD.Vector(dx,dy,d))
|
dx,dy,d = pln2.normal.rot.multVec(FreeCAD.Vector(dx,dy,d))
|
||||||
v = pln2.origin.vector+FreeCAD.Vector(dx,dy,d)
|
v = pln2.origin.vector+FreeCAD.Vector(dx,dy,d)
|
||||||
|
@ -200,23 +307,21 @@ class SystemExtension(object):
|
||||||
|
|
||||||
if not lockAngle and count==2:
|
if not lockAngle and count==2:
|
||||||
# if there is already some other plane coincident constraint set for
|
# if there is already some other plane coincident constraint set for
|
||||||
# this pair of parts, we reduce this second constraint to either a
|
# this pair of parts, we reduce this second constraint to a 2D
|
||||||
# points horizontal or vertical constraint, i.e. reduce the
|
# PointOnLine. The line is formed by the first part's two elements
|
||||||
# constraining DOF down to 1.
|
# in the previous and the current constraint. The point is taken
|
||||||
|
# from the element of the second part of the current constraint.
|
||||||
|
# The projection plane is taken from the element of the first part
|
||||||
|
# of the current constraint.
|
||||||
#
|
#
|
||||||
# We project the initial points to the first element plane, and
|
# This 2D PointOnLine effectively reduce the second PlaneCoincidence
|
||||||
# check for differences in x and y components of the points to
|
# constraining DOF down to 1.
|
||||||
# determine whether to use horizontal or vertical constraint.
|
prev = self.coincidences.get(
|
||||||
rot = pln1.normal.pla.Rotation.multiply(pln1.normal.rot)
|
(self.firstInfo.Part, self.secondInfo.Part))
|
||||||
v1 = pln1.normal.pla.multVec(pln1.origin.vector)
|
ln = self.addLineSegment(prev.origin.entity,
|
||||||
v2 = pln2.normal.pla.multVec(v)
|
pln1.origin.entity, group=self.firstInfo.Group)
|
||||||
v1,v2 = project2D(rot, v1, v2)
|
h.append(self.addPointOnLine(
|
||||||
if abs(v1.x-v2.x) < abs(v1.y-v2.y):
|
pln2.origin.entity, ln, pln1.entity, group=group))
|
||||||
h.append(self.addPointsHorizontal(
|
|
||||||
pln1.origin.entity, e, pln1.entity, group=group))
|
|
||||||
else:
|
|
||||||
h.append(self.addPointsVertical(
|
|
||||||
pln1.origin.entity, e, pln1.entity, group=group))
|
|
||||||
return h
|
return h
|
||||||
|
|
||||||
h.append(self.addPointsCoincident(pln1.origin.entity, e, group=group))
|
h.append(self.addPointsCoincident(pln1.origin.entity, e, group=group))
|
||||||
|
@ -225,7 +330,7 @@ class SystemExtension(object):
|
||||||
pln1.normal, pln2.normal, group)
|
pln1.normal, pln2.normal, group)
|
||||||
|
|
||||||
def addAttachment(self, pln1, pln2, group=0):
|
def addAttachment(self, pln1, pln2, group=0):
|
||||||
return self.addPlaneCoincident(0,0,0,False,0,0,0, pln1, pln2, group)
|
return self.addPlaneCoincident(0,0,0,True,0,0,0, pln1, pln2, group)
|
||||||
|
|
||||||
def addPlaneAlignment(self,d,lockAngle,yaw,pitch,roll,pln1,pln2,group=0):
|
def addPlaneAlignment(self,d,lockAngle,yaw,pitch,roll,pln1,pln2,group=0):
|
||||||
if not group:
|
if not group:
|
||||||
|
@ -233,15 +338,12 @@ class SystemExtension(object):
|
||||||
h = []
|
h = []
|
||||||
if self.relax:
|
if self.relax:
|
||||||
dof = 2 if lockAngle else 1
|
dof = 2 if lockAngle else 1
|
||||||
cstrs = self._countConstraints(dof,3,'Alignment')
|
cstrs = self._countConstraints(dof,3,'Alignment',item=pln1.entity)
|
||||||
count = len(cstrs)
|
count = len(cstrs)
|
||||||
if count > 3:
|
if count > 3:
|
||||||
return
|
return
|
||||||
if count == 1:
|
|
||||||
cstrs[0] = pln1.entity
|
|
||||||
else:
|
else:
|
||||||
count = 0
|
count = 0
|
||||||
cstrs = None
|
|
||||||
|
|
||||||
if d:
|
if d:
|
||||||
h.append(self.addPointPlaneDistance(
|
h.append(self.addPointPlaneDistance(
|
||||||
|
@ -252,7 +354,7 @@ class SystemExtension(object):
|
||||||
if count<=2:
|
if count<=2:
|
||||||
n1,n2 = pln1.normal,pln2.normal
|
n1,n2 = pln1.normal,pln2.normal
|
||||||
if count==2 and not lockAngle:
|
if count==2 and not lockAngle:
|
||||||
self.reportRedundancy()
|
self.reportRedundancy(count=count, limit=count)
|
||||||
h.append(self.addParallel(n2.entity,n1.entity,cstrs[0],group))
|
h.append(self.addParallel(n2.entity,n1.entity,cstrs[0],group))
|
||||||
else:
|
else:
|
||||||
self.setOrientation(h,lockAngle,yaw,pitch,roll,n1,n2,group)
|
self.setOrientation(h,lockAngle,yaw,pitch,roll,n1,n2,group)
|
||||||
|
|
|
@ -77,7 +77,10 @@ def addIconToFCAD(iconFile,path=None):
|
||||||
|
|
||||||
def objName(obj):
|
def objName(obj):
|
||||||
try:
|
try:
|
||||||
return getattr(obj,'FullName',obj.Name)
|
name = getattr(obj,'FullName',obj.Name)
|
||||||
|
if obj.Label != obj.Name:
|
||||||
|
name = '%s (%s)' % (name, obj.Label)
|
||||||
|
return name
|
||||||
except Exception:
|
except Exception:
|
||||||
return '?'
|
return '?'
|
||||||
|
|
||||||
|
@ -85,7 +88,7 @@ def isLine(param):
|
||||||
return isinstance(param,(Part.Line,Part.LineSegment))
|
return isinstance(param,(Part.Line,Part.LineSegment))
|
||||||
|
|
||||||
def deduceSelectedElement(obj,subname):
|
def deduceSelectedElement(obj,subname):
|
||||||
shape = obj.getSubObject(subname)
|
shape = getElementShape(obj, subname)
|
||||||
if not shape:
|
if not shape:
|
||||||
return
|
return
|
||||||
count = shape.countElement('Face')
|
count = shape.countElement('Face')
|
||||||
|
@ -207,6 +210,14 @@ def isElement(obj):
|
||||||
|
|
||||||
def getElement(shape, element):
|
def getElement(shape, element):
|
||||||
res = None
|
res = None
|
||||||
|
if not isinstance(shape, Part.Shape):
|
||||||
|
try:
|
||||||
|
res = getElementShape(shape, element)
|
||||||
|
if res and not res.isNull():
|
||||||
|
return res
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = shape.getElement(element, True)
|
res = shape.getElement(element, True)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user