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
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.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