Refactor part move command

This commit is contained in:
Zheng, Lei 2017-10-30 04:01:33 +08:00
parent 398b1fad86
commit f16fbf52a8
2 changed files with 463 additions and 225 deletions

View File

@ -8,6 +8,8 @@ from asm3.constraint import Constraint
from asm3.system import System
def setupUndo(doc,undoDocs,name):
if undoDocs is None:
return
if doc.HasPendingTransaction or doc in undoDocs:
return
if not name:
@ -110,7 +112,7 @@ class ViewProviderAsmGroup(ViewProviderAsmBase):
def claimChildren(self):
return self.ViewObject.Object.Group
def doubleClicked(self):
def doubleClicked(self, _vobj):
return False
@ -300,142 +302,83 @@ class ViewProviderAsmElement(ViewProviderAsmBase):
vobj.ShapeMaterial.EmissiveColor = self.getDefaultColor()
vobj.DrawStyle = 1
vobj.LineWidth = 4
vobj.PointSize = 6
vobj.PointSize = 8
def getDefaultColor(self):
return (60.0/255.0,1.0,1.0)
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
class AsmElementLink(AsmBase):
def __init__(self,parent):
super(AsmElementLink,self).__init__()
self.info = None
self.parent = getProxy(parent,AsmConstraint)
def getPartInfo(parent, subname):
'''Return a named tuple containing the part object element information
def linkSetup(self,obj):
super(AsmElementLink,self).linkSetup(obj)
obj.configLinkProperty('LinkedObject')
# obj.setPropertyStatus('LinkedObject','Immutable')
obj.setPropertyStatus('LinkedObject','ReadOnly')
Parameters:
def attach(self,obj):
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
super(AsmElementLink,self).attach(obj)
parent: the parent document object, either an assembly, or a part group
def execute(self,obj):
obj.ViewObject.Proxy.onExecute(self.getInfo(True))
return False
subname: subname reference to the part element (i.e. edge, face, vertex)
def getElement(self):
linked = self.Object.getLinkedObject(False)
if not linked:
raise RuntimeError('Element link broken')
if not isTypeOf(linked,AsmElement):
raise RuntimeError('Invalid element type')
return linked.Proxy
Return a named tuple with the following fields:
def getAssembly(self):
return self.parent.parent.parent
Parent: set to the input parent object
def getSubName(self):
link = self.Object.LinkedObject
if not isinstance(link,tuple):
raise RuntimeError('Invalid element link "{}"'.format(
objName(self.Object)))
return link[1]
SubnameRef: set to the input subname reference
def getShapeSubName(self):
element = self.getElement()
assembly = element.getAssembly()
if assembly == self.getAssembly():
return element.getSubName()
# pop two names from back (i.e. element group, element)
subname = self.getSubName()
sub = subname.split('.')[:-3]
sub = '.'.join(sub) + '.' + assembly.getPartGroup().Name + \
'.' + element.getSubName()
logger.debug('shape subname {} -> {}'.format(subname,sub))
return sub
Part: either the part object, or a tuple(obj, idx) to refer to an element in
an link array,
def prepareLink(self,owner,subname):
assembly = self.getAssembly()
sobj = owner.getSubObject(subname,1)
if not sobj:
raise RuntimeError('invalid element link {} broken: {}'.format(
objName(owner),subname))
if isTypeOf(sobj,AsmElementLink):
# if it points to another AsElementLink that belongs the same
# assembly, simply return the same link
if sobj.Proxy.getAssembly() == assembly:
return sobj.LinkedObject
# If it is from another assembly (i.e. a nested assembly), convert
# the subname reference by poping three names (constraint group,
# constraint, element link) from the back, and then append with the
# element link's own subname reference
sub = subname.split('.')[:-4]
sub = '.'.join(subname)+'.'+sobj.Proxy.getSubName()
logger.debug('convert element link {} -> {}'.format(subname,sub))
return (owner,sub)
PartName: a string name for the part
if isTypeOf(sobj,AsmElement):
return (owner,subname)
Placement: the placement of the part
# try to see if the reference comes from some nested assembly
ret = assembly.findChild(owner,subname,recursive=True)
if not ret:
# It is from a non assembly child part, then use our own element
# group as the holder for elements
ret = [Assembly.Info(assembly.Object,owner,subname)]
Object: the object that owns the element. In case 'Part' is an assembly, we
the element owner will always be some (grand)child of the 'Part'
if not isTypeOf(ret[-1].Object,AsmPartGroup):
raise RuntimeError('Invalid element link ' + subname)
Subname: the subname reference to the element owner object. The reference is
realtive to the 'Part', i.e. Object = Part.getSubObject(subname), or if
'Part' is a tuple, Object = Part[0].getSubObject(str(Part[1]) + '.' +
subname)
# call AsmElement.make to either create a new element, or an existing
# element if there is one
element = AsmElement.make(AsmElement.Selection(
ret[-1].Assembly,None,ret[-1].Subname))
if ret[-1].Assembly == assembly.Object:
return (assembly.getElementGroup(),element.Name+'.')
Shape: Part.Shape of the linked element. The shape's placement is relative
to the owner Part.
'''
elementSub = ret[-1].Object.Name + '.' + ret[-1].Subname
sub = subname[:-(len(elementSub)+1)] + '.' + \
ret[-1].Assembly.Proxy.getElementGroup().Name + '.' + \
element.Name + '.'
logger.debug('generate new element {} -> {}'.format(subname,sub))
return (owner,sub)
subnameRef = subname
def setLink(self,owner,subname):
obj = self.Object
obj.setLink(*self.prepareLink(owner,subname))
linked = obj.getLinkedObject(False)
if linked and linked!=obj:
label = linked.Label.split('_')
if label[-1].startswith('Element'):
label[-1] = 'Link'
obj.Label = '_'.join(label)
else:
obj.Label = obj.Name
Info = namedtuple('AsmElementLinkInfo',
('Part','PartName','Placement','Object','Subname','Shape'))
def getInfo(self,refresh=False):
if not refresh:
ret = getattr(self,'info',None)
if ret:
return ret
self.info = None
if not getattr(self,'Object',None):
return
assembly = self.getAssembly()
subname = self.getShapeSubName()
names = subname.split('.')
partGroup = assembly.getPartGroup()
if isTypeOf(parent,Assembly):
child = parent.getSubObject(names[0]+'.',1)
if not child:
raise RuntimeError('Invalid sub object {}, {}'.format(
objName(parent), subname))
if isTypeOf(child,AsmElementGroup):
raise RuntimeError('Element object cannot be moved directly')
if isTypeOf(child,AsmConstraintGroup):
child = parent.getSubObject(subname,1)
if not child:
raise RuntimeError('Invalid sub object {}, {}'.format(
objName(parent), subname))
if not isTypeOf(child,AsmElementLink):
raise RuntimeError('{} cannot be moved'.format(objName(child)))
return child.Proxy.getInfo()
partGroup = child
names = names[1:]
subname = '.'.join(names)
elif isTypeOf(parent,AsmPartGroup):
partGroup = parent
else:
raise RuntimeError('{} is not Assembly or PartGroup'.format(
objName(parent)))
part = partGroup.getSubObject(names[0]+'.',1)
if not part:
raise RuntimeError('Eelement link "{}" borken: {}'.format(
objName(self.Object),subname))
raise RuntimeError('Invalid sub object {}, {}'.format(
objName(parent), subnameRef))
# For storing the shape of the element with proper transformation
shape = None
@ -445,9 +388,7 @@ class AsmElementLink(AsmBase):
# a link
obj = None
if not isTypeOf(part,Assembly,True) and \
not Constraint.isDisabled(self.parent.Object) and \
not Constraint.isLocked(self.parent.Object):
if not isTypeOf(part,Assembly,True):
getter = getattr(part.getLinkedObject(True),
'getLinkExtProperty',None)
@ -488,8 +429,8 @@ class AsmElementLink(AsmBase):
part = (part[0],int(idx),part[1])
pla = part[0].PlacementList[idx]
except ValueError:
raise RuntimeError('invalid array subname of element '
'{}: {}'.format(objName(self.Object),subname))
raise RuntimeError('invalid array subname of element {}: '
'{}'.format(objName(parent),subnameRef))
partName = '{}.{}.'.format(part[0].Name,idx)
@ -506,12 +447,145 @@ class AsmElementLink(AsmBase):
obj = part.getLinkedObject(False)
partName = part.Name
self.info = AsmElementLink.Info(Part = part,
return PartInfo(Parent = parent,
SubnameRef = subnameRef,
Part = part,
PartName = partName,
Placement = pla.copy(),
Object = obj,
Subname = subname,
Shape = shape.copy())
class AsmElementLink(AsmBase):
def __init__(self,parent):
super(AsmElementLink,self).__init__()
self.info = None
self.parent = getProxy(parent,AsmConstraint)
def linkSetup(self,obj):
super(AsmElementLink,self).linkSetup(obj)
obj.configLinkProperty('LinkedObject')
# obj.setPropertyStatus('LinkedObject','Immutable')
obj.setPropertyStatus('LinkedObject','ReadOnly')
def attach(self,obj):
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
super(AsmElementLink,self).attach(obj)
def execute(self,_obj):
self.getInfo(True)
return False
def getElement(self):
linked = self.Object.getLinkedObject(False)
if not linked:
raise RuntimeError('Element link broken')
if not isTypeOf(linked,AsmElement):
raise RuntimeError('Invalid element type')
return linked.Proxy
def getAssembly(self):
return self.parent.parent.parent
def getSubName(self):
link = self.Object.LinkedObject
if not isinstance(link,tuple):
raise RuntimeError('Invalid element link "{}"'.format(
objName(self.Object)))
return link[1]
def getElementSubname(self):
'''Resolve element link subname
AsmElementLink links to elements of a constraint. It always link to an
AsmElement object belonging to the same parent assembly. AsmElement is
also a link, which links to an arbitarily nested part object. This
function is for resolving the AsmElementLink's subname reference to the
actual part object subname reference relative to the parent assembly's
part group
'''
element = self.getElement()
assembly = element.getAssembly()
if assembly == self.getAssembly():
return element.getSubName()
# pop two names from back (i.e. element group, element)
subname = self.getSubName()
sub = subname.split('.')[:-3]
sub = '.'.join(sub) + '.' + assembly.getPartGroup().Name + \
'.' + element.getSubName()
logger.debug('shape subname {} -> {}'.format(subname,sub))
return sub
def prepareLink(self,owner,subname):
assembly = self.getAssembly()
sobj = owner.getSubObject(subname,1)
if not sobj:
raise RuntimeError('invalid element link {} broken: {}'.format(
objName(owner),subname))
if isTypeOf(sobj,AsmElementLink):
# if it points to another AsElementLink that belongs the same
# assembly, simply return the same link
if sobj.Proxy.getAssembly() == assembly:
return sobj.LinkedObject
# If it is from another assembly (i.e. a nested assembly), convert
# the subname reference by poping three names (constraint group,
# constraint, element link) from the back, and then append with the
# element link's own subname reference
sub = subname.split('.')[:-4]
sub = '.'.join(subname)+'.'+sobj.Proxy.getSubName()
logger.debug('convert element link {} -> {}'.format(subname,sub))
return (owner,sub)
if isTypeOf(sobj,AsmElement):
return (owner,subname)
# try to see if the reference comes from some nested assembly
ret = assembly.find(owner,subname,recursive=True)
if not ret:
# It is from a non assembly child part, then use our own element
# group as the holder for elements
ret = [Assembly.Info(assembly.Object,owner,subname)]
if not isTypeOf(ret[-1].Object,AsmPartGroup):
raise RuntimeError('Invalid element link ' + subname)
# call AsmElement.make to either create a new element, or an existing
# element if there is one
element = AsmElement.make(AsmElement.Selection(
ret[-1].Assembly,None,ret[-1].Subname))
if ret[-1].Assembly == assembly.Object:
return (assembly.getElementGroup(),element.Name+'.')
elementSub = ret[-1].Object.Name + '.' + ret[-1].Subname
sub = subname[:-(len(elementSub)+1)] + '.' + \
ret[-1].Assembly.Proxy.getElementGroup().Name + '.' + \
element.Name + '.'
logger.debug('generate new element {} -> {}'.format(subname,sub))
return (owner,sub)
def setLink(self,owner,subname):
obj = self.Object
obj.setLink(*self.prepareLink(owner,subname))
linked = obj.getLinkedObject(False)
if linked and linked!=obj:
label = linked.Label.split('_')
if label[-1].startswith('Element'):
label[-1] = 'Link'
obj.Label = '_'.join(label)
else:
obj.Label = obj.Name
def getInfo(self,refresh=False):
if not refresh:
ret = getattr(self,'info',None)
if ret:
return ret
self.info = None
if not getattr(self,'Object',None):
return
self.info = getPartInfo(self.getAssembly().getPartGroup(),
self.getElementSubname())
return self.info
@staticmethod
@ -548,84 +622,10 @@ class AsmElementLink(AsmBase):
def setPlacement(part,pla,undoDocs,undoName=None):
AsmElementLink.setPlacement(part,pla,undoDocs,undoName)
class AsmDraggingContext(object):
def __init__(self,info):
self.undos = None
self.part = info.Part
rot = utils.getElementRotation(info.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 has the owner part.
rot = FreeCAD.Rotation()
pla = FreeCAD.Placement(utils.getElementPos(info.Shape),rot)
self.offset = FreeCAD.Placement(pla.toMatrix())
self.offsetInv = FreeCAD.Placement(pla.toMatrix().inverse())
self.placement = info.Placement.multiply(pla)
self.tracePoint = self.placement.Base
self.trace = None
def update(self,info):
self.part = info.Part
pla = info.Placement.multiply(FreeCAD.Placement(self.offset))
self.placement = pla
if asm3.gui.AsmCmdManager.Trace and \
self.tracePoint.isEqual(pla.Base,1e5):
if not self.trace:
self.trace = FreeCAD.ActiveDocument.addObject(
'Part::Polygon','AsmTrace')
self.trace.Nodes = {-1:self.tracePoint}
self.tracePoint = pla.Base
self.trace.Nodes = {-1:pla.Base}
self.trace.recompute()
return pla
class ViewProviderAsmElementLink(ViewProviderAsmBase):
def __init__(self,vobj):
self._draggingContext = None
super(ViewProviderAsmElementLink,self).__init__(vobj)
def doubleClicked(self, vobj):
return vobj.Document.setEdit(vobj,1)
def onExecute(self,info):
if not getattr(self,'_draggingContext',None):
return
self.ViewObject.DraggingPlacement = self._draggingContext.update(info)
def initDraggingPlacement(self):
info = self.ViewObject.Object.Proxy.getInfo()
self._draggingContext = AsmDraggingContext(info)
return (FreeCADGui.editDocument().EditingTransform,
self._draggingContext.placement,
info.Shape.BoundBox)
def onDragStart(self):
self._draggingContext.undos = set()
def onDragMotion(self):
ctx = self._draggingContext
pla = self.ViewObject.DraggingPlacement.multiply(ctx.offsetInv)
setPlacement(ctx.part,pla,ctx.undos, 'Assembly drag')
from PySide import QtCore,QtGui
obj = self.ViewObject.Object
if QtGui.QApplication.keyboardModifiers() == QtCore.Qt.ControlModifier:
obj.getLinkedObject(False).recompute()
obj.recompute()
return
obj.Proxy.parent.solve()
return ctx.placement
def onDragEnd(self):
for doc in self._draggingContext.undos:
doc.commitTransaction()
def unsetEdit(self,_vobj,_mode):
self._draggingContext = None
return False
def doubleClicked(self,_vobj):
return movePart()
class AsmConstraint(AsmGroup):
@ -636,14 +636,6 @@ class AsmConstraint(AsmGroup):
self.parent = getProxy(parent,AsmConstraintGroup)
super(AsmConstraint,self).__init__()
def solve(self, excp=False):
try:
asm3.solver.solve(self.getAssembly().Object)
except RuntimeError as e:
if excp:
raise e
logger.error(e)
def getAssembly(self):
return self.parent.parent
@ -653,8 +645,7 @@ class AsmConstraint(AsmGroup):
obj = getattr(self,'Object',None)
if not obj:
return
if Constraint.isLocked(obj) or \
Constraint.isDisabled(obj):
if Constraint.isDisabled(obj):
return
parent = getattr(self,'parent',None)
if not parent:
@ -726,7 +717,7 @@ class AsmConstraint(AsmGroup):
'''
sels = FreeCADGui.Selection.getSelectionEx('',False)
if not sels:
return
raise RuntimeError('no selection')
if len(sels)>1:
raise RuntimeError(
'The selections must have a common (grand)parent assembly')
@ -737,7 +728,10 @@ class AsmConstraint(AsmGroup):
assembly = None
for sub in sel.SubElementNames:
sobj = sel.Object.getSubObject(sub,1)
ret = Assembly.findChild(sel.Object,sub,recursive=True)
if not sobj:
raise RuntimeError('Cannot find sub-object "{}" of {}'.format(
sub,sel.Object))
ret = Assembly.find(sel.Object,sub,recursive=True)
if not ret:
raise RuntimeError('Selection {}.{} is not from an '
'assembly'.format(sel.Object.Name,sub))
@ -768,7 +762,7 @@ class AsmConstraint(AsmGroup):
typeid = Constraint.getTypeID(cstr)
info = cstr.Proxy.getInfo()
check = [o.getShape() for o in info.Elements] + elements
elif typeid:
else:
check = elements
if check:
Constraint.check(typeid,check)
@ -778,25 +772,39 @@ class AsmConstraint(AsmGroup):
Elements = elements)
@staticmethod
def make(typeid, selection=None, name='Constraint'):
def make(typeid, selection=None, name='Constraint', undo=True):
if not selection:
selection = AsmConstraint.getSelection(typeid)
if selection.Constraint:
cstr = selection.Constraint
if undo:
doc = cstr.Document
doc.openTransaction('Assembly change constraint')
else:
constraints = selection.Assembly.Proxy.getConstraintGroup()
if undo:
doc = constraints.Document
doc.openTransaction('Assembly make constraint')
cstr = constraints.Document.addObject("App::FeaturePython",
name,AsmConstraint(constraints),None,True)
ViewProviderAsmConstraint(cstr.ViewObject)
constraints.setLink({-1:cstr})
Constraint.setTypeID(cstr,typeid)
try:
for e in selection.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
cstr.Proxy._initializing = False
if cstr.recompute() and asm3.gui.AsmCmdManager.AutoRecompute:
cstr.Proxy.solve()
logger.catch('solver exception when auto recompute',
asm3.solver.solve, selection.Assembly.Object, undo=undo)
if undo:
doc.commitTransaction()
return cstr
except Exception:
if undo:
doc.abortTransaction()
raise
class ViewProviderAsmConstraint(ViewProviderAsmGroup):
@ -902,6 +910,7 @@ class Assembly(AsmGroup):
self.constraints = None
self.buildShape()
System.touch(obj)
obj.ViewObject.Proxy.onExecute()
return False # return False to call LinkBaseExtension::execute()
def onSolverChanged(self,setup=False):
@ -1016,6 +1025,11 @@ class Assembly(AsmGroup):
logger.debug('skip constraint "{}" type '
'{}'.format(objName(o),o.Type))
continue
if not System.isConstraintSupported(self.Object,
Constraint.getTypeName(o)):
logger.debug('skip unsupported constraint "{}" type '
'{}'.format(objName(o),o.Type))
continue
ret.append(o)
self.constraints = ret
return self.constraints
@ -1078,7 +1092,7 @@ class Assembly(AsmGroup):
Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname'))
@staticmethod
def find(sels=None):
def getSelection(sels=None):
'Find all assembly objects among the current selection'
objs = set()
if sels is None:
@ -1089,14 +1103,14 @@ class Assembly(AsmGroup):
objs.add(sel.Object)
continue
for subname in sel.SubElementNames:
ret = Assembly.findChild(sel.Object,subname,recursive=True)
ret = Assembly.find(sel.Object,subname,recursive=True)
if ret:
objs.add(ret[-1].Assembly)
return tuple(objs)
@staticmethod
def findChild(obj,subname,childType=None,
recursive=False,relativeToChild=True):
def find(obj,subname,childType=None,
recursive=False,relativeToChild=True,keepEmptyChild=False):
'''
Find the immediate child of the first Assembly referenced in 'subs'
@ -1119,55 +1133,256 @@ class Assembly(AsmGroup):
'''
assembly = None
child = None
idx = -1
if isTypeOf(obj,Assembly,True):
assembly = obj
subs = subname if isinstance(subname,list) else subname.split('.')
i= 0
for i,name in enumerate(subs[:-1]):
obj = obj.getSubObject(name+'.',1)
if not obj:
raise RuntimeError('Cannot find sub object {}'.format(name))
sobj = obj.getSubObject(name+'.',1)
if not sobj:
raise RuntimeError('Cannot find sub object {}, '
'{}'.format(objName(obj),name))
obj = sobj
if assembly and isTypeOf(obj,childType):
child = obj
if relativeToChild:
idx = i+1
else:
idx = i
break
assembly = obj if isTypeOf(obj,Assembly,True) else None
if not child:
if keepEmptyChild and assembly:
ret = Assembly.Info(Assembly=assembly,Object=None,Subname='')
return [ret] if recursive else ret
return
subs = subs[idx:]
ret = Assembly.Info(Assembly = assembly,
Object = child,
Subname = '.'.join(subs))
ret = Assembly.Info(Assembly = assembly, Object = child,
Subname = '.'.join(subs[i+1:] if relativeToChild else subs[i:]))
if not recursive:
return ret
nret = Assembly.findChild(child,subs,childType,True)
nret = Assembly.find(child, subs[i+1:], childType, recursive,
relativeToChild, keepEmptyChild)
if nret:
return [ret] + nret
return [ret]
@staticmethod
def findChildren(obj,subname,tp=None):
return Assembly.find(obj,subname,tp,True,False,True)
@staticmethod
def findPartGroup(obj,subname='2.',recursive=False,relativeToChild=True):
return Assembly.findChild(
return Assembly.find(
obj,subname,AsmPartGroup,recursive,relativeToChild)
@staticmethod
def findElementGroup(obj,subname='1.',relativeToChild=True):
return Assembly.findChild(
return Assembly.find(
obj,subname,AsmElementGroup,False,relativeToChild)
@staticmethod
def findConstraintGroup(obj,subname='0.',relativeToChild=True):
return Assembly.findChild(
return Assembly.find(
obj,subname,AsmConstraintGroup,False,relativeToChild)
class AsmMovingPart(object):
def __init__(self,hierarchy,info):
self.objs = [h.Assembly for h in reversed(hierarchy)]
if isTypeOf(info.Parent,Assembly):
self.assembly = info.Parent.Proxy
elif isTypeOf(info.Parent,AsmPartGroup):
self.assembly = info.Parent.Proxy.parent
else:
raise RuntimeError('invalid moving part parent object {}'.format(
objName(info.Parent)))
self.parent = info.Parent
self.subname = info.SubnameRef
self.undos = None
self.part = info.Part
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 has the owner part.
rot = FreeCAD.Rotation()
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)
pla = FreeCAD.Placement(utils.getElementPos(shape),rot)
self.offset = pla.copy()
self.offsetInv = pla.inverse()
self.draggerPlacement = info.Placement.multiply(pla)
self.tracePoint = self.draggerPlacement.Base
self.trace = None
def update(self):
info = getPartInfo(self.parent,self.subname)
self.part = info.Part
pla = info.Placement.multiply(FreeCAD.Placement(self.offset))
logger.trace('part move update {}: {}'.format(objName(self.parent),pla))
self.draggerPlacement = pla
if asm3.gui.AsmCmdManager.Trace and \
self.tracePoint.isEqual(pla.Base,1e5):
if not self.trace:
self.trace = FreeCAD.ActiveDocument.addObject(
'Part::Polygon','AsmTrace')
self.trace.Nodes = {-1:self.tracePoint}
self.tracePoint = pla.Base
self.trace.Nodes = {-1:pla.Base}
self.trace.recompute()
return pla
_undoName = 'Assembly move'
def move(self):
obj = self.assembly.Object
pla = obj.ViewObject.DraggingPlacement
update = True
if self.fixedTransform:
fixed = self.fixedTransform
movement = self.draggerPlacement.inverse().multiply(pla)
if not fixed.Shape:
# The moving part has completly fixed placement, so we move the
# parent assembly instead
pla = obj.Placement.multiply(movement)
setPlacement(obj,pla,self.undos,self._undoName)
update = False
else:
# 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 update:
# obtain and update the part placement
pla = pla.multiply(self.offsetInv)
setPlacement(self.part,pla,self.undos,self._undoName)
if not asm3.gui.AsmCmdManager.AutoRecompute:
# AsmCmdManager.AutoRecompute means auto re-solve the system. The
# recompute() call below is only for updating linked element and
# stuff
obj.recompute(True)
return
System.touch(obj)
# calls asm3.solver.solve(obj) and redirect all the exceptions message
# to logger only.
logger.catch('solver exception when moving part',
asm3.solver.solve,self.objs)
# 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 getMovingPartInfo():
'''Extract information from current selection for part moving
It returns a tuple containing the selected assembly hierarchy (obtained from
Assembly.findChildren()), and AsmPartInfo of the selected child part object.
If there is only one selection, then the moving part will be one belong to
the deepest nested assembly object is 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 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 = getPartInfo(ret[-1].Assembly,ret[-1].Subname)
if not info and len(ret)>1:
info = getPartInfo(ret[-2].Assembly,ret[-2].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, getPartInfo(r.Assembly,r.Subname))
raise RuntimeError('not child parent selection')
def canMovePart():
return logger.catchTrace('',getMovingPartInfo) is not None
def movePart(useCenterballDragger=None):
ret = logger.catch('exception when moving part', getMovingPartInfo)
if not ret:
return False
info = ret[1]
doc = FreeCADGui.editDocument()
if doc:
doc.resetEdit()
if isTypeOf(info.Parent,AsmPartGroup):
vobj = info.Parent.Proxy.parent.Object.ViewObject
else:
vobj = info.Parent.ViewObject
if useCenterballDragger is not None:
vobj.UseCenterballDragger = useCenterballDragger
vobj.Proxy._movingPart = AsmMovingPart(*ret)
return vobj.Document.setEdit(vobj,1)
class ViewProviderAssembly(ViewProviderAsmGroup):
def __init__(self,vobj):
self._movingPart = None
super(ViewProviderAssembly,self).__init__(vobj)
def canDragObject(self,_child):
return False
@ -1191,3 +1406,39 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
def getIcon(self):
return System.getIcon(self.ViewObject.Object)
def doubleClicked(self, _vobj):
return movePart()
def onExecute(self):
if not getattr(self,'_movingPart',None):
return
pla = logger.catch('exception when update moving part',
self._movingPart.update)
if pla:
self.ViewObject.DraggingPlacement = pla
else:
doc = FreeCADGui.editDocument()
if doc:
doc.resetEdit()
def initDraggingPlacement(self):
if not getattr(self,'_movingPart',None):
return
return (FreeCADGui.editDocument().EditingTransform,
self._movingPart.draggerPlacement,
self._movingPart.bbox)
def onDragStart(self):
self._movingPart.undos = set()
def onDragMotion(self):
return self._movingPart.move()
def onDragEnd(self):
for doc in self._movingPart.undos:
doc.commitTransaction()
def unsetEdit(self,_vobj,_mode):
self._movingPart = None
return False

17
gui.py
View File

@ -141,26 +141,13 @@ class AsmCmdMove(AsmCmdBase):
_iconName = 'Assembly_Move.svg'
_useCenterballDragger = True
@classmethod
def getSelection(cls):
from asm3.assembly import isTypeOf,AsmElementLink
sels = FreeCADGui.Selection.getSelection()
if len(sels)==1 and isTypeOf(sels[0],AsmElementLink):
return sels[0].ViewObject
@classmethod
def Activated(cls):
vobj = cls.getSelection()
if vobj:
doc = FreeCADGui.editDocument()
if doc:
doc.resetEdit()
vobj.UseCenterballDragger = cls._useCenterballDragger
vobj.doubleClicked()
asm3.assembly.movePart(cls._useCenterballDragger)
@classmethod
def checkActive(cls):
cls._active = True if cls.getSelection() else False
cls._active = asm3.assembly.canMovePart()
@classmethod
def onClearSelection(cls):