summaryrefslogtreecommitdiff
path: root/cad/src/commands/PartLibrary/PartLibrary_Command.py
blob: b894c906f947465fd97e10b453e879eaa29a94b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
#Copyright 2004-2008 Nanorex, Inc.  See LICENSE file for details.
"""
PartLibrary_Command.py

Class PartLibrary_Command allows depositing parts from the partlib into the 3D
workspace.Its property manager shows the current selected part in its 'Preview'
box. The part can be deposited by doubleclicking on empty space in 3D workspace
or if it has a hotspot, it can be deposited on a bondpoint of an existing model.
User can return to previous mode by hitting  'Escape' key or pressing 'Done'
button in the Part Library mode.

@author: Bruce, Huaicai, Mark, Ninad
@version: $Id$
@copyright: 2004-2008 Nanorex, Inc.  See LICENSE file for details.

History:
The Partlib existed as a tab in the MMKit of Build Atoms Command. (MMKit has been
deprecated since 2007-08-29.) Now it has its own temporary mode.

ninad 2007-09-06: Created. Split out some methods originally in depositMode.py
                  to this file.

"""

import os
from utilities.Log import  greenmsg, quote_html, redmsg
from model.chem import Atom
from model.elements import Singlet
from geometry.VQT import Q
from operations.ops_copy import copied_nodes_for_DND

from commands.Paste.PasteFromClipboard_Command import PasteFromClipboard_Command
from commands.PartLibrary.PartLibPropertyManager import PartLibPropertyManager
from ne1_ui.toolbars.Ui_PartLibraryFlyout import PartLibraryFlyout
from commands.PartLibrary.PartLibrary_GraphicsMode import PartLibrary_GraphicsMode

class PartLibrary_Command(PasteFromClipboard_Command):
    """
    The PartLibrary_Command allows depositing parts from the partlib into the 3D
    workspace. Its property manager shows the current selected part in its
    'Preview' box. The part can be deposited by doubleclicking on empty space
    in 3D workspace or if it has a hotspot, it can be deposited on a bondpoint
    of an existing model.  User can return to previous mode by hitting  'Escape'
    key or pressing 'Done' button in this mode.
    """
    commandName = 'PARTLIB'
    featurename = "Part Library"
    from utilities.constants import CL_EDIT_GENERIC
    command_level = CL_EDIT_GENERIC

    GraphicsMode_class = PartLibrary_GraphicsMode

    #Property Manager
    PM_class = PartLibPropertyManager

    #Flyout Toolbar
    FlyoutToolbar_class = PartLibraryFlyout

    def deposit_from_Library_page(self, atom_or_pos):
        """
        Deposit a copy of the selected part from the Property Manager.

        @param atom_or_pos: If user clicks on a bondpoint in 3D workspace,
                            this is that bondpoint. NE1 will try to bond the
                            part to this bondpoint, by Part's hotspot(if exists)
                            If user double clicks on empty space, this gives
                            the coordinates at that point. This data is then
                            used to deposit the item.
        @type atom_or_pos: Array (vector) of coordinates or L{Atom}

        @return: (deposited_stuff, status_msg_text) Object deposited in the 3 D
                workspace. (Deposits the selected  part as a 'Group'. The status
                message text tells whether the Part got deposited.
        @rtype: (L{Group} , str)
        """
        #Needs cleanup. Copying old code from deprecated 'depositMode.py'
        #-- ninad 2007-09-06

        newPart, hotSpot = self.propMgr.getPastablePart()

        if not newPart: # Make sure a part is selected in the MMKit Library.
            # Whenever the MMKit is closed with the 'Library' page open,
            # MMKit.closeEvent() will change the current page to 'Atoms'.
            # This ensures that this condition never happens if the MMKit is
            # closed.
            # Mark 051213.

            return False, "No library part has been selected to paste."

        if isinstance(atom_or_pos, Atom):
            a = atom_or_pos
            if a.element is Singlet:
                if hotSpot : # bond the part to the singlet.
                    return self._depositLibraryPart(newPart, hotSpot, a)
                else: # part doesn't have hotspot.
                    #if newPart.has_singlets(): # need a method like this so we
                    # can provide more descriptive msgs.
                    #    msg = "To bond this part, you must pick a hotspot by \
                    #           left-clicking on a bondpoint  of the library \
                    #           part in the Modeling Kit's 3D thumbview."
                    #else:
                    #    msg = "The library part cannot be bonded because it \
                    #           has no bondpoints."

                    msg = "The library part cannot be bonded because either " \
                        "it has no bondpoints"\
                        " or its hotspot hasn't been specified"

                    return False, msg # nothing deposited
            else:
                # atom_or_pos was an atom, but wasn't a singlet.  Do nothing.
                msg = "internal error: can't deposit onto a real atom %r" %a
                return False, msg

        else:
            # deposit into empty space at the cursor position
            #bruce 051227 note: looks like subr repeats these conds;
            #are they needed here?
            return self._depositLibraryPart(newPart, hotSpot, atom_or_pos)

        assert 0, "notreached"

    #Method _depositLibraryPart moved from deprecated depositMode.py to here
    #-- Ninad 2008-01-02
    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
                            ##(stub is by bruce 051227)