Add mover support for draft wire and circle
* Draft wire points can be dragged individually. * Draft wire individual edge can be moved and rotated. * Draft circle/arc can be moved and rotated by dragging edge or face. * Draft circle/arc radius can be changed by dragging vertex * Draft arc first/last angle can be changed by dragging end points
This commit is contained in:
parent
b087200e50
commit
c56a9d85d7
232
assembly.py
232
assembly.py
|
@ -796,7 +796,8 @@ class ViewProviderAsmElementLink(ViewProviderAsmOnTop):
|
||||||
return (1.0,60.0/255.0,60.0/255.0)
|
return (1.0,60.0/255.0,60.0/255.0)
|
||||||
|
|
||||||
def doubleClicked(self,_vobj):
|
def doubleClicked(self,_vobj):
|
||||||
return movePart()
|
from . import mover
|
||||||
|
return mover.movePart()
|
||||||
|
|
||||||
def canDropObjectEx(self,_obj,owner,subname):
|
def canDropObjectEx(self,_obj,owner,subname):
|
||||||
if logger.catchTrace('Cannot drop to AsmLink {}'.format(
|
if logger.catchTrace('Cannot drop to AsmLink {}'.format(
|
||||||
|
@ -1554,227 +1555,6 @@ class Assembly(AsmGroup):
|
||||||
obj,subname,AsmConstraintGroup,False,relativeToChild)
|
obj,subname,AsmConstraintGroup,False,relativeToChild)
|
||||||
|
|
||||||
|
|
||||||
class AsmMovingPart(object):
|
|
||||||
def __init__(self,hierarchy,info):
|
|
||||||
self.objs = [h.Assembly for h in reversed(hierarchy)]
|
|
||||||
self.assembly = resolveAssembly(info.Parent)
|
|
||||||
self.parent = info.Parent
|
|
||||||
self.subname = info.SubnameRef
|
|
||||||
self.undos = None
|
|
||||||
self.part = info.Part
|
|
||||||
self.partName = info.PartName
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
rot = utils.getElementRotation(shape)
|
|
||||||
if not rot:
|
|
||||||
# in case the shape has no normal, like a vertex, just use an empty
|
|
||||||
# rotation, which means having the same rotation as the owner part.
|
|
||||||
rot = FreeCAD.Rotation()
|
|
||||||
|
|
||||||
hasBound = True
|
|
||||||
if not utils.isVertex(shape):
|
|
||||||
self.bbox = shape.BoundBox
|
|
||||||
else:
|
|
||||||
bbox = info.Object.ViewObject.getBoundingBox()
|
|
||||||
if bbox.isValid():
|
|
||||||
self.bbox = bbox
|
|
||||||
else:
|
|
||||||
logger.warn('empty bounding box of part {}'.format(
|
|
||||||
info.PartName))
|
|
||||||
self.bbox = FreeCAD.BoundBox(0,0,0,5,5,5)
|
|
||||||
hasBound = False
|
|
||||||
|
|
||||||
pos = utils.getElementPos(shape)
|
|
||||||
if not pos:
|
|
||||||
if hasBound:
|
|
||||||
pos = self.bbox.Center
|
|
||||||
else:
|
|
||||||
pos = shape.Placement.Base
|
|
||||||
pla = FreeCAD.Placement(pos,rot)
|
|
||||||
|
|
||||||
self.oldPlacement = info.Placement.copy()
|
|
||||||
self.offset = pla.copy()
|
|
||||||
self.offsetInv = pla.inverse()
|
|
||||||
self.draggerPlacement = info.Placement.multiply(pla)
|
|
||||||
self.tracePoint = self.draggerPlacement.Base
|
|
||||||
self.trace = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def onRollback(cls):
|
|
||||||
doc = FreeCADGui.editDocument()
|
|
||||||
if not doc:
|
|
||||||
return
|
|
||||||
vobj = doc.getInEdit()
|
|
||||||
if vobj and isTypeOf(vobj,ViewProviderAssembly):
|
|
||||||
movingPart = getattr(vobj.Proxy,'_movingPart',None)
|
|
||||||
if movingPart:
|
|
||||||
vobj.Object.recompute(True)
|
|
||||||
movingPart.tracePoint = movingPart.draggerPlacement.Base
|
|
||||||
|
|
||||||
def update(self):
|
|
||||||
info = getElementInfo(self.parent,self.subname)
|
|
||||||
self.oldPlacement = info.Placement.copy()
|
|
||||||
self.part = info.Part
|
|
||||||
self.partName = info.PartName
|
|
||||||
pla = info.Placement.multiply(self.offset)
|
|
||||||
logger.trace('part move update {}: {}'.format(objName(self.parent),pla))
|
|
||||||
self.draggerPlacement = pla
|
|
||||||
return pla
|
|
||||||
|
|
||||||
def move(self):
|
|
||||||
obj = self.assembly.Object
|
|
||||||
pla = obj.ViewObject.DraggingPlacement
|
|
||||||
|
|
||||||
rollback = []
|
|
||||||
if self.fixedTransform:
|
|
||||||
fixed = self.fixedTransform
|
|
||||||
movement = self.draggerPlacement.inverse().multiply(pla)
|
|
||||||
if fixed.Shape:
|
|
||||||
# fixed position, so reset translation
|
|
||||||
movement.Base = FreeCAD.Vector()
|
|
||||||
if not utils.isVertex(fixed.Shape):
|
|
||||||
yaw,_,_ = movement.Rotation.toEuler()
|
|
||||||
# when dragging with a fixed axis, we align the dragger Z
|
|
||||||
# axis with that fixed axis. So we shall only keep the yaw
|
|
||||||
# among the euler angles
|
|
||||||
movement.Rotation = FreeCAD.Rotation(yaw,0,0)
|
|
||||||
pla = self.draggerPlacement.multiply(movement)
|
|
||||||
|
|
||||||
# obtain and update the part placement
|
|
||||||
pla = pla.multiply(self.offsetInv)
|
|
||||||
setPlacement(self.part,pla)
|
|
||||||
rollback.append((self.partName,self.part,self.oldPlacement.copy()))
|
|
||||||
|
|
||||||
if not gui.AsmCmdManager.AutoRecompute or \
|
|
||||||
QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ControlModifier:
|
|
||||||
# AsmCmdManager.AutoRecompute means auto re-solve the system. The
|
|
||||||
# recompute() call below is only for updating linked element and
|
|
||||||
# stuff
|
|
||||||
obj.recompute(True)
|
|
||||||
return
|
|
||||||
|
|
||||||
# calls solver.solve(obj) and redirect all the exceptions message
|
|
||||||
# to logger only.
|
|
||||||
from . import solver
|
|
||||||
if not logger.catch('solver exception when moving part',
|
|
||||||
solver.solve, self.objs, dragPart=self.part, rollback=rollback):
|
|
||||||
obj.recompute(True)
|
|
||||||
|
|
||||||
if gui.AsmCmdManager.Trace and \
|
|
||||||
not self.tracePoint.isEqual(self.draggerPlacement.Base,1e-5):
|
|
||||||
try:
|
|
||||||
# check if the object is deleted
|
|
||||||
self.trace.Name
|
|
||||||
except Exception:
|
|
||||||
self.trace = None
|
|
||||||
mat = FreeCADGui.editDocument().EditingTransform
|
|
||||||
if not self.trace:
|
|
||||||
self.trace = FreeCAD.ActiveDocument.addObject(
|
|
||||||
'Part::Polygon','AsmTrace')
|
|
||||||
self.trace.Nodes = {-1:mat.multiply(self.tracePoint)}
|
|
||||||
self.tracePoint = self.draggerPlacement.Base
|
|
||||||
self.trace.Nodes = {-1:mat.multiply(self.draggerPlacement.Base)}
|
|
||||||
self.trace.recompute()
|
|
||||||
|
|
||||||
# self.draggerPlacement, which holds the intended dragger placement, is
|
|
||||||
# updated by the above solver call through the following chain,
|
|
||||||
# solver.solve() -> (triggers dependent objects recompute when done)
|
|
||||||
# Assembly.execute() ->
|
|
||||||
# ViewProviderAssembly.onExecute() ->
|
|
||||||
# AsmMovingPart.update()
|
|
||||||
return self.draggerPlacement
|
|
||||||
|
|
||||||
def getMovingElementInfo():
|
|
||||||
'''Extract information from current selection for part moving
|
|
||||||
|
|
||||||
It returns a tuple containing the selected assembly hierarchy (obtained from
|
|
||||||
Assembly.findChildren()), and 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.
|
|
||||||
|
|
||||||
If there are two selections, then one selection must be a parent assembly
|
|
||||||
containing the other child object. The moving object will then be the
|
|
||||||
immediate child part object of the owner assembly. The actual selected sub
|
|
||||||
element, i.e. vertex, edge, face will determine the dragger placement
|
|
||||||
'''
|
|
||||||
|
|
||||||
sels = FreeCADGui.Selection.getSelectionEx('',False)
|
|
||||||
if not sels:
|
|
||||||
raise RuntimeError('no selection')
|
|
||||||
|
|
||||||
if not sels[0].SubElementNames:
|
|
||||||
raise RuntimeError('no sub object in selection')
|
|
||||||
|
|
||||||
if len(sels)>1 or len(sels[0].SubElementNames)>2:
|
|
||||||
raise RuntimeError('too many selection')
|
|
||||||
|
|
||||||
ret = Assembly.findChildren(sels[0].Object,sels[0].SubElementNames[0])
|
|
||||||
if not ret:
|
|
||||||
raise RuntimeError('invalid selection {}, subname {}'.format(
|
|
||||||
objName(sels[0].Object),sels[0].SubElementNames[0]))
|
|
||||||
|
|
||||||
if len(sels[0].SubElementNames)==1:
|
|
||||||
info = getElementInfo(ret[0].Assembly,ret[0].Subname)
|
|
||||||
if not info:
|
|
||||||
return
|
|
||||||
return (ret, info)
|
|
||||||
|
|
||||||
ret2 = Assembly.findChildren(sels[0].Object,sels[0].SubElementNames[1])
|
|
||||||
if not ret2:
|
|
||||||
raise RuntimeError('invalid selection {}, subname {}'.format(
|
|
||||||
objName(sels[0].Object),sels[0].SubElementNames[1]))
|
|
||||||
|
|
||||||
if len(ret) == len(ret2):
|
|
||||||
if not ret2[-1].Object:
|
|
||||||
ret,ret2 = ret2,ret
|
|
||||||
elif len(ret) > len(ret2):
|
|
||||||
ret,ret2 = ret2,ret
|
|
||||||
|
|
||||||
assembly = ret[-1].Assembly
|
|
||||||
for r in ret2:
|
|
||||||
if assembly == r.Assembly:
|
|
||||||
return (ret2, getElementInfo(r.Assembly,r.Subname))
|
|
||||||
raise RuntimeError('not child parent selection')
|
|
||||||
|
|
||||||
def canMovePart():
|
|
||||||
return logger.catchTrace('',getMovingElementInfo) is not None
|
|
||||||
|
|
||||||
def movePart(useCenterballDragger=None):
|
|
||||||
ret = logger.catch('exception when moving part', getMovingElementInfo)
|
|
||||||
if not ret:
|
|
||||||
return False
|
|
||||||
|
|
||||||
info = ret[1]
|
|
||||||
doc = FreeCADGui.editDocument()
|
|
||||||
if doc:
|
|
||||||
doc.resetEdit()
|
|
||||||
vobj = resolveAssembly(info.Parent).Object.ViewObject
|
|
||||||
doc = info.Parent.ViewObject.Document
|
|
||||||
if useCenterballDragger is not None:
|
|
||||||
vobj.UseCenterballDragger = useCenterballDragger
|
|
||||||
vobj.Proxy._movingPart = AsmMovingPart(*ret)
|
|
||||||
return doc.setEdit(vobj,1)
|
|
||||||
|
|
||||||
class AsmDocumentObserver:
|
|
||||||
def slotUndoDocument(self,_doc):
|
|
||||||
AsmMovingPart.onRollback()
|
|
||||||
|
|
||||||
def slotRedoDocument(self,_doc):
|
|
||||||
AsmMovingPart.onRollback()
|
|
||||||
|
|
||||||
def slotChangedObject(self,obj,prop):
|
|
||||||
Assembly.checkPartChange(obj,prop)
|
|
||||||
|
|
||||||
|
|
||||||
class ViewProviderAssembly(ViewProviderAsmGroup):
|
class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
def __init__(self,vobj):
|
def __init__(self,vobj):
|
||||||
self._movingPart = None
|
self._movingPart = None
|
||||||
|
@ -1811,7 +1591,8 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
return System.getIcon(self.ViewObject.Object)
|
return System.getIcon(self.ViewObject.Object)
|
||||||
|
|
||||||
def doubleClicked(self, _vobj):
|
def doubleClicked(self, _vobj):
|
||||||
return movePart()
|
from . import mover
|
||||||
|
return mover.movePart()
|
||||||
|
|
||||||
def onExecute(self):
|
def onExecute(self):
|
||||||
if not getattr(self,'_movingPart',None):
|
if not getattr(self,'_movingPart',None):
|
||||||
|
@ -1844,14 +1625,15 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
||||||
|
|
||||||
def onDragStart(self):
|
def onDragStart(self):
|
||||||
Assembly.cancelAutoSolve();
|
Assembly.cancelAutoSolve();
|
||||||
AsmMovingPart._Busy = True
|
FreeCADGui.Selection.clearSelection()
|
||||||
|
self.__class__._Busy = True
|
||||||
FreeCAD.setActiveTransaction('Assembly move')
|
FreeCAD.setActiveTransaction('Assembly move')
|
||||||
|
|
||||||
def onDragMotion(self):
|
def onDragMotion(self):
|
||||||
return self._movingPart.move()
|
return self._movingPart.move()
|
||||||
|
|
||||||
def onDragEnd(self):
|
def onDragEnd(self):
|
||||||
AsmMovingPart._Busy = False
|
self.__class__._Busy = False
|
||||||
FreeCAD.closeActiveTransaction()
|
FreeCAD.closeActiveTransaction()
|
||||||
|
|
||||||
def unsetEdit(self,_vobj,_mode):
|
def unsetEdit(self,_vobj,_mode):
|
||||||
|
|
|
@ -423,7 +423,7 @@ class Constraint(ProxyType):
|
||||||
if elements:
|
if elements:
|
||||||
info = elements[0].Proxy.getInfo()
|
info = elements[0].Proxy.getInfo()
|
||||||
firstPart = info.Part
|
firstPart = info.Part
|
||||||
if not found and firstPart:
|
if not found and firstPart and not utils.isDraftObject(firstPart):
|
||||||
ret[firstPart] = False
|
ret[firstPart] = False
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -594,9 +594,10 @@ class Locked(Base):
|
||||||
ret = []
|
ret = []
|
||||||
for e in obj.Proxy.getElements():
|
for e in obj.Proxy.getElements():
|
||||||
info = e.Proxy.getInfo()
|
info = e.Proxy.getInfo()
|
||||||
|
if utils.isDraftObject(info):
|
||||||
|
continue
|
||||||
shape = None
|
shape = None
|
||||||
if utils.isVertex(info.Shape) or \
|
if utils.isVertex(info.Shape) or \
|
||||||
utils.isDraftCircle(info) or \
|
|
||||||
utils.isLinearEdge(info.Shape):
|
utils.isLinearEdge(info.Shape):
|
||||||
shape = info.Shape
|
shape = info.Shape
|
||||||
ret.append(cls.Info(Part=info.Part,Shape=shape))
|
ret.append(cls.Info(Part=info.Part,Shape=shape))
|
||||||
|
|
19
gui.py
19
gui.py
|
@ -106,6 +106,7 @@ class AsmCmdBase(object):
|
||||||
_toolbarName = 'Assembly3'
|
_toolbarName = 'Assembly3'
|
||||||
_menuGroupName = ''
|
_menuGroupName = ''
|
||||||
_contextMenuName = 'Assembly'
|
_contextMenuName = 'Assembly'
|
||||||
|
_accel = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def checkActive(cls):
|
def checkActive(cls):
|
||||||
|
@ -113,17 +114,20 @@ class AsmCmdBase(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def GetResources(cls):
|
def GetResources(cls):
|
||||||
return {
|
ret = {
|
||||||
'Pixmap':addIconToFCAD(cls._iconName),
|
'Pixmap':addIconToFCAD(cls._iconName),
|
||||||
'MenuText':cls.getMenuText(),
|
'MenuText':cls.getMenuText(),
|
||||||
'ToolTip':cls.getToolTip()
|
'ToolTip':cls.getToolTip()
|
||||||
}
|
}
|
||||||
|
if cls._accel:
|
||||||
|
ret['Accel'] = cls._accel
|
||||||
|
return ret
|
||||||
|
|
||||||
class AsmCmdNew(AsmCmdBase):
|
class AsmCmdNew(AsmCmdBase):
|
||||||
_id = 0
|
_id = 0
|
||||||
_menuText = 'Create assembly'
|
_menuText = 'Create assembly'
|
||||||
_iconName = 'Assembly_New_Assembly.svg'
|
_iconName = 'Assembly_New_Assembly.svg'
|
||||||
|
_accel = 'A, N'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def Activated(cls):
|
def Activated(cls):
|
||||||
|
@ -134,6 +138,7 @@ class AsmCmdSolve(AsmCmdBase):
|
||||||
_id = 1
|
_id = 1
|
||||||
_menuText = 'Solve constraints'
|
_menuText = 'Solve constraints'
|
||||||
_iconName = 'AssemblyWorkbench.svg'
|
_iconName = 'AssemblyWorkbench.svg'
|
||||||
|
_accel = 'A, S'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def Activated(cls):
|
def Activated(cls):
|
||||||
|
@ -149,16 +154,17 @@ class AsmCmdMove(AsmCmdBase):
|
||||||
_menuText = 'Move part'
|
_menuText = 'Move part'
|
||||||
_iconName = 'Assembly_Move.svg'
|
_iconName = 'Assembly_Move.svg'
|
||||||
_useCenterballDragger = True
|
_useCenterballDragger = True
|
||||||
|
_accel = 'A, M'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def Activated(cls):
|
def Activated(cls):
|
||||||
from . import assembly
|
from . import mover
|
||||||
assembly.movePart(cls._useCenterballDragger)
|
mover.movePart(cls._useCenterballDragger)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def checkActive(cls):
|
def checkActive(cls):
|
||||||
from . import assembly
|
from . import mover
|
||||||
cls._active = assembly.canMovePart()
|
cls._active = mover.canMovePart()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def onClearSelection(cls):
|
def onClearSelection(cls):
|
||||||
|
@ -169,6 +175,7 @@ class AsmCmdAxialMove(AsmCmdMove):
|
||||||
_menuText = 'Axial move part'
|
_menuText = 'Axial move part'
|
||||||
_iconName = 'Assembly_AxialMove.svg'
|
_iconName = 'Assembly_AxialMove.svg'
|
||||||
_useCenterballDragger = False
|
_useCenterballDragger = False
|
||||||
|
_accel = 'A, A'
|
||||||
|
|
||||||
class AsmCmdCheckable(AsmCmdBase):
|
class AsmCmdCheckable(AsmCmdBase):
|
||||||
_id = -2
|
_id = -2
|
||||||
|
|
|
@ -34,7 +34,7 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
||||||
cmd.workbenchDeactivated()
|
cmd.workbenchDeactivated()
|
||||||
|
|
||||||
def Initialize(self):
|
def Initialize(self):
|
||||||
from .assembly import AsmDocumentObserver
|
from .mover import AsmDocumentObserver
|
||||||
from .gui import AsmCmdManager,SelectionObserver
|
from .gui import AsmCmdManager,SelectionObserver
|
||||||
cmdSet = set()
|
cmdSet = set()
|
||||||
for name,cmds in AsmCmdManager.Toolbars.items():
|
for name,cmds in AsmCmdManager.Toolbars.items():
|
||||||
|
@ -60,7 +60,6 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
||||||
self.appendContextMenu(name,cmds)
|
self.appendContextMenu(name,cmds)
|
||||||
|
|
||||||
def ContextMenu(self, _recipient):
|
def ContextMenu(self, _recipient):
|
||||||
from .utils import logger
|
|
||||||
logger.catch('',self._contextMenu)
|
logger.catch('',self._contextMenu)
|
||||||
|
|
||||||
FreeCADGui.addWorkbench(Assembly3Workbench)
|
FreeCADGui.addWorkbench(Assembly3Workbench)
|
||||||
|
|
280
mover.py
Normal file
280
mover.py
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
import math
|
||||||
|
import FreeCAD, FreeCADGui
|
||||||
|
from PySide import QtCore, QtGui
|
||||||
|
from . import utils, gui
|
||||||
|
from .assembly import isTypeOf, Assembly, ViewProviderAssembly, \
|
||||||
|
resolveAssembly, getElementInfo, setPlacement
|
||||||
|
from .utils import logger, objName
|
||||||
|
from .constraint import Constraint
|
||||||
|
|
||||||
|
class AsmMovingPart(object):
|
||||||
|
def __init__(self,hierarchy,info):
|
||||||
|
self.objs = [h.Assembly for h in reversed(hierarchy)]
|
||||||
|
self.assembly = resolveAssembly(info.Parent)
|
||||||
|
self.viewObject = self.assembly.Object.ViewObject
|
||||||
|
self.info = info
|
||||||
|
self.undos = None
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
rot = utils.getElementRotation(shape)
|
||||||
|
if not rot:
|
||||||
|
# in case the shape has no normal, like a vertex, just use an empty
|
||||||
|
# rotation, which means having the same rotation as the owner part.
|
||||||
|
rot = FreeCAD.Rotation()
|
||||||
|
|
||||||
|
hasBound = True
|
||||||
|
if not utils.isVertex(shape):
|
||||||
|
self.bbox = shape.BoundBox
|
||||||
|
else:
|
||||||
|
bbox = info.Object.ViewObject.getBoundingBox()
|
||||||
|
if bbox.isValid():
|
||||||
|
self.bbox = bbox
|
||||||
|
else:
|
||||||
|
logger.warn('empty bounding box of part {}'.format(
|
||||||
|
info.PartName))
|
||||||
|
self.bbox = FreeCAD.BoundBox(0,0,0,5,5,5)
|
||||||
|
hasBound = False
|
||||||
|
|
||||||
|
pos = utils.getElementPos(shape)
|
||||||
|
if not pos:
|
||||||
|
if hasBound:
|
||||||
|
pos = self.bbox.Center
|
||||||
|
else:
|
||||||
|
pos = shape.Placement.Base
|
||||||
|
pla = FreeCAD.Placement(pos,rot)
|
||||||
|
|
||||||
|
self.offset = pla.copy()
|
||||||
|
self.offsetInv = pla.inverse()
|
||||||
|
self.draggerPlacement = info.Placement.multiply(pla)
|
||||||
|
self.tracePoint = self.draggerPlacement.Base
|
||||||
|
self.trace = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def onRollback(cls):
|
||||||
|
doc = FreeCADGui.editDocument()
|
||||||
|
if not doc:
|
||||||
|
return
|
||||||
|
vobj = doc.getInEdit()
|
||||||
|
if vobj and isTypeOf(vobj,ViewProviderAssembly):
|
||||||
|
movingPart = getattr(vobj.Proxy,'_movingPart',None)
|
||||||
|
if movingPart:
|
||||||
|
vobj.Object.recompute(True)
|
||||||
|
movingPart.tracePoint = movingPart.draggerPlacement.Base
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
info = getElementInfo(self.info.Parent,self.info.SubnameRef)
|
||||||
|
self.info = info
|
||||||
|
if utils.isDraftObject(info):
|
||||||
|
pos = utils.getElementPos(info.Shape)
|
||||||
|
rot = utils.getElementRotation(info.Shape)
|
||||||
|
pla = info.Placement.multiply(FreeCAD.Placement(pos,rot))
|
||||||
|
else:
|
||||||
|
pla = info.Placement.multiply(self.offset)
|
||||||
|
logger.trace('part move update {}: {}'.format(objName(info.Parent),pla))
|
||||||
|
self.draggerPlacement = pla
|
||||||
|
return pla
|
||||||
|
|
||||||
|
@property
|
||||||
|
def Movement(self):
|
||||||
|
pla = self.viewObject.DraggingPlacement.multiply(
|
||||||
|
self.draggerPlacement.inverse())
|
||||||
|
return utils.roundPlacement(pla)
|
||||||
|
|
||||||
|
def move(self):
|
||||||
|
info = self.info
|
||||||
|
part = info.Part
|
||||||
|
obj = self.assembly.Object
|
||||||
|
pla = self.viewObject.DraggingPlacement
|
||||||
|
updatePla = True
|
||||||
|
|
||||||
|
rollback = []
|
||||||
|
if utils.isDraftWire(part):
|
||||||
|
updatePla = False
|
||||||
|
if info.Subname.startswith('Vertex'):
|
||||||
|
idx = utils.draftWireVertex2PointIndex(part,info.Subname)
|
||||||
|
if idx is None:
|
||||||
|
logger.error('Invalid draft wire vertex {} {}'.format(
|
||||||
|
info.Subname, info.PartName))
|
||||||
|
return
|
||||||
|
change = [idx]
|
||||||
|
else:
|
||||||
|
change = utils.draftWireEdge2PointIndex(part,info.Subname)
|
||||||
|
if change[0] is None or change[1] is None:
|
||||||
|
logger.error('Invalid draft wire edge {} {}'.format(
|
||||||
|
info.Subname, info.PartName))
|
||||||
|
return
|
||||||
|
|
||||||
|
movement = self.Movement
|
||||||
|
points = part.Points
|
||||||
|
for idx in change:
|
||||||
|
pt = points[idx]
|
||||||
|
rollback.append((info.PartName, part, (idx,pt)))
|
||||||
|
points[idx] = movement.multVec(pt)
|
||||||
|
part.Points = points
|
||||||
|
|
||||||
|
elif info.Subname.startswith('Vertex') and \
|
||||||
|
utils.isDraftCircle(part):
|
||||||
|
updatePla = False
|
||||||
|
a1 = part.FirstAngle
|
||||||
|
a2 = part.LastAngle
|
||||||
|
r = part.Radius
|
||||||
|
rollback.append((info.PartName, part, (r,a1,a2)))
|
||||||
|
pt = info.Placement.inverse().multVec(pla.Base)
|
||||||
|
part.Radius = pt.Length
|
||||||
|
if a1 != a2:
|
||||||
|
pt.z = 0
|
||||||
|
a = math.degrees(FreeCAD.Vector(1,0,0).getAngle(pt))
|
||||||
|
if info.Subname.endswith('1'):
|
||||||
|
part.FirstAngle = a
|
||||||
|
else:
|
||||||
|
part.LastAngle = a
|
||||||
|
|
||||||
|
elif self.fixedTransform:
|
||||||
|
fixed = self.fixedTransform
|
||||||
|
movement = self.Movement
|
||||||
|
if fixed.Shape:
|
||||||
|
# fixed position, so reset translation
|
||||||
|
movement.Base = FreeCAD.Vector()
|
||||||
|
if not utils.isVertex(fixed.Shape):
|
||||||
|
yaw,_,_ = movement.Rotation.toEuler()
|
||||||
|
# when dragging with a fixed axis, we align the dragger Z
|
||||||
|
# axis with that fixed axis. So we shall only keep the yaw
|
||||||
|
# among the euler angles
|
||||||
|
movement.Rotation = FreeCAD.Rotation(yaw,0,0)
|
||||||
|
pla = self.draggerPlacement.multiply(movement)
|
||||||
|
|
||||||
|
if updatePla:
|
||||||
|
# obtain and update the part placement
|
||||||
|
pla = pla.multiply(self.offsetInv)
|
||||||
|
setPlacement(info.Part,pla)
|
||||||
|
rollback.append((info.PartName,info.Part,info.Placement.copy()))
|
||||||
|
|
||||||
|
if not gui.AsmCmdManager.AutoRecompute or \
|
||||||
|
QtGui.QApplication.keyboardModifiers()==QtCore.Qt.ControlModifier:
|
||||||
|
# AsmCmdManager.AutoRecompute means auto re-solve the system. The
|
||||||
|
# recompute() call below is only for updating linked element and
|
||||||
|
# stuff
|
||||||
|
obj.recompute(True)
|
||||||
|
return
|
||||||
|
|
||||||
|
# calls solver.solve(obj) and redirect all the exceptions message
|
||||||
|
# to logger only.
|
||||||
|
from . import solver
|
||||||
|
if not logger.catch('solver exception when moving part',
|
||||||
|
solver.solve, self.objs, dragPart=info.Part, rollback=rollback):
|
||||||
|
obj.recompute(True)
|
||||||
|
|
||||||
|
if gui.AsmCmdManager.Trace and \
|
||||||
|
not self.tracePoint.isEqual(self.draggerPlacement.Base,1e-5):
|
||||||
|
try:
|
||||||
|
# check if the object is deleted
|
||||||
|
self.trace.Name
|
||||||
|
except Exception:
|
||||||
|
self.trace = None
|
||||||
|
mat = FreeCADGui.editDocument().EditingTransform
|
||||||
|
if not self.trace:
|
||||||
|
self.trace = FreeCAD.ActiveDocument.addObject(
|
||||||
|
'Part::Polygon','AsmTrace')
|
||||||
|
self.trace.Nodes = {-1:mat.multiply(self.tracePoint)}
|
||||||
|
self.tracePoint = self.draggerPlacement.Base
|
||||||
|
self.trace.Nodes = {-1:mat.multiply(self.draggerPlacement.Base)}
|
||||||
|
self.trace.recompute()
|
||||||
|
|
||||||
|
# self.draggerPlacement, which holds the intended dragger placement, is
|
||||||
|
# updated by the above solver call through the following chain,
|
||||||
|
# solver.solve() -> (triggers dependent objects recompute when done)
|
||||||
|
# Assembly.execute() ->
|
||||||
|
# ViewProviderAssembly.onExecute() ->
|
||||||
|
# AsmMovingPart.update()
|
||||||
|
return self.draggerPlacement
|
||||||
|
|
||||||
|
def getMovingElementInfo():
|
||||||
|
'''Extract information from current selection for part moving
|
||||||
|
|
||||||
|
It returns a tuple containing the selected assembly hierarchy (obtained from
|
||||||
|
Assembly.findChildren()), and 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.
|
||||||
|
|
||||||
|
If there are two selections, then one selection must be a parent assembly
|
||||||
|
containing the other child object. The moving object will then be the
|
||||||
|
immediate child part object of the owner assembly. The actual selected sub
|
||||||
|
element, i.e. vertex, edge, face will determine the dragger placement
|
||||||
|
'''
|
||||||
|
|
||||||
|
sels = FreeCADGui.Selection.getSelectionEx('',False)
|
||||||
|
if not sels:
|
||||||
|
raise RuntimeError('no selection')
|
||||||
|
|
||||||
|
if not sels[0].SubElementNames:
|
||||||
|
raise RuntimeError('no sub object in selection')
|
||||||
|
|
||||||
|
if len(sels)>1 or len(sels[0].SubElementNames)>2:
|
||||||
|
raise RuntimeError('too many selection')
|
||||||
|
|
||||||
|
ret = Assembly.findChildren(sels[0].Object,sels[0].SubElementNames[0])
|
||||||
|
if not ret:
|
||||||
|
raise RuntimeError('invalid selection {}, subname {}'.format(
|
||||||
|
objName(sels[0].Object),sels[0].SubElementNames[0]))
|
||||||
|
|
||||||
|
if len(sels[0].SubElementNames)==1:
|
||||||
|
info = getElementInfo(ret[0].Assembly,ret[0].Subname)
|
||||||
|
if not info:
|
||||||
|
return
|
||||||
|
return (ret, info)
|
||||||
|
|
||||||
|
ret2 = Assembly.findChildren(sels[0].Object,sels[0].SubElementNames[1])
|
||||||
|
if not ret2:
|
||||||
|
raise RuntimeError('invalid selection {}, subname {}'.format(
|
||||||
|
objName(sels[0].Object),sels[0].SubElementNames[1]))
|
||||||
|
|
||||||
|
if len(ret) == len(ret2):
|
||||||
|
if not ret2[-1].Object:
|
||||||
|
ret,ret2 = ret2,ret
|
||||||
|
elif len(ret) > len(ret2):
|
||||||
|
ret,ret2 = ret2,ret
|
||||||
|
|
||||||
|
assembly = ret[-1].Assembly
|
||||||
|
for r in ret2:
|
||||||
|
if assembly == r.Assembly:
|
||||||
|
return (ret2, getElementInfo(r.Assembly,r.Subname))
|
||||||
|
raise RuntimeError('not child parent selection')
|
||||||
|
|
||||||
|
def canMovePart():
|
||||||
|
return logger.catchTrace('',getMovingElementInfo) is not None
|
||||||
|
|
||||||
|
def movePart(useCenterballDragger=None):
|
||||||
|
ret = logger.catch('exception when moving part', getMovingElementInfo)
|
||||||
|
if not ret:
|
||||||
|
return False
|
||||||
|
|
||||||
|
info = ret[1]
|
||||||
|
doc = FreeCADGui.editDocument()
|
||||||
|
if doc:
|
||||||
|
doc.resetEdit()
|
||||||
|
vobj = resolveAssembly(info.Parent).Object.ViewObject
|
||||||
|
doc = info.Parent.ViewObject.Document
|
||||||
|
if useCenterballDragger is not None:
|
||||||
|
vobj.UseCenterballDragger = useCenterballDragger
|
||||||
|
vobj.Proxy._movingPart = AsmMovingPart(*ret)
|
||||||
|
return doc.setEdit(vobj,1)
|
||||||
|
|
||||||
|
class AsmDocumentObserver:
|
||||||
|
def slotUndoDocument(self,_doc):
|
||||||
|
AsmMovingPart.onRollback()
|
||||||
|
|
||||||
|
def slotRedoDocument(self,_doc):
|
||||||
|
AsmMovingPart.onRollback()
|
||||||
|
|
||||||
|
def slotChangedObject(self,obj,prop):
|
||||||
|
Assembly.checkPartChange(obj,prop)
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Solver(object):
|
||||||
addDragPoint = getattr(self.system,'addWhereDragged',None)
|
addDragPoint = getattr(self.system,'addWhereDragged',None)
|
||||||
if addDragPoint:
|
if addDragPoint:
|
||||||
info = self._partMap.get(dragPart,None)
|
info = self._partMap.get(dragPart,None)
|
||||||
if info:
|
if info and info.Workplane:
|
||||||
# add dragging point
|
# add dragging point
|
||||||
self.system.log('add drag point '
|
self.system.log('add drag point '
|
||||||
'{}'.format(info.Workplane[1]))
|
'{}'.format(info.Workplane[1]))
|
||||||
|
|
19
utils.py
19
utils.py
|
@ -469,18 +469,18 @@ def fit_rotation_axis_to_surface1( surface, n_u=3, n_v=3 ):
|
||||||
|
|
||||||
_tol = 10e-7
|
_tol = 10e-7
|
||||||
|
|
||||||
|
def roundPlacement(pla):
|
||||||
|
pos = [ 0.0 if abs(v)<_tol else v for v in pla.Base ]
|
||||||
|
q = [ 0.0 if abs(v)<_tol else v for v in pla.Rotation.Q ]
|
||||||
|
return FreeCAD.Placement(FreeCAD.Vector(*pos),FreeCAD.Rotation(*q))
|
||||||
|
|
||||||
def isSameValue(v1,v2):
|
def isSameValue(v1,v2):
|
||||||
if isinstance(v1,(tuple,list)):
|
if isinstance(v1,(tuple,list)):
|
||||||
assert(len(v1)==len(v2))
|
assert(len(v1)==len(v2))
|
||||||
vs = zip(v1,v2)
|
vs = zip(v1,v2)
|
||||||
else:
|
else:
|
||||||
vs = (v1,v2),
|
vs = (v1,v2),
|
||||||
|
return all([abs(v1-v2)<_tol for v1,v2 in vs])
|
||||||
for v1,v2 in vs:
|
|
||||||
v = v1-v2
|
|
||||||
if v>=_tol or v<=-_tol:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def isSamePos(p1,p2):
|
def isSamePos(p1,p2):
|
||||||
return p1.distanceToPoint(p2) < _tol
|
return p1.distanceToPoint(p2) < _tol
|
||||||
|
@ -511,6 +511,13 @@ def draftWireVertex2PointIndex(obj,name):
|
||||||
if idx < len(obj.Points):
|
if idx < len(obj.Points):
|
||||||
return idx
|
return idx
|
||||||
|
|
||||||
|
def draftWireEdge2PointIndex(obj,name):
|
||||||
|
vname1,vname2 = edge2VertexIndex(name)
|
||||||
|
if not vname1:
|
||||||
|
return None,None
|
||||||
|
return (draftWireVertex2PointIndex(obj,vname1),
|
||||||
|
draftWireVertex2PointIndex(obj,vname2))
|
||||||
|
|
||||||
def edge2VertexIndex(name):
|
def edge2VertexIndex(name):
|
||||||
'deduct the vertex index from the edge index'
|
'deduct the vertex index from the edge index'
|
||||||
idx = getElementIndex(name,'Edge')
|
idx = getElementIndex(name,'Edge')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user