assembly: add 'Offset' and 'Flip' menu action for AsmElement(Link)

This commit is contained in:
Zheng, Lei 2020-03-03 09:42:24 +08:00
parent f7c032ed5d
commit 66d7fbd4fa
3 changed files with 194 additions and 31 deletions

View File

@ -691,8 +691,7 @@ class AsmElement(AsmBase):
info = None
try:
info = getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname())
info = self.getInfo(False)
except Exception:
self.updatePlacement()
@ -700,8 +699,7 @@ class AsmElement(AsmBase):
raise
self.fix()
info = getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname())
info = self.getInfo(False)
if not getattr(obj,'Radius',None):
shape = Part.Shape(info.Shape).copy()
@ -754,8 +752,7 @@ class AsmElement(AsmBase):
# Call getElementInfo() to obtain part's placement only. We don't
# need the shape here, in order to handle missing down-stream
# element
info = getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname(),False,True)
info = self.getInfo()
pla = info.Placement
if obj.Offset.isIdentity():
@ -1115,6 +1112,10 @@ class AsmElement(AsmBase):
subname = ''
return obj.getSubObject(subname, retType, mat, transform, depth)
def getInfo(self, noShape=True):
return getElementInfo(self.getAssembly().getPartGroup(),
self.getElementSubname(),False,noShape)
class ViewProviderAsmElement(ViewProviderAsmOnTop):
_iconName = 'Assembly_Assembly_Element.svg'
@ -1159,7 +1160,7 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
def doubleClicked(self,_vobj):
from . import mover
return mover.movePart()
return mover.movePart(element=self.ViewObject.Object, moveElement=False)
def getIcon(self):
return utils.getIcon(self.__class__,
@ -1248,20 +1249,56 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
if prop == 'ShowCS':
self.setupAxis()
def setupContextMenu(self,vobj,menu):
@staticmethod
def setupMenu(menu, vobj, vobj2):
obj = vobj.Object
action = QtGui.QAction(QtGui.QIcon(),
"Attach" if obj.Detach else "Detach", menu)
if obj.Detach:
action.setToolTip('Attach this element to its linked geometry,\n'
'so that it will auto update on change.')
else:
action.setToolTip('Detach this element so that it stays the same\n'
'on change of the linked geometry.')
QtCore.QObject.connect(
action,QtCore.SIGNAL("triggered()"),self.toggleDetach)
action,QtCore.SIGNAL("triggered()"),vobj.Proxy.toggleDetach)
menu.addAction(action)
if obj.Proxy.isBroken():
action = QtGui.QAction(QtGui.QIcon(), "Fix", menu)
QtCore.QObject.connect(
action,QtCore.SIGNAL("triggered()"),self.fix)
action.setToolTip('Auto fix broken element')
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),vobj.Proxy.fix)
menu.addAction(action)
action = QtGui.QAction(QtGui.QIcon(), 'Offset', menu)
action.setToolTip('Activate dragger to offset this element')
menu.addAction(action)
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),vobj2.Proxy.offset)
def setupContextMenu(self,vobj,menu):
ViewProviderAsmElement.setupMenu(menu, vobj, vobj)
action = QtGui.QAction(QtGui.QIcon(), 'Flip element', menu)
action.setToolTip('Flip this element\' Z normal by rotating 180 degree\n'
'along the X axis (or Y axis by holding the CTRL key).\n\n'
'Note that this is only effective when for elements\n'
'used in "Attachment" constraint. For others, please\n'
'try "Flip part" instead.')
menu.addAction(action)
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),self.flip)
action = QtGui.QAction(QtGui.QIcon(), 'Flip part', menu)
action.setToolTip('Flip the owner part using this element Z normal as\n'
'reference, which is done by rotating 180 degree along\n'
'the element\'s X axis (or Y axis by holding the CTRL key).\n\n'
'Note that this won\'t work for elements in "Attachment"\n'
'constraint. Please try "Flip element" instead.')
menu.addAction(action)
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),self.flipPart)
return True
def fix(self):
obj = self.ViewObject.Object
FreeCAD.setActiveTransaction('Fix element')
@ -1283,6 +1320,39 @@ class ViewProviderAsmElement(ViewProviderAsmOnTop):
FreeCAD.closeActiveTransaction(True)
raise
def offset(self):
from . import mover
return mover.movePart(element=self.ViewObject.Object, moveElement=True)
@staticmethod
def doFlip(obj, info, flipElement=False):
if QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ControlModifier:
rot = FreeCAD.Rotation(FreeCAD.Vector(0,1,0),180)
else:
rot = FreeCAD.Rotation(FreeCAD.Vector(1,0,0),180)
rot = FreeCAD.Placement(FreeCAD.Vector(), rot)
FreeCAD.setActiveTransaction(
'Flip element' if flipElement else 'Flip part')
try:
if flipElement:
obj.Offset = rot.multiply(obj.Offset)
else:
offset = utils.getElementPlacement(obj.getSubObject(''))
offset = offset.inverse().multiply(rot).multiply(offset)
setPlacement(info.Part, info.Placement.multiply(offset))
FreeCAD.closeActiveTransaction()
except Exception:
FreeCAD.closeActiveTransaction(True)
raise
def flip(self):
obj = self.ViewObject.Object
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo())
def flipPart(self):
obj = self.ViewObject.Object
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(), False)
class AsmElementSketch(AsmElement):
def __init__(self,obj,parent):
@ -1950,7 +2020,7 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
def doubleClicked(self,_vobj):
from . import mover
return mover.movePart()
return mover.movePart(element=self.ViewObject.Object, moveElement=False)
def canDropObjectEx(self,_obj,owner,subname,elements):
if len(elements)>1 or not owner:
@ -1970,6 +2040,32 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
subname += elements[0]
vobj.Object.Proxy.setLink(owner,subname)
def setupContextMenu(self,vobj,menu):
element = vobj.Object.LinkedObject
if not isTypeOf(element, AsmElement):
return;
ViewProviderAsmElement.setupMenu(menu, element.ViewObject, vobj)
action = QtGui.QAction(QtGui.QIcon(), 'Flip', menu)
action.setToolTip('For element link inside an "Attachment" constraint,\n'
'flip the element\'s Z normal by rotating 180 degree along\n'
'its X axis (or Y axis by holding the CTRL key). For other\n'
'constraint, flip the owner part instead.')
menu.addAction(action)
QtCore.QObject.connect(action,QtCore.SIGNAL("triggered()"),self.flip)
return True
def offset(self):
from . import mover
return mover.movePart(element=self.ViewObject.Object, moveElement=True)
def flip(self):
obj = self.ViewObject.Object
ViewProviderAsmElement.doFlip(obj, obj.Proxy.getInfo(),
Constraint.isAttachment(obj.Proxy.parent.Object))
class AsmConstraint(AsmGroup):
@ -4196,7 +4292,9 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
self.__class__._Busy = False
def unsetEdit(self,_vobj,_mode):
self._movingPart = None
if self._movingPart:
self._movingPart.end()
self._movingPart = None
return False
def showParts(self):

