Exemplo n.º 1
0
    def drop_on(self, drag_type, nodes):  ###@@@ needs a big cleanup
        # todo: also return description of what we did (for statusbar text)
        # [bruce 080303 comment]
        """
        After a "drag and drop" of type 'move' or 'copy' (according to
        drag_type), perform the drop of the given list of nodes
        onto self.node. The list always contains the original nodes --
        for drag_type == 'copy', this method should make the copies itself.

        Exactly how to do this depends on whether self.node is a leaf or group;
        subclasses of Node can override this to change the UI behavior.
        (As of 050307, only the Clipboard overrides this. Update 071025:
        that's now done by an 'if' statement in the following method,
        which calls self.node.drop_on_should_autogroup, which is what
        the Clipboard overrides.)

        @return: list of new nodes made by this operation, which is [] if it
                 didn't make any (making them is normal for copy, but can also
                 happen in some cases for move)
        @rtype: list of nodes
        """
        will_drop_inside = self.node.MT_DND_can_drop_inside()
        # if true, nodes or their copies will become new children of self.node;
        # if false, they will become new siblings. [revised, bruce 080317]
        autogroup_at_top = will_drop_inside and \
                           self.node.drop_on_should_autogroup(drag_type, nodes)
        drop_onto_Group_at_top = pref_drop_onto_Group_puts_nodes_at_top(
        )  #bruce 080414
        if autogroup_at_top:
            #bruce 050203/080303:
            # nodes dropped onto the clipboard come from one
            # "physical space" (Part) and ought to stay that way by default;
            # user can drag them one-at-a-time if desired. (In theory this
            #  autogrouping need only be done for the subsets of them which
            #  are bonded; for now that's too hard -- maybe not for long,
            #  similar to bug 371. But this simpler behavior might be better
            #  anyway.)
            if drag_type == 'move':
                name = self.node.assy.name_autogrouped_nodes_for_clipboard(
                    nodes, howmade=drag_type)
                new = Group(name, self.node.assy, None)
                for node in nodes[:]:  #bruce 050216 don't reverse the order, it's already correct
                    node.unpick(
                    )  #bruce 050216; don't know if needed or matters; 050307 moved from after to before moveto
                    node.moveto(
                        new
                    )  ####@@@@ guess, same as in super.drop_on (move here, regardless of drag_type? no, not correct!)
                nodes = [new]  # a new length-1 list of nodes
                env.history.message(
                    "(fyi: Grouped some nodes to keep them in one clipboard item)"
                )  ###e improve text
            else:
                # the above implem is wrong for copy, so we handle it differently below,
                # when autogroup_at_top and drag_type != 'move'
                pass
            pass
        #e rewrite to use copy_nodes (nim)? (also rewrite the assy methods? not for alpha)

        res = [
        ]  #bruce 050203: return any new nodes this creates (toplevel nodes only, for copied groups)

        #bruce 050216: order is correct if you're dropping on a group, but (for the ops used below)
        # wrong if you're dropping on a node. This needs a big cleanup, but for now, use this kluge
        # [revised to fix bug 2403 (most likely, this never worked as intended for copy until now), bruce 070525]:

        if not will_drop_inside:
            # drops on nodes which act like leaf nodes are placed after them,
            # when done by the methods named in the following flags,
            # so to drop several nodes in a row and preserve order,
            # drop them in reverse order -- but *when* we reverse them
            # (as well as which method we use) depends on whether we're
            # doing move or copy, so these flags are used in the right
            # place below.
            ##            reverse_moveto = True

            reverse_addmember = True  # for either addchild or addsibling
            pass
        else:
            # will_drop_inside is True
            #
            # drops on groups which accept drops inside themselves
            # go at the end of their members,
            # when done by those methods, so *don't* reverse node order --
            # UNLESS drop_onto_Group_at_top is set, which means, we put nodes dropped
            # on groups at the beginning of their members list.
            # REVIEW: are there any other things that need to be changed for that? ###

            ## assert not debug_pref_DND_drop_at_start_of_groups()
            ##            reverse_moveto = drop_onto_Group_at_top

            reverse_addmember = drop_onto_Group_at_top

            #bruce 060203 removing this, to implement one aspect of NFR 932:
            ## self.node.open = True # open groups which have nodes dropped on them [bruce 050528 new feature]
            pass

        if drag_type == 'move':
            # TODO: this code is now the same as the end of the copy case;
            # after the release, clean this up by moving the common code
            # outside of (after) this move/copy 'if' statement.
            # [bruce 080414 comment]

            if reverse_addmember:  # [bruce 080414 post-rc0 code cleanup: reverse_moveto -> reverse_addmember]
                nodes = nodes[::-1]
            for node in nodes[:]:
                ## node.moveto(self.node)
                if will_drop_inside:
                    self.node.addchild(node, top=drop_onto_Group_at_top)
                else:
                    self.node.addsibling(node, before=False)
                continue
            pass

        else:
            #bruce 050527 [revised 071025] this uses copied_nodes_for_DND,
            # new code to "copy anything". That code is preliminary
            # (probably not enough history messages, or maybe sometimes
            # too many). It would be better to create the Copier object
            # (done in that subr?) earlier, when the drag is started,
            # for various reasons mentioned elsewhere.
            # update 071025: autogroup_at_top is now set at start of this method
            ## autogroup_at_top = isinstance(self.node, ClipboardShelfGroup)
            #####@@@@@ kluge! replace with per-group variable or func.
            #e or perhaps better, a per-group method to process the nodes list, eg to do the grouping
            # as the comment in copied_nodes_for_DND or its subr suggests.

            nodes = copied_nodes_for_DND(nodes,
                                         autogroup_at_top=autogroup_at_top)
            # Note: this ignores order within input list of nodes, using only their MT order
            # to affect the order of copied nodes which it returns. [bruce 070525 comment]
            if not nodes:  # might be None
                return res  # return copied nodes [bruce 080414 post-rc0 code cleanup: [] -> res]

            res.extend(nodes)

            # note: the following code is the same in the move/copy cases;
            # see above for more about this.
            if reverse_addmember:
                nodes = nodes[::-1]
                # note: if autogroup_at_top makes len(nodes) == 1, this has no effect,
                # but it's harmless then, and logically best to do it whenever using
                # addmember on list elements.
            for node in nodes[:]:
                # node is a "copied node" [bruce 080414 post-rc0 code cleanup: renamed nc -> node]
                ## self.node.addmember(node) # self.node is sometimes a Group,
                ##     # so this does need to be addmember (not addchild or addsibling)
                if will_drop_inside:
                    self.node.addchild(node, top=drop_onto_Group_at_top)
                else:
                    self.node.addsibling(node, before=False)
                continue
            pass

        self.node.assy.update_parts(
        )  #e could be optimized to scan only what's needed (same for most other calls of update_parts)

        return res
    def _depositLibraryPart(self, newPart, hotspotAtom, atom_or_pos):
        # probably by Huaicai; revised by bruce 051227, 060627, 070501
        """
        This method serves as an overloaded method, <atom_or_pos> is
        the Singlet atom or the empty position that the new part <newPart>
        [which is an assy, at least sometimes] will be attached to or placed at.
        [If <atom_or_pos> is a singlet, <hotspotAtom> should be an atom in some
        chunk in <newPart>.]
        Currently, it doesn't consider group or jigs in the <newPart>.
        Not so sure if my attempt to copy a part into another assembly is all
        right. [It wasn't, so bruce 051227 revised it.]
        Copies all molecules in the <newPart>, change their assy attribute to
        current assembly, move them into <pos>.
        [bruce 051227 new feature:] return a list of new nodes created, and a
        message for history (currently almost a stub).
        [not sure if subrs ever print history messages...
        if they do we'd want to return those instead.]
        """

        attach2Bond = False
        stuff = []  # list of deposited nodes [bruce 051227 new feature]

        if isinstance(atom_or_pos, Atom):
            attch2Singlet = atom_or_pos
            if hotspotAtom and hotspotAtom.is_singlet() and attch2Singlet.is_singlet():

                newMol = hotspotAtom.molecule.copy_single_chunk(None)
                # [this can break interchunk bonds,
                #  thus it still has bug 2028]
                newMol.set_assy(self.o.assy)
                hs = newMol.hotspot
                ha = hs.singlet_neighbor()  # hotspot neighbor atom
                attch2Atom = attch2Singlet.singlet_neighbor()  # attach to atom

                rotCenter = newMol.center
                rotOffset = Q(ha.posn() - hs.posn(), attch2Singlet.posn() - attch2Atom.posn())
                newMol.rot(rotOffset)

                moveOffset = attch2Singlet.posn() - hs.posn()
                newMol.move(moveOffset)

                self.graphicsMode._createBond(hs, ha, attch2Singlet, attch2Atom)

                self.o.assy.addmol(newMol)
                stuff.append(newMol)

                # e if there are other chunks in <newPart>,
                # they are apparently copied below. [bruce 060627 comment]

            else:  ## something is wrong, do nothing
                return stuff, "internal error"
            attach2Bond = True
        else:
            placedPos = atom_or_pos
            if hotspotAtom:
                hotspotAtomPos = hotspotAtom.posn()
                moveOffset = placedPos - hotspotAtomPos
            else:
                if newPart.molecules:
                    moveOffset = placedPos - newPart.molecules[0].center  # e not
                    # the best choice of center [bruce 060627 comment]

        if attach2Bond:  # Connect part to a bondpoint of an existing chunk
            for m in newPart.molecules:
                if not m is hotspotAtom.molecule:
                    newMol = m.copy_single_chunk(None)
                    # [this can break interchunk bonds,
                    #  thus it still has bug 2028]
                    newMol.set_assy(self.o.assy)

                    ## Get each of all other chunks' center movement for the
                    ## rotation around 'rotCenter'
                    coff = rotOffset.rot(newMol.center - rotCenter)
                    coff = rotCenter - newMol.center + coff

                    # The order of the following 2 statements doesn't matter
                    newMol.rot(rotOffset)
                    newMol.move(moveOffset + coff)

                    self.o.assy.addmol(newMol)
                    stuff.append(newMol)
        else:  # Behaves like dropping a part anywhere you specify, independent
            # of existing chunks.
            # copy all nodes in newPart (except those in clipboard items),
            # regardless of node classes;
            # put it in a new Group if more than one thing [bruce 070501]
            # [TODO: this should be done in the cases above, too, but that's
            # not yet implemented,
            #  and requires adding rot or pivot to the Node API and revising
            #  the rot-calling code above,
            #  and also reviewing the definition of the "hotspot of a Part" and
            # maybe of a "depositable clipboard item".]
            assert newPart.tree.is_group()
            nodes = list(newPart.tree.members)  # might be []
            assy = self.o.assy
            newnodes = copied_nodes_for_DND(nodes, autogroup_at_top=True, assy=assy)
            # Note: that calls name_autogrouped_nodes_for_clipboard
            # internally, if it forms a Group,
            # but we ignore that and rename the new node differently below,
            # whether or not it was autogrouped. We could just as well do
            # the autogrouping ourselves...
            # Note [bruce 070525]: it's better to call copied_nodes_for_DND
            # here than copy_nodes_in_order, even if we didn't need to
            # autogroup. One reason is that if some node is not copied,
            # that's not necessarily an error, since we don't care about 1-1
            # orig-copy correspondence here.
            if not newnodes:
                if newnodes is None:
                    print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,)
                    # TODO: This might be possible, for arbitrary partlib
                    # contents, just not for legitimate ones...
                    # but partlib will probably be (or is) user-expandable,
                    # so we should turn this into history message,
                    # not a bug print. But I'm not positive it's possible
                    # w/o a bug, so review first. ###FIX [bruce 070501 comment]
                    import utilities.debug as debug

                    debug._bugnodes = nodes
                    newnodes = []
                msg = redmsg("error: nothing to deposit in [%s]" % quote_html(str(newPart.name)))
                return [], msg
            assert len(newnodes) == 1  # due to autogroup_at_top = True
            # but the remaining code works fine regardless of len(newnodes),
            # in case we make autogroup a preference
            for newnode in newnodes:
                # Rename newnode based on the partlib name and a unique number.
                # It seems best to let the partlib mmp file contents (not just
                # filename)
                # control the name used here, so use newPart.tree.name rather
                # than just newPart.name.
                # (newPart.name is a complete file pathname; newPart.tree.name
                # is usually its basename w/o extension.)
                basename = str(newPart.tree.name)
                if basename == "Untitled":
                    # kluge, for the sake of 3 current partlib files, and files
                    # saved only once by users (due to NE1 bug in save)
                    dirjunk, base = os.path.split(newPart.name)
                    basename, extjunk = os.path.splitext(base)
                from utilities.constants import gensym

                newnode.name = gensym(basename, assy)  # name library part
                # bruce 080407 basename + " " --> basename, and pass assy
                # (per Mark NFR desire)
                # based on basename recorded in its mmp file's top node
                newnode.move(moveOffset)  # k not sure this method is correctly
                # implemented for measurement jigs, named views
                assy.addnode(newnode)
                stuff.append(newnode)

        ##            #bruce 060627 new code: fix bug 2028 (non-hotspot case only)
        ##            # about interchunk bonds being broken
        ##            nodes = newPart.molecules
        ##            newnodes = copied_nodes_for_DND(nodes)
        ##            if newnodes is None:
        ##                print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,)
        ##                debug._bugnodes = nodes
        ##                newnodes = [] # kluge
        ##            for newMol in newnodes:
        ##                # some of the following probably only work for Chunks,
        ##                # though coding them for other nodes would not be hard
        ##                newMol.set_assy(self.o.assy)
        ##                newMol.move(moveOffset)
        ##                self.o.assy.addmol(newMol)
        ##                stuff.append(newMol)

        self.o.assy.update_parts()  # bruce 051227 see if this fixes the
        # atom_debug exception in checkparts

        msg = (
            greenmsg("Deposited library part: ") + " [" + quote_html(str(newPart.name)) + "]"
        )  # ninad060924 fix bug 1164

        return stuff, msg  ####@@@@ should revise this message
