diff --git a/assembly.py b/assembly.py
index 057f8be..deae02d 100644
--- a/assembly.py
+++ b/assembly.py
@@ -28,13 +28,16 @@ def isTypeOf(obj,tp,resolve=False):
 
 def checkType(obj,tp,resolve=False):
     if not isTypeOf(obj,tp,resolve):
-        raise TypeError('Expect object "{}" to be of type "{}"'.format(
+        raise TypeError('Expect object {} to be of type "{}"'.format(
                 objName(obj),tp.__name__))
 
 def getProxy(obj,tp):
     checkType(obj,tp)
     return obj.Proxy
 
+# For faking selection obtained from Gui.getSelectionEx()
+Selection = namedtuple('AsmSelection',('Object','SubElementNames'))
+
 class AsmBase(object):
     def __init__(self):
         self.Object = None
@@ -86,6 +89,15 @@ class ViewProviderAsmBase(object):
         if cls._iconName:
             return utils.getIcon(cls)
 
+    def canDropObjects(self):
+        return True
+
+    def canDragObjects(self):
+        return False
+
+    def canDragAndDropObject(self,_obj):
+        return False
+
 
 class AsmGroup(AsmBase):
     def linkSetup(self,obj):
@@ -115,6 +127,9 @@ class ViewProviderAsmGroup(ViewProviderAsmBase):
     def doubleClicked(self, _vobj):
         return False
 
+    def canDropObject(self,_child):
+        return False
+
 
 class AsmPartGroup(AsmGroup):
     def __init__(self,parent):
@@ -139,12 +154,19 @@ class ViewProviderAsmPartGroup(ViewProviderAsmBase):
     def onDelete(self,_obj,_subs):
         return False
 
-    def canDropObject(self,obj):
+    def canDropObjectEx(self,obj,_owner,_subname):
         return isTypeOf(obj,Assembly) or not isTypeOf(obj,AsmBase)
 
-    def canDropObjects(self):
+    def canDragObject(self,_obj):
         return True
 
+    def canDragObjects(self):
+        return True
+
+    def canDragAndDropObject(self,_obj):
+        return True
+
+
 class AsmElement(AsmBase):
     def __init__(self,parent):
         self.shape = None
@@ -237,17 +259,24 @@ class AsmElement(AsmBase):
                     'The selections must have a common (grand)parent assembly')
 
         sel = sels[0]
-        subs = sel.SubElementNames
+        subs = list(sel.SubElementNames)
+        if not subs:
+            raise RuntimeError('no sub object in selection')
         if len(subs)>2:
             raise RuntimeError('At most two selection is allowed.\n'
                 'The first selection must be a sub element belonging to some '
                 'assembly. The optional second selection must be an element '
                 'belonging to the same assembly of the first selection')
+        if len(subs)==2:
+            if len(subs[0])<len(subs[1]):
+                subs = [subs[1],subs[2]]
 
-        subElement = subs[0].split('.')[-1]
-        if not subElement:
-            raise RuntimeError(
-                'Please select a sub element belonging to some assembly')
+        if subs[0][-1] == '.':
+            subElement = utils.deduceSelectedElement(sel.Object,subs[0])
+            if not subElement:
+                raise RuntimeError('no sub element (face, edge, vertex) in '
+                        '{}.{}'.format(sel.Object.Name,subs[0]))
+            subs[0] += subElement
 
         link = Assembly.findPartGroup(sel.Object,subs[0])
         if not link:
@@ -276,14 +305,17 @@ class AsmElement(AsmBase):
     def make(selection=None,name='Element'):
         if not selection:
             selection = AsmElement.getSelection()
+        if not selection.Subname or selection.Subname[-1]=='.':
+            raise RuntimeError('Subname must refer to a sub-element')
         assembly = getProxy(selection.Assembly,Assembly)
         element = selection.Element
         if not element:
             elements = assembly.getElementGroup()
             # try to search the element group for an existing element
             for e in elements.Group:
-                if getProxy(e,AsmElement).getSubName() == selection.Subname:
-                    return element
+                sub = logger.catch('',e.Proxy.getSubName)
+                if sub == selection.Subname:
+                    return e
             element = elements.Document.addObject("App::FeaturePython",
                     name,AsmElement(elements),None,True)
             ViewProviderAsmElement(element.ViewObject)
@@ -307,6 +339,20 @@ class ViewProviderAsmElement(ViewProviderAsmBase):
     def getDefaultColor(self):
         return (60.0/255.0,1.0,1.0)
 
+    def canDropObjectEx(self,_obj,owner,subname):
+        # check if is dropping a sub-element
+        if not subname or subname[-1]=='.':
+            return False
+        proxy = self.ViewObject.Object.Proxy
+        return proxy.getAssembly().getPartGroup()==owner
+
+    def dropObjectEx(self,vobj,_obj,_owner,subname):
+        obj = vobj.Object
+        AsmElement.make(AsmElement.Selection(
+            Assembly=obj.Proxy.getAssembly().Object,
+            Element=obj, Subname=subname))
+
+
 PartInfo = namedtuple('AsmPartInfo', ('Parent','SubnameRef','Part',
     'PartName','Placement','Object','Subname','Shape'))
 
@@ -517,7 +563,9 @@ class AsmElementLink(AsmBase):
         logger.debug('shape subname {} -> {}'.format(subname,sub))
         return sub
 
-    def prepareLink(self,owner,subname):
+    def prepareLink(self,owner,subname,checkOnly=False):
+        if not owner or not subname:
+            raise RuntimeError('no owner or subname')
         assembly = self.getAssembly()
         sobj = owner.getSubObject(subname,1)
         if not sobj:
@@ -550,6 +598,11 @@ class AsmElementLink(AsmBase):
         if not isTypeOf(ret[-1].Object,AsmPartGroup):
             raise RuntimeError('Invalid element link ' + subname)
 
+        if checkOnly:
+            if not ret[-1].Subname or ret[-1].Subname[-1]=='.':
+                raise RuntimeError('Subname must refer to a sub-element')
+            return True
+
         # call AsmElement.make to either create a new element, or an existing
         # element if there is one
         element = AsmElement.make(AsmElement.Selection(
@@ -566,7 +619,16 @@ class AsmElementLink(AsmBase):
 
     def setLink(self,owner,subname):
         obj = self.Object
-        obj.setLink(*self.prepareLink(owner,subname))
+        owner,subname = self.prepareLink(owner,subname)
+        for sibling in self.parent.Object.Group:
+            if sibling == self.Object:
+                continue
+            linked = sibling.LinkedObject
+            if isinstance(linked,tuple) and \
+               linked[0]==owner and linked[1]==subname:
+                raise RuntimeError('duplicate element link {} in constraint '
+                    '{}'.format(objName(sibling),objName(self.parent.Object)))
+        obj.setLink(owner,subname)
         linked = obj.getLinkedObject(False)
         if linked and linked!=obj:
             label = linked.Label.split('_')
@@ -607,7 +669,7 @@ class AsmElementLink(AsmBase):
             setupUndo(part.Document,undoDocs,undoName)
             part.Placement = pla
 
-    MakeInfo = namedtuple('AsmElementLinkSelection',
+    MakeInfo = namedtuple('AsmElementLinkMakeInfo',
             ('Constraint','Owner','Subname'))
 
     @staticmethod
@@ -627,6 +689,17 @@ class ViewProviderAsmElementLink(ViewProviderAsmBase):
     def doubleClicked(self,_vobj):
         return movePart()
 
+    def canDropObjectEx(self,_obj,owner,subname):
+        if logger.catchTrace('Cannot drop to AsmLink {}'.format(
+            objName(self.ViewObject.Object)),
+            self.ViewObject.Object.Proxy.prepareLink,
+            owner, subname, True):
+            return True
+        return False
+
+    def dropObjectEx(self,vobj,_obj,owner,subname):
+        vobj.Object.Proxy.setLink(owner,subname)
+
 
 class AsmConstraint(AsmGroup):
 
@@ -700,77 +773,100 @@ class AsmConstraint(AsmGroup):
                 return
             shapes.append(info.Shape)
             elements.append(o)
-        Constraint.check(obj,shapes)
+        Constraint.check(obj,shapes,True)
         self.elements = elements
         return self.elements
 
-    Selection = namedtuple('ConstraintSelection',
+    Selection = namedtuple('AsmConstraintSelection',
                 ('SelObject','SelSubname','Assembly','Constraint','Elements'))
 
     @staticmethod
-    def getSelection(typeid=0):
+    def getSelection(typeid=0,sels=None):
         '''
         Parse Gui.Selection for making a constraint
 
         The selected elements must all belong to the same immediate parent
         assembly. 
         '''
-        sels = FreeCADGui.Selection.getSelectionEx('',False)
+        if not sels:
+            sels = FreeCADGui.Selection.getSelectionEx('',False)
         if not sels:
             raise RuntimeError('no selection')
         if len(sels)>1:
             raise RuntimeError(
                     'The selections must have a common (grand)parent assembly')
+        subs = sels[0].SubElementNames
+        if not subs:
+            raise RuntimeError('no sub-object in selection')
+        if len(subs)>2:
+            raise RuntimeError('too many selection')
+        if len(subs)==2:
+            sobj = sels[0].Object.getSubObject(subs[1],1)
+            if isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
+                subs = (subs[1],subs[0])
 
         sel = sels[0]
         cstr = None
         elements = []
         assembly = None
         selSubname = None
-        for sub in sel.SubElementNames:
+        for sub in subs:
             sobj = sel.Object.getSubObject(sub,1)
             if not sobj:
-                raise RuntimeError('Cannot find sub-object "{}" of {}'.format(
-                    sub,sel.Object))
+                raise RuntimeError('Cannot find sub-object {}.{}'.format(
+                    sel.Object.Name,sub))
             ret = Assembly.find(sel.Object,sub,
                     recursive=True,relativeToChild=False)
             if not ret:
                 raise RuntimeError('Selection {}.{} is not from an '
                     'assembly'.format(sel.Object.Name,sub))
+
+            # check if the selection is a constraint group or a constraint
+            if isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
+                if assembly:
+                    raise RuntimeError('no element selection')
+                assembly = ret[-1].Assembly
+                selSubname = sub[:-len(ret[-1].Subname)]
+                if isTypeOf(sobj,AsmConstraint):
+                    cstr = sobj
+                continue
+
             if not assembly:
-                # check if the selection is a constraint group or a constraint
-                if isTypeOf(sobj,(AsmConstraintGroup,Assembly,AsmConstraint)):
-                    assembly = ret[-1].Assembly
-                    selSubname = sub[:-len(ret[-1].Subname)]
-                    if isTypeOf(sobj,AsmConstraint):
-                        cstr = sobj
-                    continue
                 assembly = ret[0].Assembly
                 selSubname = sub[:-len(ret[0].Subname)]
-
-            found = None
-            for r in ret:
-                if r.Assembly == assembly:
-                    found = r
-                    break
-            if not found:
-                raise RuntimeError('Selection {}.{} is not from the target '
-                    'assembly {}'.format(sel.Object.Name,sub,objName(assembly)))
+                found = ret[0]
+            else:
+                found = None
+                for r in ret:
+                    if r.Assembly == assembly:
+                        found = r
+                        break
+                if not found:
+                    raise RuntimeError('Selection {}.{} is not from the target '
+                        'assembly {}'.format(
+                            sel.Object.Name,sub,objName(assembly)))
 
             # because we call Assembly.find() above with relativeToChild=False,
             # 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)):
+                # Too bad, its a full selection, let's guess the sub element
+                subElement = utils.deduceSelectedElement(found.Object,sub)
+                if not subElement:
+                    raise RuntimeError('no sub element (face, edge, vertex) in '
+                        '{}.{}'.format(found.Object.Name,sub))
+                sub += subElement
+
             elements.append((found.Object,sub))
 
-        check = None
-        if cstr and not Constraint.isDisabled(cstr):
-            typeid = Constraint.getTypeID(cstr)
-            info = cstr.Proxy.getInfo()
-            check = [o.getShape() for o in info.Elements] + elements
-        else:
-            check = elements
-        if check:
+        if not Constraint.isDisabled(cstr):
+            if cstr:
+                typeid = Constraint.getTypeID(cstr)
+                check = [o.Proxy.getInfo().Shape for o in cstr.Group] + elements
+            else:
+                check = elements
             Constraint.check(typeid,check)
 
         return AsmConstraint.Selection(SelObject=sel.Object,
@@ -780,7 +876,7 @@ class AsmConstraint(AsmGroup):
                                        Elements = elements)
 
     @staticmethod
-    def make(typeid, sel=None, name='Constraint', undo=True):
+    def make(typeid,sel=None,name='Constraint',undo=True):
         if not sel:
             sel = AsmConstraint.getSelection(typeid)
         if sel.Constraint:
@@ -795,7 +891,10 @@ class AsmConstraint(AsmGroup):
                 doc.openTransaction('Assembly make constraint')
             cstr = constraints.Document.addObject("App::FeaturePython",
                     name,AsmConstraint(constraints),None,True)
-            ViewProviderAsmConstraint(cstr.ViewObject)
+            proxy = ViewProviderAsmConstraint(cstr.ViewObject)
+            logger.debug('cstr viewobject {},{},{},{}'.format(
+                id(proxy),id(cstr.ViewObject.Proxy),
+                id(proxy.ViewObject),id(cstr.ViewObject)))
             constraints.setLink({-1:cstr})
             Constraint.setTypeID(cstr,typeid)
 
@@ -809,13 +908,15 @@ class AsmConstraint(AsmGroup):
             if undo:
                 doc.commitTransaction()
 
-            FreeCADGui.Selection.clearSelection()
-            subname = sel.SelSubname
-            if subname:
-                subname += '.'
-            subname += sel.Assembly.Proxy.getConstraintGroup().Name + '.' + \
-                    cstr.Name + '.'
-            FreeCADGui.Selection.addSelection(sel.SelObject,subname)
+            if sel.SelObject:
+                FreeCADGui.Selection.clearSelection()
+                subname = sel.SelSubname
+                if subname:
+                    subname += '.'
+                subname += sel.Assembly.Proxy.getConstraintGroup().Name + \
+                        '.' + cstr.Name + '.'
+                FreeCADGui.Selection.addSelection(sel.SelObject,subname)
+                FreeCADGui.runCommand('Std_TreeSelection')
             return cstr
 
         except Exception:
@@ -837,6 +938,44 @@ class ViewProviderAsmConstraint(ViewProviderAsmGroup):
     def getIcon(self):
         return Constraint.getIcon(self.ViewObject.Object)
 
+    def _getSelection(self,owner,subname):
+        if not owner:
+            raise RuntimeError('no owner')
+        parent = getattr(owner.Proxy,'parent',None)
+        if isinstance(parent,AsmConstraintGroup):
+            # This can happen when we are dropping another element link from the
+            # same constraint group, in which case, 'owner' here will be the
+            # parent constraint of the dropping element link
+            subname = owner.Name + '.' + subname
+            owner = parent.Object
+            parent = parent.parent # ascend to the parent assembly
+        if not isinstance(parent,Assembly):
+            raise RuntimeError('not from the same assembly')
+        subname = owner.Name + '.' + subname
+        obj = self.ViewObject.Object
+        mysub = parent.getConstraintGroup().Name + '.' + obj.Name + '.'
+        sel = [Selection(Object=parent.Object,SubElementNames=[subname,mysub])]
+        typeid = Constraint.getTypeID(obj)
+        return AsmConstraint.getSelection(typeid,sel)
+
+    def canDropObjectEx(self,_obj,owner,subname):
+        cstr = self.ViewObject.Object
+        if logger.catchTrace('Cannot drop to AsmConstraint {}'.format(cstr),
+                self._getSelection,owner,subname):
+            return True
+        return False
+
+    def dropObjectEx(self,_vobj,_obj,owner,subname):
+        sel = self._getSelection(owner,subname)
+        cstr = self.ViewObject.Object
+        typeid = Constraint.getTypeID(cstr)
+        sel = AsmConstraint.Selection(SelObject=None,
+                                      SelSubname=None,
+                                      Assembly=sel.Assembly,
+                                      Constraint=cstr,
+                                      Elements=sel.Elements)
+        AsmConstraint.make(typeid,sel,undo=False)
+
 
 class AsmConstraintGroup(AsmGroup):
     def __init__(self,parent):
@@ -864,6 +1003,9 @@ class AsmConstraintGroup(AsmGroup):
 class ViewProviderAsmConstraintGroup(ViewProviderAsmBase):
     _iconName = 'Assembly_Assembly_Constraints_Tree.svg'
 
+    def canDropObjects(self):
+        return False
+
 
 class AsmElementGroup(AsmGroup):
     def __init__(self,parent):
@@ -876,6 +1018,9 @@ class AsmElementGroup(AsmGroup):
         for o in obj.Group:
             getProxy(o,AsmElement).parent = self
 
+    def getAssembly(self):
+        return self.parent
+
     @staticmethod
     def make(parent,name='Elements'):
         obj = parent.Document.addObject("App::FeaturePython",name,
@@ -891,24 +1036,17 @@ class ViewProviderAsmElementGroup(ViewProviderAsmBase):
     def onDelete(self,_obj,_subs):
         return False
 
-    def canDragObject(self,_obj):
-        return False
-
-    def canDragObjects(self):
-        return False
-
-    def canDragAndDropObject(self,_obj):
-        return False
-
     def canDropObjectEx(self,_obj,owner,subname):
         # check if is dropping a sub-element
-        if subname.rfind('.')+1 == len(subname):
+        if not subname or subname[-1]=='.':
             return False
-        return self.ViewObject.Object.Proxy.parent.getPartGroup()==owner
+        proxy = self.ViewObject.Object.Proxy
+        return proxy.getAssembly().getPartGroup()==owner
 
     def dropObjectEx(self,vobj,_obj,_owner,subname):
         AsmElement.make(AsmElement.Selection(
-            vobj.Object.Proxy.parent.Object,None,subname))
+            Assembly=vobj.Object.Proxy.getAssembly().Object,
+            Element=None, Subname=subname))
 
 
 BuildShapeNone = 'None'
@@ -1341,6 +1479,9 @@ def getMovingPartInfo():
     if not sels:
         raise RuntimeError('no selection')
 
+    if not sels[0].SubElementNames:
+        raise RuntimeError('no sub object in selection')
+
     if len(sels)>1 or len(sels[0].SubElementNames)>2:
         raise RuntimeError('too many selection')
 
@@ -1401,24 +1542,29 @@ class ViewProviderAssembly(ViewProviderAsmGroup):
         self._movingPart = None
         super(ViewProviderAssembly,self).__init__(vobj)
 
-    def canDragObject(self,_child):
-        return False
+    def _convertSubname(self,owner,subname):
+        sub = subname.split('.')
+        if not sub:
+            return
+        me = self.ViewObject.Object
+        partGroup = me.Proxy.getPartGroup().ViewObject
+        if sub == me.Name:
+            return partGroup,partGroup,subname[len[sub]+1:]
+        return partGroup,owner,subname
 
-    def canDragObjects(self):
-        return False
-
-    @property
-    def PartGroup(self):
-        return self.ViewObject.Object.Proxy.getPartGroup()
-
-    def canDropObject(self,obj):
-        self.PartGroup.ViewObject.canDropObject(obj)
-
-    def canDropObjects(self):
-        return True
+    def canDropObjectEx(self,obj,owner,subname):
+        info = self._convertSubname(owner,subname)
+        if not info:
+            return False
+        partGroup,owner,subname = info
+        return partGroup.canDropObject(obj,owner,subname)
 
     def dropObjectEx(self,_vobj,obj,owner,subname):
-        self.PartGroup.ViewObject.dropObject(obj,owner,subname)
+        info = self._convertSubname(owner,subname)
+        if not info:
+            return False
+        partGroup,owner,subname = info
+        partGroup.dropObject(obj,owner,subname)
 
     def getIcon(self):
         return System.getIcon(self.ViewObject.Object)
diff --git a/constraint.py b/constraint.py
index fd07119..8188836 100644
--- a/constraint.py
+++ b/constraint.py
@@ -218,8 +218,8 @@ class Constraint(ProxyType):
         return getattr(obj,mcs._disabled,False)
 
     @classmethod
-    def check(mcs,tp,group):
-        mcs.getType(tp).check(group)
+    def check(mcs,tp,group,checkCount=False):
+        mcs.getType(tp).check(group,checkCount)
 
     @classmethod
     def prepare(mcs,obj,solver):
@@ -260,9 +260,10 @@ class Constraint(ProxyType):
         ret = {}
         for obj in cstrs:
             cstr = mcs.getProxy(obj)
-            for info in cstr.getFixedTransform(obj):
-                found = True
-                ret[info.Part] = info
+            if cstr.hasFixedPart(obj):
+                for info in cstr.getFixedTransform(obj):
+                    found = True
+                    ret[info.Part] = info
 
             if not found and not firstPart:
                 elements = obj.Proxy.getElements()
@@ -331,32 +332,33 @@ class Base(with_metaclass(Constraint,object)):
     @classmethod
     def getEntityDef(cls,group,checkCount,obj=None):
         entities = cls._entityDef
-        if len(group) != len(entities):
-            if not checkCount and len(group)<len(entities):
-                return entities[:len(group)]
-            if cls._workplane and len(group)==len(entities)+1:
-                entities = list(entities)
-                entities.append(_w)
-            else:
-                if not obj:
-                    name = cls.getName()
-                else:
-                    name += cstrName(obj)
-                raise RuntimeError('Constraint {} has wrong number of '
-                    'elements {}, expecting {}'.format(
-                        name,len(group),len(entities)))
-        return entities
+        if len(group) == len(entities):
+            return entities
+        if cls._workplane and len(group)==len(entities)+1:
+            return list(entities) + [_w]
+        if not checkCount and len(group)<len(entities):
+            return entities[:len(group)]
+        if not obj:
+            name = cls.getName()
+        else:
+            name += cstrName(obj)
+        if len(group)<len(entities):
+            msg = entities[len(group)](None,None,None,None)
+            raise RuntimeError('Constraint {} expects a {} element of '
+                '{}'.format(name,_ordinal[len(group)],msg))
+        raise RuntimeError('Constraint {} has too many elements, expecting '
+            'only {}'.format(name,len(entities)))
 
     @classmethod
-    def check(cls,group):
-        entities = cls.getEntityDef(group,False)
+    def check(cls,group,checkCount=False):
+        entities = cls.getEntityDef(group,checkCount)
         for i,e in enumerate(entities):
             o = group[i]
             msg = e(None,None,None,o)
             if not msg:
                 continue
             if i == len(cls._entityDef):
-                raise RuntimeError('Constraint "{}" requires the optional {} '
+                raise RuntimeError('Constraint "{}" requires an optional {} '
                     'element to be a planar face for defining a '
                     'workplane'.format(cls.getName(), _ordinal[i], msg))
             raise RuntimeError('Constraint "{}" requires the {} element to be'
@@ -475,7 +477,7 @@ class Locked(Base):
         return ret
 
     @classmethod
-    def check(cls,group):
+    def check(cls,group,_checkCount=False):
         if not all([utils.isElement(o) for o in group]):
             raise RuntimeError('Constraint "{}" requires all children to be '
                     'of element (Vertex, Edge or Face)'.format(cls.getName()))
@@ -486,7 +488,7 @@ class BaseMulti(Base):
     _entityDef = (_wa,)
 
     @classmethod
-    def check(cls,group):
+    def check(cls,group,_checkCount=False):
         if len(group)<2:
             raise RuntimeError('Constraint "{}" requires at least two '
                 'elements'.format(cls.getName()))
diff --git a/utils.py b/utils.py
index eb1c2b1..01d226c 100644
--- a/utils.py
+++ b/utils.py
@@ -61,6 +61,22 @@ def isLine(param):
     else:
         return isinstance(param,Part.Line)
 
+def deduceSelectedElement(obj,subname):
+    shape = obj.getSubObject(subname)
+    if not shape:
+        return
+    count = len(shape.Faces)
+    if count==1:
+        return 'Face1'
+    elif not count:
+        count = len(shape.Edges)
+        if count==1:
+            return 'Edge1'
+        elif not count:
+            count = len(shape.Vertexes)
+            if count==1:
+                return 'Vertex1'
+
 def getElement(obj,tp):
     if isinstance(obj,tuple):
        obj = obj[0].getSubObject(obj[1])