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)
|
||||
|
||||
def doubleClicked(self,_vobj):
|
||||
return movePart()
|
||||
from . import mover
|
||||
return mover.movePart()
|
||||
|
||||
def canDropObjectEx(self,_obj,owner,subname):
|
||||
if logger.catchTrace('Cannot drop to AsmLink {}'.format(
|
||||
|
@ -1554,227 +1555,6 @@ class Assembly(AsmGroup):
|
|||
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):
|
||||
def __init__(self,vobj):
|
||||
self._movingPart = None
|
||||
|
@ -1811,7 +1591,8 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
|||
return System.getIcon(self.ViewObject.Object)
|
||||
|
||||
def doubleClicked(self, _vobj):
|
||||
return movePart()
|
||||
from . import mover
|
||||
return mover.movePart()
|
||||
|
||||
def onExecute(self):
|
||||
if not getattr(self,'_movingPart',None):
|
||||
|
@ -1844,14 +1625,15 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
|
|||
|
||||
def onDragStart(self):
|
||||
Assembly.cancelAutoSolve();
|
||||
AsmMovingPart._Busy = True
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
self.__class__._Busy = True
|
||||
FreeCAD.setActiveTransaction('Assembly move')
|
||||
|
||||
def onDragMotion(self):
|
||||
return self._movingPart.move()
|
||||
|
||||
def onDragEnd(self):
|
||||
AsmMovingPart._Busy = False
|
||||
self.__class__._Busy = False
|
||||
FreeCAD.closeActiveTransaction()
|
||||
|
||||
def unsetEdit(self,_vobj,_mode):
|
||||
|
|
|
@ -423,7 +423,7 @@ class Constraint(ProxyType):
|
|||
if elements:
|
||||
info = elements[0].Proxy.getInfo()
|
||||
firstPart = info.Part
|
||||
if not found and firstPart:
|
||||
if not found and firstPart and not utils.isDraftObject(firstPart):
|
||||
ret[firstPart] = False
|
||||
return ret
|
||||
|
||||
|
@ -594,9 +594,10 @@ class Locked(Base):
|
|||
ret = []
|
||||
for e in obj.Proxy.getElements():
|
||||
info = e.Proxy.getInfo()
|
||||
if utils.isDraftObject(info):
|
||||
continue
|
||||
shape = None
|
||||
if utils.isVertex(info.Shape) or \
|
||||
utils.isDraftCircle(info) or \
|
||||
utils.isLinearEdge(info.Shape):
|
||||
shape = info.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'
|
||||
_menuGroupName = ''
|
||||
_contextMenuName = 'Assembly'
|
||||
_accel = None
|
||||
|
||||
@classmethod
|
||||
def checkActive(cls):
|
||||
|
@ -113,17 +114,20 @@ class AsmCmdBase(object):
|
|||
|
||||
@classmethod
|
||||
def GetResources(cls):
|
||||
return {
|
||||
ret = {
|
||||
'Pixmap':addIconToFCAD(cls._iconName),
|
||||
'MenuText':cls.getMenuText(),
|
||||
'ToolTip':cls.getToolTip()
|
||||
}
|
||||
|
||||
if cls._accel:
|
||||
ret['Accel'] = cls._accel
|
||||
return ret
|
||||
|
||||
class AsmCmdNew(AsmCmdBase):
|
||||
_id = 0
|
||||
_menuText = 'Create assembly'
|
||||
_iconName = 'Assembly_New_Assembly.svg'
|
||||
_accel = 'A, N'
|
||||
|
||||
@classmethod
|
||||
def Activated(cls):
|
||||
|
@ -134,6 +138,7 @@ class AsmCmdSolve(AsmCmdBase):
|
|||
_id = 1
|
||||
_menuText = 'Solve constraints'
|
||||
_iconName = 'AssemblyWorkbench.svg'
|
||||
_accel = 'A, S'
|
||||
|
||||
@classmethod
|
||||
def Activated(cls):
|
||||
|
@ -149,16 +154,17 @@ class AsmCmdMove(AsmCmdBase):
|
|||
_menuText = 'Move part'
|
||||
_iconName = 'Assembly_Move.svg'
|
||||
_useCenterballDragger = True
|
||||
_accel = 'A, M'
|
||||
|
||||
@classmethod
|
||||
def Activated(cls):
|
||||
from . import assembly
|
||||
assembly.movePart(cls._useCenterballDragger)
|
||||
from . import mover
|
||||
mover.movePart(cls._useCenterballDragger)
|
||||
|
||||
@classmethod
|
||||
def checkActive(cls):
|
||||
from . import assembly
|
||||
cls._active = assembly.canMovePart()
|
||||
from . import mover
|
||||
cls._active = mover.canMovePart()
|
||||
|
||||
@classmethod
|
||||
def onClearSelection(cls):
|
||||
|
@ -169,6 +175,7 @@ class AsmCmdAxialMove(AsmCmdMove):
|
|||
_menuText = 'Axial move part'
|
||||
_iconName = 'Assembly_AxialMove.svg'
|
||||
_useCenterballDragger = False
|
||||
_accel = 'A, A'
|
||||
|
||||
class AsmCmdCheckable(AsmCmdBase):
|
||||
_id = -2
|
||||
|
|
|
@ -34,7 +34,7 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
|||
cmd.workbenchDeactivated()
|
||||
|
||||
def Initialize(self):
|
||||
from .assembly import AsmDocumentObserver
|
||||
from .mover import AsmDocumentObserver
|
||||
from .gui import AsmCmdManager,SelectionObserver
|
||||
cmdSet = set()
|
||||
for name,cmds in AsmCmdManager.Toolbars.items():
|
||||
|
@ -60,7 +60,6 @@ class Assembly3Workbench(FreeCADGui.Workbench):
|
|||
self.appendContextMenu(name,cmds)
|
||||
|
||||
def ContextMenu(self, _recipient):
|
||||
from .utils import logger
|
||||
logger.catch('',self._contextMenu)
|
||||
|
||||
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)
|
||||
if addDragPoint:
|
||||
info = self._partMap.get(dragPart,None)
|
||||
if info:
|
||||
if info and info.Workplane:
|
||||
# add dragging point
|
||||
self.system.log('add drag point '
|
||||
'{}'.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
|
||||
|
||||
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):
|
||||
if isinstance(v1,(tuple,list)):
|
||||
assert(len(v1)==len(v2))
|
||||
vs = zip(v1,v2)
|
||||
else:
|
||||
vs = (v1,v2),
|
||||
|
||||
for v1,v2 in vs:
|
||||
v = v1-v2
|
||||
if v>=_tol or v<=-_tol:
|
||||
return False
|
||||
return True
|
||||
return all([abs(v1-v2)<_tol for v1,v2 in vs])
|
||||
|
||||
def isSamePos(p1,p2):
|
||||
return p1.distanceToPoint(p2) < _tol
|
||||
|
@ -511,6 +511,13 @@ def draftWireVertex2PointIndex(obj,name):
|
|||
if idx < len(obj.Points):
|
||||
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):
|
||||
'deduct the vertex index from the edge index'
|
||||
idx = getElementIndex(name,'Edge')
|
||||
|
|
Loading…
Reference in New Issue
Block a user