From c738d24b7db9b46f74fe6135401c03ca37c1b5dc Mon Sep 17 00:00:00 2001
From: "Zheng, Lei" <realthunder.dev@gmail.com>
Date: Sun, 3 Dec 2017 18:20:25 +0800
Subject: [PATCH] assembly: fixed linked part moving

---
 assembly.py | 88 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 52 insertions(+), 36 deletions(-)

diff --git a/assembly.py b/assembly.py
index b3ec285..130616c 100644
--- a/assembly.py
+++ b/assembly.py
@@ -4,7 +4,7 @@ import FreeCAD, FreeCADGui
 import asm3
 import asm3.utils as utils
 from asm3.utils import logger, objName
-from asm3.constraint import Constraint
+from asm3.constraint import Constraint, cstrName
 from asm3.system import System
 
 def setupUndo(doc,undoDocs,name):
@@ -35,6 +35,26 @@ def getProxy(obj,tp):
     checkType(obj,tp)
     return obj.Proxy
 
+def resolveAssembly(obj):
+    '''Try various ways to obtain an assembly from the input object
+
+    obj can be a link, a proxy, a child group of an assembly, or simply an
+    assembly
+    '''
+    func = getattr(obj,'getLinkedObject',None)
+    if func:
+        obj = func(True)
+    proxy = getattr(obj,'Proxy',None)
+    if proxy:
+        obj = proxy
+    if isinstance(obj,Assembly):
+        return obj
+    func = getattr(obj,'getAssembly',None)
+    if func:
+        return func()
+    raise TypeError('cannot resolve assembly from {}'.format(obj))
+
+
 # For faking selection obtained from Gui.getSelectionEx()
 Selection = namedtuple('AsmSelection',('Object','SubElementNames'))
 
@@ -170,7 +190,7 @@ class ViewProviderAsmPartGroup(ViewProviderAsmGroup):
         return False
 
     def canDropObjectEx(self,obj,_owner,_subname):
-        return isTypeOf(obj,Assembly) or not isTypeOf(obj,AsmBase)
+        return isTypeOf(obj,Assembly, True) or not isTypeOf(obj,AsmBase)
 
     def canDragObject(self,_obj):
         return True
@@ -413,7 +433,7 @@ class AsmElement(AsmBase):
                 # Pop the immediate child name, and replace it with child
                 # assembly's element group name
                 prefix = prefix[:prefix.rfind('.')+1] + \
-                    ret.Assembly.Proxy.getElementGroup().Name
+                    resolveAssembly(ret.Assembly).getElementGroup().Name
 
                 subname = '{}.${}.'.format(prefix,element.Label)
 
@@ -505,7 +525,7 @@ def getPartInfo(parent, subname):
     subnameRef = subname
 
     names = subname.split('.')