View File

@ -709,6 +709,11 @@ class Constraint(ProxyType):
else:
obj.setExpression('.Label2', None)
@classmethod
def isAttachment(mcs, obj):
cstr = mcs.getProxy(obj)
return isinstance(cstr, Attachment)
def _makeProp(name,tp,doc='',getter=propGet,internal=False,default=None):
return PropertyInfo(Constraint,name,tp,doc,getter=getter,duplicate=True,

View File

@ -12,20 +12,72 @@ MovingPartInfo = namedtuple('MovingPartInfo',
('Hierarchy','HierarchyList','ElementInfo','SelObj','SelSubname'))
class AsmMovingPart(object):
def __init__(self,hierarchy,info):
def __init__(self, moveInfo, element, moveElement):
hierarchy = moveInfo.HierarchyList
info = moveInfo.ElementInfo
self.objs = [h.Assembly for h in reversed(hierarchy)]
self.assembly = resolveAssembly(info.Parent)
self.viewObject = self.assembly.Object.ViewObject
self.info = info
self.element = element
self.undos = None
self.trace = None
self.tracePoint = None
self.moveElement = moveElement
self.sels = []
view = self.viewObject.Document.ActiveView
shape = None
if hasattr(view, 'addObjectOnTop'):
self.view = view
else:
self.view = None
if element:
if self.view:
self.sels.append((moveInfo.SelObj, moveInfo.SelSubname))
view.addObjectOnTop(*self.sels[0])
logger.debug('group on top {}.{}',
moveInfo.SelObj.Name, moveInfo.SelSubname)
shape = element.getSubObject('')
# whether to move element itself or its owner part
if moveElement:
self.bbox = shape.BoundBox
# Place the dragger at element's current (maybe offseted) shape
# center point in assembly coordinate
self.draggerPlacement = utils.getElementPlacement(shape)
# calculate the placement of an unoffseted element in assembly coordinate
self.offset = utils.getElementPlacement(element.getSubObject('',transform=False))
# Calculate the placement to transform the unoffseted element
# shape to the origin of its own coordinate space
self.offsetInv = self.offset.inverse()
return
# if we are not moving the element, but its owner part, transform
# the element shape to part's coordinate space
shape.Placement = shape.Placement.multiply(info.Placement.inverse());
if self.view:
sub = moveInfo.SelSubname[:-len(info.SubnameRef)]
if isinstance(info.Part,tuple):
sub += '2.{}.{}.'.format(info.Part[0].Name,info.Part[1])
else:
sub += '2.{}.'.format(info.Part.Name)
self.sels.append((moveInfo.SelObj, sub))
logger.debug('group on top {}.{}', moveInfo.SelObj.Name,sub)
view.addObjectOnTop(*self.sels[-1])
fixed = Constraint.getFixedTransform(self.assembly.getConstraints())
fixed = fixed.get(info.Part,None)
self.fixedTransform = fixed
if fixed and fixed.Shape:
shape = fixed.Shape
else:
shape = info.Shape
if not shape:
if fixed and fixed.Shape:
shape = fixed.Shape
else:
shape = info.Shape
rot = utils.getElementRotation(shape)
if not rot:
@ -56,8 +108,6 @@ class AsmMovingPart(object):
self.offset = pla.copy()
self.offsetInv = pla.inverse()
self.draggerPlacement = info.Placement.multiply(pla)
self.trace = None
self.tracePoint = None
@classmethod
def onRollback(cls):
@ -74,10 +124,17 @@ class AsmMovingPart(object):
def begin(self):
self.tracePoint = self.TracePosition
def end(self):
for obj,sub in self.sels:
self.view.removeObjectOnTop(obj,sub)
def update(self):
info = getElementInfo(self.info.Parent,self.info.SubnameRef)
self.info = info
if utils.isDraftObject(info.Part):
if self.element:
shape = self.element.getSubObject('')
pla = utils.getElementPlacement(shape)
elif utils.isDraftObject(info.Part):
pos = utils.getElementPos(info.Shape)
rot = utils.getElementRotation(info.Shape)
pla = info.Placement.multiply(FreeCAD.Placement(pos,rot))
@ -105,11 +162,15 @@ class AsmMovingPart(object):
info = self.info
part = info.Part
obj = self.assembly.Object
pla = self.viewObject.DraggingPlacement
pla = utils.roundPlacement(self.viewObject.DraggingPlacement)
updatePla = True
rollback = []
if not info.Subname.startswith('Face') and utils.isDraftWire(part):
if self.moveElement:
updatePla = False
offset = utils.roundPlacement(self.offsetInv.multiply(pla))
self.element.Offset = offset
elif not info.Subname.startswith('Face') and utils.isDraftWire(part):
updatePla = False
if info.Subname.startswith('Vertex'):
idx = utils.draftWireVertex2PointIndex(part,info.Subname)
@ -133,8 +194,7 @@ class AsmMovingPart(object):
points[idx] = movement.multVec(pt)
part.Points = points
elif info.Subname.startswith('Vertex') and \
utils.isDraftCircle(part):
elif info.Subname.startswith('Vertex') and utils.isDraftCircle(part):
updatePla = False
a1 = part.FirstAngle
a2 = part.LastAngle
@ -226,8 +286,8 @@ def getMovingElementInfo():
'''Extract information from current selection for part moving
It returns a tuple containing the selected assembly hierarchy, and
AsmElementInfo of the selected child part object.
AsmElementInfo of the selected child part object.
If there is only one selection, then the moving part will be one belong to
the highest level assembly in selected hierarchy.
@ -256,7 +316,7 @@ def getMovingElementInfo():
if len(sels[0].SubElementNames)==1:
r = ret[0]
# Warning! Must not calling like below, because r.Assembly maybe a link,
# Warning! Must not call like below, because r.Assembly maybe a link,
# and we need that information
#
# info = getElementInfo(r.Object,r.Subname, ...)
@ -286,7 +346,7 @@ def getMovingElementInfo():
assembly = ret[-1].Assembly
for r in ret2:
if assembly == r.Assembly:
# Warning! Must not calling like below, because r.Assembly maybe a
# Warning! Must not call like below, because r.Assembly maybe a
# link, and we need that information
#
# info = getElementInfo(r.Object,r.Subname, ...)
@ -299,7 +359,7 @@ def getMovingElementInfo():
ElementInfo=info)
raise RuntimeError('not child parent selection')
def movePart(useCenterballDragger=None,moveInfo=None):
def movePart(useCenterballDragger=None, moveInfo=None, element=None, moveElement=False):
if not moveInfo:
moveInfo = logger.catch(
'exception when moving part', getMovingElementInfo)
@ -310,7 +370,7 @@ def movePart(useCenterballDragger=None,moveInfo=None):
if doc:
doc.resetEdit()
vobj = resolveAssembly(info.Parent).Object.ViewObject
vobj.Proxy._movingPart = AsmMovingPart(moveInfo.HierarchyList,info)
vobj.Proxy._movingPart = AsmMovingPart(moveInfo, element, moveElement)
if useCenterballDragger is not None:
vobj.UseCenterballDragger = useCenterballDragger
FreeCADGui.Selection.clearSelection()