Exemplo n.º 3
0
    def drop_on(self, drag_type, nodes): ###@@@ needs a big cleanup
        # todo: also return description of what we did (for statusbar text)
        # [bruce 080303 comment]
        """
        After a "drag and drop" of type 'move' or 'copy' (according to
        drag_type), perform the drop of the given list of nodes
        onto self.node. The list always contains the original nodes --
        for drag_type == 'copy', this method should make the copies itself.

        Exactly how to do this depends on whether self.node is a leaf or group;
        subclasses of Node can override this to change the UI behavior.
        (As of 050307, only the Clipboard overrides this. Update 071025:
        that's now done by an 'if' statement in the following method,
        which calls self.node.drop_on_should_autogroup, which is what
        the Clipboard overrides.)

        @return: list of new nodes made by this operation, which is [] if it
                 didn't make any (making them is normal for copy, but can also
                 happen in some cases for move)
        @rtype: list of nodes
        """
        will_drop_inside = self.node.MT_DND_can_drop_inside()
            # if true, nodes or their copies will become new children of self.node;
            # if false, they will become new siblings. [revised, bruce 080317]
        autogroup_at_top = will_drop_inside and \
                           self.node.drop_on_should_autogroup(drag_type, nodes)
        drop_onto_Group_at_top = pref_drop_onto_Group_puts_nodes_at_top() #bruce 080414
        if autogroup_at_top:
            #bruce 050203/080303:
            # nodes dropped onto the clipboard come from one
            # "physical space" (Part) and ought to stay that way by default;
            # user can drag them one-at-a-time if desired. (In theory this
            #  autogrouping need only be done for the subsets of them which
            #  are bonded; for now that's too hard -- maybe not for long,
            #  similar to bug 371. But this simpler behavior might be better
            #  anyway.)
            if drag_type == 'move':
                name = self.node.assy.name_autogrouped_nodes_for_clipboard( nodes, howmade = drag_type )
                new = Group(name, self.node.assy, None)
                for node in nodes[:]: #bruce 050216 don't reverse the order, it's already correct
                    node.unpick() #bruce 050216; don't know if needed or matters; 050307 moved from after to before moveto
                    node.moveto(new) ####@@@@ guess, same as in super.drop_on (move here, regardless of drag_type? no, not correct!)
                nodes = [new] # a new length-1 list of nodes
                env.history.message( "(fyi: Grouped some nodes to keep them in one clipboard item)" ) ###e improve text
            else:
                # the above implem is wrong for copy, so we handle it differently below,
                # when autogroup_at_top and drag_type != 'move'
                pass
            pass
        #e rewrite to use copy_nodes (nim)? (also rewrite the assy methods? not for alpha)

        res = [] #bruce 050203: return any new nodes this creates (toplevel nodes only, for copied groups)

        #bruce 050216: order is correct if you're dropping on a group, but (for the ops used below)
        # wrong if you're dropping on a node. This needs a big cleanup, but for now, use this kluge
        # [revised to fix bug 2403 (most likely, this never worked as intended for copy until now), bruce 070525]:

        if not will_drop_inside:
            # drops on nodes which act like leaf nodes are placed after them,
            # when done by the methods named in the following flags,
            # so to drop several nodes in a row and preserve order,
            # drop them in reverse order -- but *when* we reverse them
            # (as well as which method we use) depends on whether we're
            # doing move or copy, so these flags are used in the right
            # place below.
