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,11 +302,160 @@ 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'))
def getPartInfo(parent, subname):
'''Return a named tuple containing the part object element information
Parameters:
parent: the parent document object, either an assembly, or a part group
subname: subname reference to the part element (i.e. edge, face, vertex)
Return a named tuple with the following fields:
Parent: set to the input parent object
SubnameRef: set to the input subname reference
Part: either the part object, or a tuple(obj, idx) to refer to an element in
an link array,
PartName: a string name for the part
Placement: the placement of the part
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'
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)
Shape: Part.Shape of the linked element. The shape's placement is relative
to the owner Part.
'''
subnameRef = subname
names = subname.split('.')
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('Invalid sub object {}, {}'.format(
objName(parent), subnameRef))
# For storing the shape of the element with proper transformation
shape = None
# For storing the placement of the movable part
pla = None
# For storing the actual geometry object of the part, in case 'part' is
# a link
obj = None
if not isTypeOf(part,Assembly,True):
getter = getattr(part.getLinkedObject(True),
'getLinkExtProperty',None)
# special treatment of link array (i.e. when ElementCount!=0), we
# allow the array element to be moveable by the solver
if getter and getter('ElementCount'):
# store both the part (i.e. the link array), and the array
# element object
part = (part,part.getSubObject(names[1]+'.',1))
# trim the subname to be after the array element
sub = '.'.join(names[2:])
# There are two states of an link array.
if getter('ElementList'):
# a) The elements are expanded as individual objects, i.e
# when ElementList has members, then the moveable Placement
# is a property of the array element. So we obtain the shape
# before 'Placement' by setting 'transform' set to False.
shape=part[1].getSubObject(sub,transform=False)
pla = part[1].Placement
obj = part[0].getLinkedObject(False)
partName = part[1].Name
else:
# b) The elements are collapsed. Then the moveable Placement
# is stored inside link object's PlacementList property. So,
# the shape obtained below is already before 'Placement',
# i.e. no need to set 'transform' to False.
shape=part[1].getSubObject(sub)
obj = part[1]
try:
idx = names[1].split('_i')[-1]
# we store the array index instead, in order to modified
# Placement later when the solver is done. Also because
# that when the elements are collapsed, there is really
# no element object here.
part = (part[0],int(idx),part[1])
pla = part[0].PlacementList[idx]
except ValueError:
raise RuntimeError('invalid array subname of element {}: '
'{}'.format(objName(parent),subnameRef))
partName = '{}.{}.'.format(part[0].Name,idx)
subname = sub
if not shape:
# Here means, either the 'part' is an assembly or it is a non array
# object. We trim the subname reference to be relative to the part
# object. And obtain the shape before part's Placement by setting
# 'transform' to False
subname = '.'.join(names[1:])
shape = part.getSubObject(subname,transform=False)
pla = part.Placement
obj = part.getLinkedObject(False)
partName = part.Name
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):
@ -322,8 +473,8 @@ class AsmElementLink(AsmBase):
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
super(AsmElementLink,self).attach(obj)
def execute(self,obj):
obj.ViewObject.Proxy.onExecute(self.getInfo(True))
def execute(self,_obj):
self.getInfo(True)
return False
def getElement(self):
@ -344,7 +495,16 @@ class AsmElementLink(AsmBase):
objName(self.Object)))
return link[1]
def getShapeSubName(self):
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():
@ -381,7 +541,7 @@ class AsmElementLink(AsmBase):
return (owner,subname)
# try to see if the reference comes from some nested assembly
ret = assembly.findChild(owner,subname,recursive=True)
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
@ -416,9 +576,6 @@ class AsmElementLink(AsmBase):
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)
@ -427,91 +584,8 @@ class AsmElementLink(AsmBase):
self.info = None
if not getattr(self,'Object',None):
return
assembly = self.getAssembly()
subname = self.getShapeSubName()
names = subname.split('.')
partGroup = assembly.getPartGroup()
part = partGroup.getSubObject(names[0]+'.',1)
if not part:
raise RuntimeError('Eelement link "{}" borken: {}'.format(
objName(self.Object),subname))
# For storing the shape of the element with proper transformation
shape = None
# For storing the placement of the movable part
pla = None
# For storing the actual geometry object of the part, in case 'part' is
# a link
obj = None
if not isTypeOf(part,Assembly,True) and \
not Constraint.isDisabled(self.parent.Object) and \
not Constraint.isLocked(self.parent.Object):
getter = getattr(part.getLinkedObject(True),
'getLinkExtProperty',None)
# special treatment of link array (i.e. when ElementCount!=0), we
# allow the array element to be moveable by the solver
if getter and getter('ElementCount'):
# store both the part (i.e. the link array), and the array
# element object
part = (part,part.getSubObject(names[1]+'.',1))
# trim the subname to be after the array element
sub = '.'.join(names[2:])
# There are two states of an link array.
if getter('ElementList'):
# a) The elements are expanded as individual objects, i.e
# when ElementList has members, then the moveable Placement
# is a property of the array element. So we obtain the shape
# before 'Placement' by setting 'transform' set to False.
shape=part[1].getSubObject(sub,transform=False)
pla = part[1].Placement
obj = part[0].getLinkedObject(False)
partName = part[1].Name
else:
# b) The elements are collapsed. Then the moveable Placement
# is stored inside link object's PlacementList property. So,
# the shape obtained below is already before 'Placement',
# i.e. no need to set 'transform' to False.
shape=part[1].getSubObject(sub)
obj = part[1]
try:
idx = names[1].split('_i')[-1]
# we store the array index instead, in order to modified
# Placement later when the solver is done. Also because
# that when the elements are collapsed, there is really
# no element object here.
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))
partName = '{}.{}.'.format(part[0].Name,idx)
subname = sub
if not shape:
# Here means, either the 'part' is an assembly or it is a non array
# object. We trim the subname reference to be relative to the part
# object. And obtain the shape before part's Placement by setting
# 'transform' to False
subname = '.'.join(names[1:])
shape = part.getSubObject(subname,transform=False)
pla = part.Placement
obj = part.getLinkedObject(False)
partName = part.Name
self.info = AsmElementLink.Info(Part = part,
PartName = partName,
Placement = pla.copy(),
Object = obj,
Subname = subname,
Shape = shape.copy())
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)
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()
return cstr
try:
for e in selection.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
cstr.Proxy._initializing = False
if cstr.recompute() and asm3.gui.AsmCmdManager.AutoRecompute:
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):