summaryrefslogtreecommitdiff
path: root/cad/src/operations/ops_motion.py
blob: 97945f6ecc744d0cc0077dafc115f0d5066cf092 (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
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# Copyright 2004-2007 Nanorex, Inc.  See LICENSE file for details.
"""
ops_motion.py -- various ways of moving or spatially distorting
selected atoms or chunks (and someday, attached jigs).
These operations don't create or destroy atoms or bonds.

@version: $Id$
@copyright: 2004-2007 Nanorex, Inc.  See LICENSE file for details.

History:

bruce 050507 made this by collecting appropriate methods from class Part.
"""

from utilities.Log import greenmsg, redmsg
from platform_dependent.PlatformDependent import fix_plurals
from geometry.VQT import V, norm, Q, vlen, orthodist
import foundation.env as env
from math import pi
from utilities.debug import print_compact_traceback

from model.chunk import Chunk
from model.jigs import Jig
from model.jigs_motors import Motor
from analysis.ESP.ESPImage import ESPImage
from foundation.Group import Group


class ops_motion_Mixin:
    """
    Mixin class for providing these methods to class Part
    """
    ###@@@ move/rot should be extended to apply to jigs too (and fit into some naming convention)

    def movesel(self, offset):
        """
        move selected chunks and jigs in space
        """
        movables = self.getSelectedMovables()

        self.translateSpecifiedMovables(offset, movables = movables)


    def translateSpecifiedMovables(self, offset, movables =()):
        """
        Translate the specified movables.
        @param movables: a list of movables (default value is empty tuple)
        @type movables: list
        """
        for m in movables:
            self.changed()
            m.move(offset)

    def rotsel(self, quat):
        """
        Rotate selected chunks/jigs in space. [Huaicai 8/30/05: Fixed the problem of each rotating
        around its own center, they will now rotate around their common center]
        """
        movables = self.getSelectedMovables()
        self.rotateSpecifiedMovables(quat, movables = movables)
        return

    def rotateSpecifiedMovables(self, quat, movables =(), commonCenter = None):
        """
        Rotate the movables specified in the 'movables' list.
        (Rotated as a unit)
        @param quat: Quaternion for the rotation.
        @param movables: A list of movables. These movables will be
                rotated around a common axis
        @type movables: list

        """
        numMovables = len(movables)

        if commonCenter is None:
            # Find the common center of all selected chunks to fix bug 594
            #--Huaicai 8/30/05
            comCenter = V(0.0, 0.0, 0.0)
            if numMovables:
                for m in movables:
                    comCenter += m.center
                comCenter /= numMovables

        else:
            comCenter = commonCenter

        # Move the selected chunks
        for m in movables:
            self.changed() #Not sure if this can be combined into one call
            # Get the moving offset because of the rotation around each
            # movable's own center
            rotOff = quat.rot(m.center - comCenter)
            rotOff = comCenter - m.center + rotOff
            m.move(rotOff)
            m.rot(quat)


    def Stretch(self):
        """
        stretch a Chunk
        """
        mc = env.begin_op("Stretch")
        try:
            cmd = greenmsg("Stretch: ")

            if not self.selmols:
                msg =  redmsg("No selected chunks to stretch")
                env.history.message(cmd + msg)
            else:
                self.changed()
                for m in self.selmols:
                    m.stretch(1.1)
                self.o.gl_update()

                # Added history message.  Mark 050413.
                info = fix_plurals( "Stretched %d chunk(s)" % len(self.selmols))
                env.history.message( cmd + info)
        finally:
            env.end_op(mc)
        return

    def Invert(self):
        """
        Invert the atoms of the selected chunk(s) around the chunk centers
        """
        mc = env.begin_op("Invert")
        cmd = greenmsg("Invert: ")

        if not self.selmols:
            msg = redmsg("No selected chunks to invert")
            env.history.message(cmd + msg)
            return
        self.changed()
        for m in self.selmols:
            m.stretch(-1.0)
        self.o.gl_update()

        info = fix_plurals( "Inverted %d chunk(s)" % len(self.selmols))
        env.history.message( cmd + info)
        env.end_op(mc) #e try/finally?

    def Mirror(self):
        """
        Mirror the selected chunk(s) about a selected grid plane.
        """
        cmd = greenmsg("Mirror: ")
        #ninad060814 this is necessary to fix a bug. Otherwise program will
        #crash if you try to mirror when the top node of the part
        #(main part of clipboard) is selected

        if self.topnode.picked:
            self.topnode.unpick_top()

        self.mirrorJigs = self.getQualifiedMirrorJigs()

        jigCounter = len(self.mirrorJigs)

        if jigCounter < 1:
            msg1 = "No mirror plane selected."
            msg2 = " Please select a Reference Plane or a Grid Plane first."
            msg = redmsg(msg1+msg2)
            instr1 = "(If it doesn't exist, create it using"
            instr2 = "<b>Insert > Reference Geometry menu </b> )"
            instruction = instr1 + instr2
            env.history.message(cmd + msg  + instruction)
            return
        elif jigCounter >1:
            msg = redmsg("More than one plane selected. Please select only one plane and try again")
            env.history.message(cmd + msg )
            return

        for j in self.mirrorJigs:
            j.unpick()

        copiedObject = self.o.assy.part.copy_sel_in_same_part()

        # ninad060812 Get the axis vector of the Grid Plane. Then you need to
        #rotate the inverted chunk by pi around this axis vector
        self.mirrorAxis = self.mirrorJigs[0].getaxis()

        if isinstance(copiedObject, Chunk):
            copiedObject.name = copiedObject.name + "-Mirror"
            self._mirrorChunk(copiedObject)
            return
        elif isinstance(copiedObject, Group):
            copiedObject.name = "Mirrored Items"
            def mirrorChild(obj):
                if isinstance(obj, Chunk):
                    self._mirrorChunk(obj)
                elif isinstance(obj, Jig):
                    self._mirrorJig(obj)

            copiedObject.apply2all(mirrorChild)
        return

    def _mirrorChunk(self, chunkToMirror):
        """
        Converts the given chunk into its own mirror.

        @param chunkToMirror: The chunk that needs to be converted into its own
               mirror chunk.
        @type  chunkToMirror: instance of class Chunk
        @see:  self.Mirror
        """
        m = chunkToMirror
        # ninad060813 Following gives an orthogonal distance between the
        #chunk center and mirror plane.
        self.mirrorDistance, self.wid = orthodist(m.center,
                                                  self.mirrorAxis,
                                                  self.mirrorJigs[0].center)
        # @@@@ ninad060813 This moves the mirror chunk on the other side of
        # the mirror plane. It surely moves the chunk along the axis of the
        # mirror plane but I am still unsure if this *always* moves the
        # chunk on the other side of the mirror.
        #Probably the 'orthodist' function has helped me here??
        m.move(2*(self.mirrorDistance)*self.mirrorAxis)

        m.stretch(-1.0)
        m.rot(Q(self.mirrorAxis, pi))
        return

    def _mirrorJig(self, jigToMirror):
        """
        Converts the given jig into its own mirror. If the jig is a motor,
        it also reverses its direction.
        @param jigToMirror: The jig that needs to be converted into its own
               mirror jig.
        @type  jigToMirror: instance of class Jig
        @see:  self.Mirror
        """
        j = jigToMirror
        # ninad060813 This gives an orthogonal distance between the chunk
        # center and mirror plane.

        #Fixed bug 2503.
        if not (isinstance(j, Motor) or isinstance(j, ESPImage)):
            return

        self.mirrorDistance, self.wid = orthodist(j.center, self.mirrorAxis,
                                                  self.mirrorJigs[0].center)
        j.move(2*(self.mirrorDistance)*self.mirrorAxis)

        j.rot(Q(self.mirrorAxis, pi))

        #Reverse the direction of Linear and Rotary motor for correct
        #mirror operation
        if isinstance(j, Motor):
            j.reverse_direction()
        return

    def getQualifiedMirrorJigs(self):
        """
        Returns a list of objects that can be used as a
        reference in Mirror Feature. (referece plane and grid planes are valid
        objects). Only the first object in this list is used for mirror.
        See Mirror method for details
        """

        jigs = self.assy.getSelectedJigs()
        mirrorJigs = []

        for j in jigs:
            if j.mmp_record_name is "gridplane" or j.mmp_record_name is "plane":
                mirrorJigs.append(j)

        return mirrorJigs

    def align_NEW(self):
        """
        Align the axes of the selected movables to the axis of the movable
        that is placed at the highest order in the Model Tree
        """
        #@@This is not called yet.
        #method *always* uses the MT order to align chunks or jigs
        #This supports jigs (including reference planes) but it has following
        #bug -- It always uses the selected movable that is placed highest
        #in the Model Tree, as the reference axis for alignment. (Instead
        #it should align to the 'first selected movable'
        #(this doesn't happen (or very rarely happens) in old align method where
        #'selmols' is used.)

        cmd = greenmsg("Align to Common Axis: ")

        movables = self.assy.getSelectedMovables()
        for m in movables:
            print "movable =", m.name
        numMovables = len(movables)
        if len(movables) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed()
        try:
            firstAxis = movables[0].getaxis()
            for m in movables[1:]:
                m.rot(Q(m.getaxis(),firstAxis))
            self.o.gl_update()
        except:
            print_compact_traceback ("bug: selected movable object doesn't have an \
            axis")
            msg = redmsg("bug: selected movable object doesn't have an axis")
            env.history.message(cmd + msg)
            return
        self.o.gl_update()

        info = fix_plurals( "Aligned %d item(s)" % (len(movables) - 1) ) \
            + " to axis of %s" % movables[0].name
        env.history.message( cmd + info)
        return

    def align(self):
        """

        """
        cmd = greenmsg("Align to Common Axis: ")

        if len(self.selmols) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed() #bruce 050131 bugfix or precaution
        #ax = V(0,0,0)
        #for m in self.selmols:
        #    ax += m.getaxis()
        #ax = norm(ax)
        ax = self.selmols[0].getaxis() # Axis of first selected chunk
        for m in self.selmols[1:]:
            m.rot(Q(m.getaxis(),ax))
        self.o.gl_update()

        info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
            + " to chunk %s" % self.selmols[0].name
        env.history.message( cmd + info)

    #Ninad 060904 The following is not called from UI. Need to see if this is useful to the user.
    def alignPerpendicularToPlane(self):
        """
        Aligns the axes of selected jigs or chunks perpendicular to a reference plane
        """
        cmd = greenmsg("Align to Plane:")

        referencePlaneList = self.getQualifiedReferencePlanes()
        jigCounter = len(referencePlaneList)

        self.changed()

        if jigCounter:
            referencePlane = referencePlaneList[0] #ninad060904 If more than 1 ref planes are selected, it selectes the first in the order in the mmp file


        if jigCounter < 1:
            msg = redmsg("Please select a plane first.")
            instruction = " Planes can also be created using the <b>Insert > Plane</b> command."
            env.history.message(cmd + msg  + instruction)
            return

        movables = self.assy.getSelectedMovables()
        numMovables = len(movables)
        #print len(movables)

        if numMovables >1:
            for obj in movables:
                if obj is referencePlane:
                    pass
                refAxis = referencePlane.getaxis()
                obj.rot(Q(obj.getaxis(),refAxis))
                self.o.gl_update()
        else:
            msg = redmsg("No chunks or movable jigs selected to align perpendicular to the reference plane.")
            env.history.message(cmd + msg  + instruction)
            return


    def getQualifiedReferencePlanes(self, jigs): #Ninad 060904
        """
        Returns a list of jigs that can be used a  reference plane in align to plane feature.
        """
        referencePlaneList = []
        for j in jigs:
            if j.mmp_record_name is "gridplane":
                referencePlaneList += [j]
        return referencePlaneList

    def alignmove(self):

        cmd = greenmsg("Move to Axis: ")

        if len(self.selmols) < 2:
            msg = redmsg("Need two or more selected chunks to align")
            env.history.message(cmd + msg)
            return
        self.changed()
        #ax = V(0,0,0)
        #for m in self.selmols:
        #    ax += m.getaxis()
        #ax = norm(ax)
        ax = self.selmols[0].getaxis() # Axis of first selected chunk
        ctr = self.selmols[0].center # Center of first selected chunk
        for m in self.selmols[1:]:
            m.rot(Q(m.getaxis(),ax))
            m.move(ctr-m.center) # offset

        self.o.gl_update()

        info = fix_plurals( "Aligned %d chunk(s)" % (len(self.selmols) - 1) ) \
            + " to chunk %s" % self.selmols[0].name
        env.history.message( cmd + info)

    pass # end of class ops_motion_Mixin

# end