##            reverse_moveto = True

            reverse_addmember = True # for either addchild or addsibling
            pass
        else:
            # will_drop_inside is True
            #
            # drops on groups which accept drops inside themselves
            # go at the end of their members,
            # when done by those methods, so *don't* reverse node order --
            # UNLESS drop_onto_Group_at_top is set, which means, we put nodes dropped
            # on groups at the beginning of their members list.
            # REVIEW: are there any other things that need to be changed for that? ###

            ## assert not debug_pref_DND_drop_at_start_of_groups()
##            reverse_moveto = drop_onto_Group_at_top

            reverse_addmember = drop_onto_Group_at_top

            #bruce 060203 removing this, to implement one aspect of NFR 932:
            ## self.node.open = True # open groups which have nodes dropped on them [bruce 050528 new feature]
            pass

        if drag_type == 'move':
            # TODO: this code is now the same as the end of the copy case;
            # after the release, clean this up by moving the common code
            # outside of (after) this move/copy 'if' statement.
            # [bruce 080414 comment]

            if reverse_addmember: # [bruce 080414 post-rc0 code cleanup: reverse_moveto -> reverse_addmember]
                nodes = nodes[::-1]
            for node in nodes[:]:
                ## node.moveto(self.node)
                if will_drop_inside:
                    self.node.addchild(node, top = drop_onto_Group_at_top)
                else:
                    self.node.addsibling(node, before = False)
                continue
            pass

        else:
            #bruce 050527 [revised 071025] this uses copied_nodes_for_DND,
            # new code to "copy anything". That code is preliminary
            # (probably not enough history messages, or maybe sometimes
            # too many). It would be better to create the Copier object
            # (done in that subr?) earlier, when the drag is started,
            # for various reasons mentioned elsewhere.
            # update 071025: autogroup_at_top is now set at start of this method
            ## autogroup_at_top = isinstance(self.node, ClipboardShelfGroup)
                #####@@@@@ kluge! replace with per-group variable or func.
                #e or perhaps better, a per-group method to process the nodes list, eg to do the grouping
                # as the comment in copied_nodes_for_DND or its subr suggests.

            nodes = copied_nodes_for_DND(nodes, autogroup_at_top = autogroup_at_top)
                # Note: this ignores order within input list of nodes, using only their MT order
                # to affect the order of copied nodes which it returns. [bruce 070525 comment]
            if not nodes: # might be None
                return res # return copied nodes [bruce 080414 post-rc0 code cleanup: [] -> res]

            res.extend(nodes)

            # note: the following code is the same in the move/copy cases;
            # see above for more about this.
            if reverse_addmember:
                nodes = nodes[::-1]
                    # note: if autogroup_at_top makes len(nodes) == 1, this has no effect,
                    # but it's harmless then, and logically best to do it whenever using
                    # addmember on list elements.
            for node in nodes[:]:
                # node is a "copied node" [bruce 080414 post-rc0 code cleanup: renamed nc -> node]
                ## self.node.addmember(node) # self.node is sometimes a Group,
                ##     # so this does need to be addmember (not addchild or addsibling)
                if will_drop_inside:
                    self.node.addchild(node, top = drop_onto_Group_at_top)
                else:
                    self.node.addsibling(node, before = False)
                continue
            pass

        self.node.assy.update_parts() #e could be optimized to scan only what's needed (same for most other calls of update_parts)

        return res