-    if isTypeOf(parent,Assembly):
+    if isTypeOf(parent,Assembly,True):
         child = parent.getSubObject(names[0]+'.',1)
         if not child:
             raise RuntimeError('Invalid sub object {}, {}'.format(
@@ -663,10 +683,14 @@ class AsmElementLink(AsmBase):
         # The reference stored inside this ElementLink. We need the sub assembly
         # name, which is the name before the first dot. This name may be
         # different from the actual assembly object's name, in case where the
-        # assembly is accessed through a link
+        # assembly is accessed through a link. And the sub assembly may be
+        # inside a link array, which we don't know for sure. But we do know that
+        # the last two names are element group and element label. So just pop
+        # two names.
         ref = self.Object.LinkedObject[1]
-        return '{}.{}.{}'.format(ref[0:ref.find('.')],
-                assembly.getPartGroup().Name, element.getElementSubname())
+        prefix = ref[0:ref.rfind('.',0,ref.rfind('.',0,-1))]
+        return '{}.{}.{}'.format(prefix, assembly.getPartGroup().Name,
+                element.getElementSubname())
 
     def setLink(self,owner,subname):
         # check if there is any sub assembly in the reference
@@ -692,7 +716,7 @@ class AsmElementLink(AsmBase):
             # Pop the immediate child name, and replace it with child
             # assembly's element group name
             prefix = prefix[:prefix.rfind('.')+1] + \
-                ret.Assembly.Proxy.getElementGroup().Name
+                resolveAssembly(ret.Assembly).getElementGroup().Name
 
             subname = '{}.${}.'.format(prefix, element.Label)
 
@@ -869,7 +893,8 @@ class AsmConstraint(AsmGroup):
             raise RuntimeError('too many selection')
         if len(subs)==2:
             sobj = sels[0].Object.getSubObject(subs[1],1)
-            if isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
+            if isTypeOf(sobj,Assembly,True) or \
+               isTypeOf(sobj,(AsmConstraintGroup,AsmConstraint)):
                 subs = (subs[1],subs[0])
 
         sel = sels[0]
@@ -889,7 +914,8 @@ class AsmConstraint(AsmGroup):
                     'assembly'.format(sel.Object.Name,sub))
 
             # check if the selection is a constraint group or a constraint
-            if isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
+            if isTypeOf(sobj,Assembly,True) or \
+               isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
                 if assembly:
                     raise RuntimeError('no element selection')
                 assembly = ret[-1].Assembly
@@ -917,8 +943,10 @@ class AsmConstraint(AsmGroup):
             # we shall adjust the element subname by popping the first '.'
             sub = found.Subname
             sub = sub[sub.index('.')+1:]
-            if sub[-1] == '.' and not isTypeOf(sobj,(Assembly,AsmConstraint,
-                    AsmConstraintGroup,AsmElement,AsmElementLink)):
+            if sub[-1] == '.' and \
+               not isTypeOf(sobj,Assembly,True) and \
+               not isTypeOf(sobj,(AsmConstraint,AsmConstraintGroup,
+                                  AsmElement,AsmElementLink)):
                 # Too bad, its a full selection, let's guess the sub element
                 subElement = utils.deduceSelectedElement(found.Object,sub)
                 if not subElement:
@@ -1070,7 +1098,7 @@ class AsmConstraintGroup(AsmGroup):
         return obj
 
 
-class ViewProviderAsmConstraintGroup(ViewProviderAsmGroupOnTop):
+class ViewProviderAsmConstraintGroup(ViewProviderAsmGroup):
     _iconName = 'Assembly_Assembly_Constraints_Tree.svg'
 
     def canDropObjects(self):
@@ -1119,7 +1147,7 @@ class AsmElementGroup(AsmGroup):
         return obj
 
 
-class ViewProviderAsmElementGroup(ViewProviderAsmGroupOnTop):
+class ViewProviderAsmElementGroup(ViewProviderAsmGroup):
     _iconName = 'Assembly_Assembly_Element_Tree.svg'
 
     def onDelete(self,_obj,_subs):
@@ -1263,13 +1291,12 @@ class Assembly(AsmGroup):
         for o in cstrGroup.Group:
             checkType(o,AsmConstraint)
             if Constraint.isDisabled(o):
-                logger.debug('skip constraint "{}" type '
-                    '{}'.format(objName(o),o.Type))
+                logger.debug('skip constraint {}'.format(cstrName(o)))
                 continue
             if not System.isConstraintSupported(self.Object,
                        Constraint.getTypeName(o)):
-                logger.debug('skip unsupported constraint "{}" type '
-                    '{}'.format(objName(o),o.Type))
+                logger.debug('skip unsupported constraint '
+                    '{}'.format(cstrName(o)))
                 continue
             ret.append(o)
         self.constraints = ret
@@ -1351,7 +1378,7 @@ class Assembly(AsmGroup):
             sels = FreeCADGui.Selection.getSelectionEx('',False)
         for sel in sels:
             if not sel.SubElementNames:
-                if isTypeOf(sel.Object,Assembly):
+                if isTypeOf(sel.Object,Assembly,True):
                     objs.add(sel.Object)
                 continue
             for subname in sel.SubElementNames:
@@ -1441,14 +1468,7 @@ class Assembly(AsmGroup):
 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.assembly = resolveAssembly(info.Parent)
         self.parent = info.Parent
         self.subname = info.SubnameRef
         self.undos = None
@@ -1573,7 +1593,7 @@ def getMovingPartInfo():
     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.
+    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
@@ -1597,9 +1617,7 @@ def getMovingPartInfo():
             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)
+        info = getPartInfo(ret[0].Assembly,ret[0].Subname)
         if not info:
             return
         return (ret, info)
@@ -1633,14 +1651,12 @@ def movePart(useCenterballDragger=None):
     doc = FreeCADGui.editDocument()
     if doc:
         doc.resetEdit()
-    if isTypeOf(info.Parent,AsmPartGroup):
-        vobj = info.Parent.Proxy.parent.Object.ViewObject
-    else:
-        vobj = info.Parent.ViewObject
+    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 vobj.Document.setEdit(vobj,1)
+    return doc.setEdit(vobj,1)
 
 
 class ViewProviderAssembly(ViewProviderAsmGroup):