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 from asm3.system import System
def setupUndo(doc,undoDocs,name): def setupUndo(doc,undoDocs,name):
if undoDocs is None:
return
if doc.HasPendingTransaction or doc in undoDocs: if doc.HasPendingTransaction or doc in undoDocs:
return return
if not name: if not name:
@ -110,7 +112,7 @@ class ViewProviderAsmGroup(ViewProviderAsmBase):
def claimChildren(self): def claimChildren(self):
return self.ViewObject.Object.Group return self.ViewObject.Object.Group
def doubleClicked(self): def doubleClicked(self, _vobj):
return False return False
@ -300,142 +302,83 @@ class ViewProviderAsmElement(ViewProviderAsmBase):
vobj.ShapeMaterial.EmissiveColor = self.getDefaultColor() vobj.ShapeMaterial.EmissiveColor = self.getDefaultColor()
vobj.DrawStyle = 1 vobj.DrawStyle = 1
vobj.LineWidth = 4 vobj.LineWidth = 4
vobj.PointSize = 6 vobj.PointSize = 8
def getDefaultColor(self): def getDefaultColor(self):
return (60.0/255.0,1.0,1.0) return (60.0/255.0,1.0,1.0)
PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
'PartName','Placement','Object','Subname','Shape'))
class AsmElementLink(AsmBase): def getPartInfo(parent, subname):
def __init__(self,parent): '''Return a named tuple containing the part object element information
super(AsmElementLink,self).__init__()
self.info = None
self.parent = getProxy(parent,AsmConstraint)
def linkSetup(self,obj): Parameters:
super(AsmElementLink,self).linkSetup(obj)
obj.configLinkProperty('LinkedObject')
# obj.setPropertyStatus('LinkedObject','Immutable')
obj.setPropertyStatus('LinkedObject','ReadOnly')
def attach(self,obj): parent: the parent document object, either an assembly, or a part group
obj.addProperty("App::PropertyXLink","LinkedObject"," Link",'')
super(AsmElementLink,self).attach(obj)
def execute(self,obj): subname: subname reference to the part element (i.e. edge, face, vertex)
obj.ViewObject.Proxy.onExecute(self.getInfo(True))
return False
def getElement(self): Return a named tuple with the following fields:
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): Parent: set to the input parent object
return self.parent.parent.parent
def getSubName(self): SubnameRef: set to the input subname reference
link = self.Object.LinkedObject
if not isinstance(link,tuple):
raise RuntimeError('Invalid element link "{}"'.format(
objName(self.Object)))
return link[1]
def getShapeSubName(self): Part: either the part object, or a tuple(obj, idx) to refer to an element in
element = self.getElement() an link array,
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): PartName: a string name for the part
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): Placement: the placement of the part
return (owner,subname)
# try to see if the reference comes from some nested assembly Object: the object that owns the element. In case 'Part' is an assembly, we
ret = assembly.findChild(owner,subname,recursive=True) the element owner will always be some (grand)child of the 'Part'
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): Subname: the subname reference to the element owner object. The reference is
raise RuntimeError('Invalid element link ' + subname) 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 Shape: Part.Shape of the linked element. The shape's placement is relative
# element if there is one to the owner Part.
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 subnameRef = 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
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('.') 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) part = partGroup.getSubObject(names[0]+'.',1)
if not part: if not part:
raise RuntimeError('Eelement link "{}" borken: {}'.format( raise RuntimeError('Invalid sub object {}, {}'.format(
objName(self.Object),subname)) objName(parent), subnameRef))
# For storing the shape of the element with proper transformation # For storing the shape of the element with proper transformation
shape = None shape = None
@ -445,9 +388,7 @@ class AsmElementLink(AsmBase):
# a link # a link
obj = None obj = None
if not isTypeOf(part,Assembly,True) and \ if not isTypeOf(part,Assembly,True):
not Constraint.isDisabled(self.parent.Object) and \
not Constraint.isLocked(self.parent.Object):
getter = getattr(part.getLinkedObject(True), getter = getattr(part.getLinkedObject(True),
'getLinkExtProperty',None) 'getLinkExtProperty',None)
@ -488,8 +429,8 @@ class AsmElementLink(AsmBase):
part = (part[0],int(idx),part[1]) part = (part[0],int(idx),part[1])
pla = part[0].PlacementList[idx] pla = part[0].PlacementList[idx]
except ValueError: except ValueError:
raise RuntimeError('invalid array subname of element ' raise RuntimeError('invalid array subname of element {}: '
'{}: {}'.format(objName(self.Object),subname)) '{}'.format(objName(parent),subnameRef))
partName = '{}.{}.'.format(part[0].Name,idx) partName = '{}.{}.'.format(part[0].Name,idx)
@ -506,12 +447,145 @@ class AsmElementLink(AsmBase):
obj = part.getLinkedObject(False) obj = part.getLinkedObject(False)
partName = part.Name partName = part.Name
self.info = AsmElementLink.Info(Part = part, return PartInfo(Parent = parent,
SubnameRef = subnameRef,
Part = part,
PartName = partName, PartName = partName,
Placement = pla.copy(), Placement = pla.copy(),
Object = obj, Object = obj,
Subname = subname, Subname = subname,
Shape = shape.copy()) 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 return self.info
@staticmethod @staticmethod
@ -548,84 +622,10 @@ class AsmElementLink(AsmBase):
def setPlacement(part,pla,undoDocs,undoName=None): def setPlacement(part,pla,undoDocs,undoName=None):
AsmElementLink.setPlacement(part,pla,undoDocs,undoName) 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): class ViewProviderAsmElementLink(ViewProviderAsmBase):
def __init__(self,vobj): def doubleClicked(self,_vobj):
self._draggingContext = None return movePart()
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
class AsmConstraint(AsmGroup): class AsmConstraint(AsmGroup):
@ -636,14 +636,6 @@ class AsmConstraint(AsmGroup):
self.parent = getProxy(parent,AsmConstraintGroup) self.parent = getProxy(parent,AsmConstraintGroup)
super(AsmConstraint,self).__init__() 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): def getAssembly(self):
return self.parent.parent return self.parent.parent
@ -653,8 +645,7 @@ class AsmConstraint(AsmGroup):
obj = getattr(self,'Object',None) obj = getattr(self,'Object',None)
if not obj: if not obj:
return return
if Constraint.isLocked(obj) or \ if Constraint.isDisabled(obj):
Constraint.isDisabled(obj):
return return
parent = getattr(self,'parent',None) parent = getattr(self,'parent',None)
if not parent: if not parent:
@ -726,7 +717,7 @@ class AsmConstraint(AsmGroup):
''' '''
sels = FreeCADGui.Selection.getSelectionEx('',False) sels = FreeCADGui.Selection.getSelectionEx('',False)
if not sels: if not sels:
return raise RuntimeError('no selection')
if len(sels)>1: if len(sels)>1:
raise RuntimeError( raise RuntimeError(
'The selections must have a common (grand)parent assembly') 'The selections must have a common (grand)parent assembly')
@ -737,7 +728,10 @@ class AsmConstraint(AsmGroup):
assembly = None assembly = None
for sub in sel.SubElementNames: for sub in sel.SubElementNames:
sobj = sel.Object.getSubObject(sub,1) 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: if not ret:
raise RuntimeError('Selection {}.{} is not from an ' raise RuntimeError('Selection {}.{} is not from an '
'assembly'.format(sel.Object.Name,sub)) 'assembly'.format(sel.Object.Name,sub))
@ -768,7 +762,7 @@ class AsmConstraint(AsmGroup):
typeid = Constraint.getTypeID(cstr) typeid = Constraint.getTypeID(cstr)
info = cstr.Proxy.getInfo() info = cstr.Proxy.getInfo()
check = [o.getShape() for o in info.Elements] + elements check = [o.getShape() for o in info.Elements] + elements
elif typeid: else:
check = elements check = elements
if check: if check:
Constraint.check(typeid,check) Constraint.check(typeid,check)
@ -778,25 +772,39 @@ class AsmConstraint(AsmGroup):
Elements = elements) Elements = elements)
@staticmethod @staticmethod
def make(typeid, selection=None, name='Constraint'): def make(typeid, selection=None, name='Constraint', undo=True):
if not selection: if not selection:
selection = AsmConstraint.getSelection(typeid) selection = AsmConstraint.getSelection(typeid)
if selection.Constraint: if selection.Constraint:
cstr = selection.Constraint cstr = selection.Constraint
if undo:
doc = cstr.Document
doc.openTransaction('Assembly change constraint')
else: else:
constraints = selection.Assembly.Proxy.getConstraintGroup() constraints = selection.Assembly.Proxy.getConstraintGroup()
if undo:
doc = constraints.Document
doc.openTransaction('Assembly make constraint')
cstr = constraints.Document.addObject("App::FeaturePython", cstr = constraints.Document.addObject("App::FeaturePython",
name,AsmConstraint(constraints),None,True) name,AsmConstraint(constraints),None,True)
ViewProviderAsmConstraint(cstr.ViewObject) ViewProviderAsmConstraint(cstr.ViewObject)
constraints.setLink({-1:cstr}) constraints.setLink({-1:cstr})
Constraint.setTypeID(cstr,typeid) Constraint.setTypeID(cstr,typeid)
try:
for e in selection.Elements: for e in selection.Elements:
AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e)) AsmElementLink.make(AsmElementLink.MakeInfo(cstr,*e))
cstr.Proxy._initializing = False cstr.Proxy._initializing = False
if cstr.recompute() and asm3.gui.AsmCmdManager.AutoRecompute: 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 return cstr
except Exception:
if undo:
doc.abortTransaction()
raise
class ViewProviderAsmConstraint(ViewProviderAsmGroup): class ViewProviderAsmConstraint(ViewProviderAsmGroup):
@ -902,6 +910,7 @@ class Assembly(AsmGroup):
self.constraints = None self.constraints = None
self.buildShape() self.buildShape()
System.touch(obj) System.touch(obj)
obj.ViewObject.Proxy.onExecute()
return False # return False to call LinkBaseExtension::execute() return False # return False to call LinkBaseExtension::execute()
def onSolverChanged(self,setup=False): def onSolverChanged(self,setup=False):
@ -1016,6 +1025,11 @@ class Assembly(AsmGroup):
logger.debug('skip constraint "{}" type ' logger.debug('skip constraint "{}" type '
'{}'.format(objName(o),o.Type)) '{}'.format(objName(o),o.Type))
continue 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) ret.append(o)
self.constraints = ret self.constraints = ret
return self.constraints return self.constraints
@ -1078,7 +1092,7 @@ class Assembly(AsmGroup):
Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname')) Info = namedtuple('AssemblyInfo',('Assembly','Object','Subname'))
@staticmethod @staticmethod
def find(sels=None): def getSelection(sels=None):
'Find all assembly objects among the current selection' 'Find all assembly objects among the current selection'
objs = set() objs = set()
if sels is None: if sels is None:
@ -1089,14 +1103,14 @@ class Assembly(AsmGroup):
objs.add(sel.Object) objs.add(sel.Object)
continue continue
for subname in sel.SubElementNames: for subname in sel.SubElementNames:
ret = Assembly.findChild(sel.Object,subname,recursive=True) ret = Assembly.find(sel.Object,subname,recursive=True)
if ret: if ret:
objs.add(ret[-1].Assembly) objs.add(ret[-1].Assembly)
return tuple(objs) return tuple(objs)
@staticmethod @staticmethod
def findChild(obj,subname,childType=None, def find(obj,subname,childType=None,
recursive=False,relativeToChild=True): recursive=False,relativeToChild=True,keepEmptyChild=False):
''' '''
Find the immediate child of the first Assembly referenced in 'subs' Find the immediate child of the first Assembly referenced in 'subs'
@ -1119,55 +1133,256 @@ class Assembly(AsmGroup):
''' '''
assembly = None assembly = None
child = None child = None
idx = -1
if isTypeOf(obj,Assembly,True): if isTypeOf(obj,Assembly,True):
assembly = obj assembly = obj
subs = subname if isinstance(subname,list) else subname.split('.') subs = subname if isinstance(subname,list) else subname.split('.')
i= 0
for i,name in enumerate(subs[:-1]): for i,name in enumerate(subs[:-1]):
obj = obj.getSubObject(name+'.',1) sobj = obj.getSubObject(name+'.',1)
if not obj: if not sobj:
raise RuntimeError('Cannot find sub object {}'.format(name)) raise RuntimeError('Cannot find sub object {}, '
'{}'.format(objName(obj),name))
obj = sobj
if assembly and isTypeOf(obj,childType): if assembly and isTypeOf(obj,childType):
child = obj child = obj
if relativeToChild:
idx = i+1
else:
idx = i
break break
assembly = obj if isTypeOf(obj,Assembly,True) else None assembly = obj if isTypeOf(obj,Assembly,True) else None
if not child: if not child:
if keepEmptyChild and assembly:
ret = Assembly.Info(Assembly=assembly,Object=None,Subname='')
return [ret] if recursive else ret
return return
subs = subs[idx:] ret = Assembly.Info(Assembly = assembly, Object = child,
ret = Assembly.Info(Assembly = assembly, Subname = '.'.join(subs[i+1:] if relativeToChild else subs[i:]))
Object = child,
Subname = '.'.join(subs))
if not recursive: if not recursive:
return ret return ret
nret = Assembly.findChild(child,subs,childType,True) nret = Assembly.find(child, subs[i+1:], childType, recursive,
relativeToChild, keepEmptyChild)
if nret: if nret:
return [ret] + nret return [ret] + nret
return [ret] return [ret]
@staticmethod
def findChildren(obj,subname,tp=None):
return Assembly.find(obj,subname,tp,True,False,True)
@staticmethod @staticmethod
def findPartGroup(obj,subname='2.',recursive=False,relativeToChild=True): def findPartGroup(obj,subname='2.',recursive=False,relativeToChild=True):
return Assembly.findChild( return Assembly.find(
obj,subname,AsmPartGroup,recursive,relativeToChild) obj,subname,AsmPartGroup,recursive,relativeToChild)
@staticmethod @staticmethod
def findElementGroup(obj,subname='1.',relativeToChild=True): def findElementGroup(obj,subname='1.',relativeToChild=True):
return Assembly.findChild( return Assembly.find(
obj,subname,AsmElementGroup,False,relativeToChild) obj,subname,AsmElementGroup,False,relativeToChild)
@staticmethod @staticmethod
def findConstraintGroup(obj,subname='0.',relativeToChild=True): def findConstraintGroup(obj,subname='0.',relativeToChild=True):
return Assembly.findChild( return Assembly.find(
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)]
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): class ViewProviderAssembly(ViewProviderAsmGroup):
def __init__(self,vobj):
self._movingPart = None
super(ViewProviderAssembly,self).__init__(vobj)
def canDragObject(self,_child): def canDragObject(self,_child):
return False return False
@ -1191,3 +1406,39 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
def getIcon(self): def getIcon(self):
return System.getIcon(self.ViewObject.Object) 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' _iconName = 'Assembly_Move.svg'
_useCenterballDragger = True _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 @classmethod
def Activated(cls): def Activated(cls):
vobj = cls.getSelection() asm3.assembly.movePart(cls._useCenterballDragger)
if vobj:
doc = FreeCADGui.editDocument()
if doc:
doc.resetEdit()
vobj.UseCenterballDragger = cls._useCenterballDragger
vobj.doubleClicked()
@classmethod @classmethod
def checkActive(cls): def checkActive(cls):
cls._active = True if cls.getSelection() else False cls._active = asm3.assembly.canMovePart()
@classmethod @classmethod
def onClearSelection(cls): def onClearSelection(cls):