Exemplo n.º 4
0
    def _depositLibraryPart(self, newPart, hotspotAtom, atom_or_pos): 
        # probably by Huaicai; revised by bruce 051227, 060627, 070501
        """
        This method serves as an overloaded method, <atom_or_pos> is 
        the Singlet atom or the empty position that the new part <newPart>
        [which is an assy, at least sometimes] will be attached to or placed at.
        [If <atom_or_pos> is a singlet, <hotspotAtom> should be an atom in some 
        chunk in <newPart>.]
        Currently, it doesn't consider group or jigs in the <newPart>. 
        Not so sure if my attempt to copy a part into another assembly is all
        right. [It wasn't, so bruce 051227 revised it.]
        Copies all molecules in the <newPart>, change their assy attribute to 
        current assembly, move them into <pos>.
        [bruce 051227 new feature:] return a list of new nodes created, and a 
        message for history (currently almost a stub).
        [not sure if subrs ever print history messages...
        if they do we'd want to return those instead.]
        """

        attach2Bond = False
        stuff = [] # list of deposited nodes [bruce 051227 new feature]

        if isinstance(atom_or_pos, Atom):
            attch2Singlet = atom_or_pos
            if hotspotAtom and hotspotAtom.is_singlet() and \
               attch2Singlet .is_singlet():

                newMol = hotspotAtom.molecule.copy_single_chunk(None)
                    # [this can break interchunk bonds,
                    #  thus it still has bug 2028]
                newMol.setAssy(self.o.assy)
                hs = newMol.hotspot
                ha = hs.singlet_neighbor() # hotspot neighbor atom
                attch2Atom = attch2Singlet.singlet_neighbor() # attach to atom

                rotCenter = newMol.center
                rotOffset = Q(ha.posn()-hs.posn(), 
                              attch2Singlet.posn()-attch2Atom.posn())
                newMol.rot(rotOffset)

                moveOffset = attch2Singlet.posn() - hs.posn()
                newMol.move(moveOffset)
    
                self.graphicsMode._createBond(hs, ha, attch2Singlet, attch2Atom)

                self.o.assy.addmol(newMol)
                stuff.append(newMol)

                #e if there are other chunks in <newPart>, 
                #they are apparently copied below. [bruce 060627 comment]

            else: ## something is wrong, do nothing
                return stuff, "internal error"
            attach2Bond = True
        else:
            placedPos = atom_or_pos
            if hotspotAtom:
                hotspotAtomPos = hotspotAtom.posn()
                moveOffset = placedPos - hotspotAtomPos
            else:
                if newPart.molecules:
                    moveOffset = placedPos - newPart.molecules[0].center #e not 
                    #the best choice of center [bruce 060627 comment]

        if attach2Bond: # Connect part to a bondpoint of an existing chunk
            for m in newPart.molecules:
                if not m is hotspotAtom.molecule: 
                    newMol = m.copy_single_chunk(None)
                        # [this can break interchunk bonds,
                        #  thus it still has bug 2028]
                    newMol.setAssy(self.o.assy)

                    ## Get each of all other chunks' center movement for the 
                    ## rotation around 'rotCenter'
                    coff = rotOffset.rot(newMol.center - rotCenter)
                    coff = rotCenter - newMol.center + coff 

                    # The order of the following 2 statements doesn't matter
                    newMol.rot(rotOffset)
                    newMol.move(moveOffset + coff)

                    self.o.assy.addmol(newMol)
                    stuff.append(newMol)
        else: # Behaves like dropping a part anywhere you specify, independent 
            #of existing chunks.
            # copy all nodes in newPart (except those in clipboard items), 
            # regardless of node classes;
            # put it in a new Group if more than one thing [bruce 070501]
            # [TODO: this should be done in the cases above, too, but that's 
            # not yet implemented,
            #  and requires adding rot or pivot to the Node API and revising
            #  the rot-calling code above,
            #  and also reviewing the definition of the "hotspot of a Part" and 
            # maybe of a "depositable clipboard item".]
            assert newPart.tree.is_group()
            nodes = list(newPart.tree.members) # might be []
            assy = self.o.assy
            newnodes = copied_nodes_for_DND(nodes, 
                                            autogroup_at_top = True, 
                                            assy = assy)
                # Note: that calls name_autogrouped_nodes_for_clipboard 
                # internally, if it forms a Group,
                # but we ignore that and rename the new node differently below,
                # whether or not it was autogrouped. We could just as well do 
                # the autogrouping ourselves...
                # Note [bruce 070525]: it's better to call copied_nodes_for_DND 
                # here than copy_nodes_in_order, even if we didn't need to 
                # autogroup. One reason is that if some node is not copied,
                # that's not necessarily an error, since we don't care about 1-1
                # orig-copy correspondence here.
            if not newnodes:
                if newnodes is None:
                    print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,)
                        # TODO: This might be possible, for arbitrary partlib 
                        # contents, just not for legitimate ones...
                        # but partlib will probably be (or is) user-expandable, 
                        #so we should turn this into history message,
                        # not a bug print. But I'm not positive it's possible
                        #w/o a bug, so review first. ###FIX [bruce 070501 comment]
                    import utilities.debug as debug
                    debug._bugnodes = nodes
                    newnodes = []
                msg = redmsg( "error: nothing to deposit in [%s]" % quote_html(str(newPart.name)) )
                return [], msg
            assert len(newnodes) == 1 # due to autogroup_at_top = True
            # but the remaining code works fine regardless of len(newnodes), 
            #in case we make autogroup a preference
            for newnode in newnodes:
                # Rename newnode based on the partlib name and a unique number.
                # It seems best to let the partlib mmp file contents (not just 
                # filename)
                # control the name used here, so use newPart.tree.name rather 
                # than just newPart.name.
                # (newPart.name is a complete file pathname; newPart.tree.name 
                #is usually its basename w/o extension.)
                basename = str(newPart.tree.name)
                if basename == 'Untitled':
                    # kluge, for the sake of 3 current partlib files, and files 
                    #saved only once by users (due to NE1 bug in save)
                    dirjunk, base = os.path.split(newPart.name)
                    basename, extjunk = os.path.splitext(base)
                from utilities.constants import gensym
                newnode.name = gensym( basename, assy) # name library part
                    #bruce 080407 basename + " " --> basename, and pass assy
                    # (per Mark NFR desire)
                #based on basename recorded in its mmp file's top node
                newnode.move(moveOffset) #k not sure this method is correctly 
                #implemented for measurement jigs, named views
                assy.addnode(newnode)
                stuff.append(newnode)

