assembly: add 'Offset' and 'Flip' menu action for AsmElement(Link)
This commit is contained in:
parent
f7c032ed5d
commit
66d7fbd4fa
freecad/asm3
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue
Block a user