##            #bruce 060627 new code: fix bug 2028 (non-hotspot case only) 
##            about interchunk bonds being broken
##            nodes = newPart.molecules
##            newnodes = copied_nodes_for_DND(nodes)
##            if newnodes is None:
##                print "bug: newnodes should not be None; nodes was %r (saved in debug._bugnodes)" % (nodes,)
##                debug._bugnodes = nodes
##                newnodes = [] # kluge
##            for newMol in newnodes:
##                # some of the following probably only work for Chunks,
##                # though coding them for other nodes would not be hard
##                newMol.setAssy(self.o.assy)
##                newMol.move(moveOffset)
##                self.o.assy.addmol(newMol)
##                stuff.append(newMol)

##            # pre-060627 old code, breaks interchunk bonds since it copies 
##            #chunks one at a time (bug 2028)
##            for m in nodes:
##                newMol = m.copy(None) # later: renamed Chunk method to copy_single_chunk -- not sure if this was only called on chunks
##                newMol.setAssy(self.o.assy) #bruce 051227 revised this
##                
##                newMol.move(moveOffset)
##                
##                self.o.assy.addmol(newMol)
##                stuff.append(newMol)
##            pass
        self.o.assy.update_parts() #bruce 051227 see if this fixes the 
                                    #atom_debug exception in checkparts

        msg = greenmsg("Deposited library part: ") + " [" + \
            quote_html(str(newPart.name)) + "]"  #ninad060924 fix bug 1164 

        return stuff, msg  ####@@@@ should revise this message