Python API 2.0 Reference
scripted/squareScaleManipContext.py
1 ########################################################################
2 #
3 # DESCRIPTION:
4 #
5 # This example is based on the squareScaleManip example but uses
6 # a context and context command. If the plug-in context is active,
7 # selecting geometry will show the manipulator. Only the right and
8 # left sides of the square currently modify the geometry if moved.
9 #
10 ########################################################################
11 #
12 # First make sure that squareScaleManipContext.py is in your
13 # MAYA_PLUG_IN_PATH. Then, to use the tool, execute the following in the
14 # script editor:
15 #
16 # from maya import cmds
17 # cmds.loadPlugin("squareScaleManipContext.py")
18 # ctx = cmds.squareScaleManipContext()
19 # cmds.setToolTo(ctx)
20 #
21 # Once the tool is active, click on an object. Move the right and left
22 # edges of the square to modify the selected object's scale.
23 #
24 ########################################################################
25 
26 import logging
27 import math
28 import sys
29 from maya.api import OpenMaya, OpenMayaUI, OpenMayaRender
30 from maya import OpenMayaRender as OpenMayaRenderV1
31 
32 logger = logging.getLogger('SquareScaleManipContext')
33 
34 # tell Maya that we want to use Python API 2.0
35 maya_useNewAPI = True
36 
37 #
38 # Utility classes
39 #
40 class PlaneMath:
41  """
42  This utility class represents a mathematical plane and performs intersection
43  tests with a line.
44  """
45  def __init__(self):
46  """
47  Initialze the member variables of the class.
48  """
49  self.a = 0.0
50  self.b = 0.0
51  self.c = 0.0
52  self.d = 0.0
53 
54  def set_plane( self, point_on_plane, normal_to_plane ):
55  """
56  Define the plane by supplying a point on the plane and the plane's normal.
57  """
58  _normal_to_plane = OpenMaya.MVector(normal_to_plane)
59  _normal_to_plane.normalize()
60 
61  # Calculate a,b,c,d based on input
62  self.a = _normal_to_plane.x
63  self.b = _normal_to_plane.y
64  self.c = _normal_to_plane.z
65  self.d = -(self.a*point_on_plane.x + self.b*point_on_plane.y + self.c*point_on_plane.z)
66 
67  def intersect( self, line ):
68  """
69  Intersect the plane with the given line. Return the intersection point if an intersection
70  occurs. Otherwise, raise an exception.
71  """
72  denominator = self.a*line[1].x + self.b*line[1].y + self.c*line[1].z
73 
74  # Verify that the vector and the plane are not parallel.
75  if (denominator < .00001):
76  raise Exception
77 
78  t = -(self.d + self.a*line[0].x + self.b*line[0].y + self.c*line[0].z) / denominator
79 
80  # Calculate the intersection point.
81  return line[0] + t * line[1]
82 
83 class LineMath:
84  """
85  This utility class represents a mathematical line and returns the closest point
86  on the line to a given point.
87  """
88  def __init__(self):
89  """
90  Initialze the member variables of the class.
91  """
92  self.point = OpenMaya.MPoint()
93  self.direction = OpenMaya.MVector()
94 
95  def set_line( self, line_point, line_direction ):
96  """
97  Define the line by supplying a point on the line and the line's direction.
98  """
99  self.point = OpenMaya.MPoint(line_point)
100  self.direction = OpenMaya.MVector(line_direction)
101  self.direction.normalize()
102 
103  def closest_point( self, to_point ):
104  """
105  Determine and return the point on the line which is closest to the given point.
106  """
107  t = self.direction * ( to_point - self.point )
108  return self.point + ( self.direction * t )
109 
110 class SquareGeometry:
111  """
112  This utility class defines methods for returning the four corner points of a unit square
113  in the X-Y plane.
114  """
115  @staticmethod
116  def top_left():
117  """
118  Return the top left corner of the square.
119  """
120  return OpenMaya.MPoint(-0.5, 0.5, 0.0)
121  @staticmethod
122  def top_right():
123  """
124  Return the top right corner of the square.
125  """
126  return OpenMaya.MPoint( 0.5, 0.5, 0.0)
127  @staticmethod
128  def bottom_left():
129  """
130  Return the bottom left corner of the square.
131  """
132  return OpenMaya.MPoint(-0.5, -0.5, 0.0)
133  @staticmethod
134  def bottom_right():
135  """
136  Return the bottom right corner of the square.
137  """
138  return OpenMaya.MPoint( 0.5, -0.5, 0.0)
139 
140 #
141 # SquareScaleManipulator
142 #
143 
144 class SquareScaleManipulator (OpenMayaUI.MPxManipulatorNode):
145  """
146  This is the subclassed manipulator node. It scales the selected objects
147  in the X direction based on dragging movements by the user.
148  """
149  kNodeName = 'SquareScaleContextManipulator'
150  kTypeId = OpenMaya.MTypeId( 0x81048 )
151 
152  def __init__(self):
153  """
154  Initialize the manipulator member variables.
155  """
157 
158  # Setup the plane with a point on the plane along with a normal
159  self.point_on_plane = SquareGeometry.top_left()
160 
161  # Set plug indicies to a default
162  self.top_index = -1
163  self.right_index = -1
164  self.bottom_index = -1
165  self.left_index = -1
166  self.top_name = -1
167  self.right_name = -1
168  self.bottom_name = -1
169  self.left_name = -1
170 
171  # initialize rotate/translate to a good default
172  self.rotate_x = 0.0
173  self.rotate_y = 0.0
174  self.rotate_z = 0.0
175  self.translate_x = 0.0
176  self.translate_y = 0.0
177  self.translate_z = 0.0
178 
179  # Normal = cross product of two vectors on the plane
180  v1 = OpenMaya.MVector(SquareGeometry.top_left()) - OpenMaya.MVector(SquareGeometry.top_right())
181  v2 = OpenMaya.MVector(SquareGeometry.top_right()) - OpenMaya.MVector(SquareGeometry.bottom_right())
182  self.normal_to_plane = v1 ^ v2
183 
184  # Necessary to normalize
185  self.normal_to_plane.normalize()
186  self.plane = PlaneMath()
187  self.plane.set_plane( self.point_on_plane, self.normal_to_plane )
188 
189 
190  @classmethod
191  def creator(cls):
192  return cls()
193 
194  @classmethod
195  def initialize(cls):
196  pass
197 
198  # virtual
199  def postConstructor(self):
200  self.top_index = self.addDoubleValue( 'topValue', 0 )
201  self.right_index = self.addDoubleValue( 'rightValue', 0 )
202  self.bottom_index = self.addDoubleValue( 'bottomValue', 0 )
203  self.left_index = self.addDoubleValue( 'leftValue', 0 )
204 
205  gl_pickable_item = self.glFirstHandle()
206  self.top_name = gl_pickable_item
207  self.bottom_name = gl_pickable_item + 1
208  self.right_name = gl_pickable_item + 2
209  self.left_name = gl_pickable_item + 3
210 
211  # virtual
212  def connectToDependNode(self, depend_node):
213  """
214  Connect the manipulator to the given dependency node.
215  """
216 
217  # Make sure we have a scaleX plug and connect the
218  # plug to the rightIndex we created in the postConstructor
219  scale_x_plug = None
220  nodeFn = OpenMaya.MFnDependencyNode(depend_node)
221 
222  try:
223  scale_x_plug = nodeFn.findPlug('scaleX', True)
224  except:
225  logger.info(" Could not find scaleX plug!")
226  return
227 
228  plug_index = 0
229  try:
230  plug_index = self.connectPlugToValue(scale_x_plug, self.right_index)
231  except:
232  logger.info(" Could not connectPlugToValue!")
233  return
234 
235  self.finishAddingManips()
236  return OpenMayaUI.MPxManipulatorNode.connectToDependNode(self, depend_node)
237 
238  def pre_draw(self):
239  """
240  Update the region dragged by the mouse.
241  """
242 
243  # Populate the point arrays which are in local space
244  tl = SquareGeometry.top_left()
245  tr = SquareGeometry.top_right()
246  bl = SquareGeometry.bottom_left()
247  br = SquareGeometry.bottom_right()
248 
249  # Depending on what's active, we modify the
250  # end points with mouse deltas in local space
251  active = self.glActiveName()
252  if active:
253  if ( active == self.top_name ):
254  tl += self.mouse_point_gl_name
255  tr += self.mouse_point_gl_name
256  if ( active == self.bottom_name ):
257  bl += self.mouse_point_gl_name
258  br += self.mouse_point_gl_name
259  if ( active == self.right_name ):
260  tr += self.mouse_point_gl_name
261  br += self.mouse_point_gl_name
262  if ( active == self.left_name ):
263  tl += self.mouse_point_gl_name
264  bl += self.mouse_point_gl_name
265 
266  return [tl, tr, bl, br]
267 
268  # virtual
269  def draw(self, view, path, style, status):
270  """
271  Draw the manupulator in a legacy viewport.
272  """
273 
274  # drawing in VP1 views will be done using V1 Python APIs:
275  gl_renderer = OpenMayaRenderV1.MHardwareRenderer.theRenderer()
276  gl_ft = gl_renderer.glFunctionTable()
277 
278  [tl, tr, bl, br] = self.pre_draw()
279 
280  # Begin the drawing
281  view.beginGL()
282 
283  # Push the matrix and set the translate/rotate. Perform
284  # operations in reverse order
285  gl_ft.glMatrixMode( OpenMayaRenderV1.MGL_MODELVIEW )
286  gl_ft.glPushMatrix()
287  gl_ft.glTranslatef( self.translate_x, self.translate_y, self.translate_z )
288  gl_ft.glRotatef( math.degrees(self.rotate_z), 0.0, 0.0, 1.0 )
289  gl_ft.glRotatef( math.degrees(self.rotate_y), 0.0, 1.0, 0.0 )
290  gl_ft.glRotatef( math.degrees(self.rotate_x), 1.0, 0.0, 0.0 )
291 
292  # Top
293  # Place before you draw the manipulator component that can be pickable.
294  self.colorAndName( view, self.top_name, False, self.mainColor() )
295  gl_ft.glBegin( OpenMayaRenderV1.MGL_LINES )
296  gl_ft.glVertex3f( tl.x, tl.y, tl.z )
297  gl_ft.glVertex3f( tr.x, tr.y, tr.z )
298  gl_ft.glEnd()
299 
300  # Right
301  self.colorAndName( view, self.right_name, True, self.mainColor() )
302  gl_ft.glBegin( OpenMayaRenderV1.MGL_LINES )
303  gl_ft.glVertex3f( tr.x, tr.y, tr.z )
304  gl_ft.glVertex3f( br.x, br.y, br.z )
305  gl_ft.glEnd()
306 
307  # Bottom
308  self.colorAndName( view, self.bottom_name, False, self.mainColor() )
309  gl_ft.glBegin( OpenMayaRenderV1.MGL_LINES )
310  gl_ft.glVertex3f( br.x, br.y, br.z )
311  gl_ft.glVertex3f( bl.x, bl.y, bl.z )
312  gl_ft.glEnd()
313 
314  # Left
315  self.colorAndName( view, self.left_name, True, self.mainColor() )
316  gl_ft.glBegin( OpenMayaRenderV1.MGL_LINES )
317  gl_ft.glVertex3f( bl.x, bl.y, bl.z )
318  gl_ft.glVertex3f( tl.x, tl.y, tl.z )
319  gl_ft.glEnd()
320 
321  # Pop matrix
322  gl_ft.glPopMatrix()
323 
324  # End the drawing
325  view.endGL()
326 
327  # virtual
328  def preDrawUI(self, view):
329  """
330  Cache the viewport for use in VP 2.0 drawing.
331  """
332  pass
333 
334  # virtual
335  def drawUI(self, draw_manager, frame_context):
336  """
337  Draw the manupulator in a VP 2.0 viewport.
338  """
339 
340  [tl, tr, bl, br] = self.pre_draw()
341 
343  xform.rotateByComponents([math.degrees(self.rotate_x), \
344  math.degrees(self.rotate_y), \
345  math.degrees(self.rotate_z), \
346  OpenMaya.MTransformationMatrix.kZYX], \
347  OpenMaya.MSpace.kWorld)
348 
349  mat = xform.asMatrix()
350  tl *= mat
351  tr *= mat
352  bl *= mat
353  br *= mat
354 
355  # Top
356  draw_manager.beginDrawable(OpenMayaRender.MUIDrawManager.kNonSelectable, self.top_name)
357  self.setHandleColor(draw_manager, self.top_name, self.dimmedColor())
358  draw_manager.line(tl, tr)
359  draw_manager.endDrawable()
360 
361  # Right
362  draw_manager.beginDrawable(OpenMayaRender.MUIDrawManager.kSelectable, self.right_name)
363  self.setHandleColor(draw_manager, self.right_name, self.mainColor())
364  draw_manager.line(tr, br)
365  draw_manager.endDrawable()
366 
367  # Bottom
368  draw_manager.beginDrawable(OpenMayaRender.MUIDrawManager.kNonSelectable, self.bottom_name)
369  self.setHandleColor(draw_manager, self.bottom_name, self.dimmedColor())
370  draw_manager.line(br, bl)
371  draw_manager.endDrawable()
372 
373  # Left
374  draw_manager.beginDrawable(OpenMayaRender.MUIDrawManager.kSelectable, self.left_name)
375  self.setHandleColor(draw_manager, self.left_name, self.mainColor())
376  draw_manager.line(bl, tl)
377  draw_manager.endDrawable()
378 
379  # virtual
380  def doPress( self, view ):
381  """
382  Handle the mouse press event in a VP2.0 viewport.
383  """
384  # Reset the mousePoint information on a new press.
385  self.mouse_point_gl_name = OpenMaya.MPoint.kOrigin
386  self.update_drag_information()
387 
388  # virtual
389  def doDrag( self, view ):
390  """
391  Handle the mouse drag event in a VP2.0 viewport.
392  """
393  self.update_drag_information()
394 
395  # virtual
396  def doRelease( self, view ):
397  """
398  Handle the mouse release event in a VP2.0 viewport.
399  """
400  pass
401 
402  def set_draw_transform_info( self, rotation, translation ):
403  """
404  Store the given rotation and translation.
405  """
406  self.rotate_x = rotation[0]
407  self.rotate_y = rotation[1]
408  self.rotate_z = rotation[2]
409  self.translate_x = translation[0]
410  self.translate_y = translation[1]
411  self.translate_z = translation[2]
412 
413  def update_drag_information( self ):
414  """
415  Update the mouse's intersection location with the manipulator
416  """
417  # Find the mouse point in local space
418  self.local_mouse = self.mouseRay()
419 
420  # Find the intersection of the mouse point with the manip plane
421  mouse_intersection_with_manip_plane = self.plane.intersect( self.local_mouse )
422 
423  self.mouse_point_gl_name = mouse_intersection_with_manip_plane
424 
425  active = self.glActiveName()
426  if active:
427  start = OpenMaya.MPoint([0, 0, 0])
428  end = OpenMaya.MPoint([0, 0, 0])
429  if ( active == self.top_name ):
430  start = OpenMaya.MPoint(-0.5, 0.5, 0.0)
431  end = OpenMaya.MPoint( 0.5, 0.5, 0.0)
432  if ( active == self.bottom_name ):
433  start = OpenMaya.MPoint(-0.5, -0.5, 0.0)
434  end = OpenMaya.MPoint( 0.5, -0.5, 0.0)
435  if ( active == self.right_name ):
436  start = OpenMaya.MPoint( 0.5, 0.5, 0.0)
437  end = OpenMaya.MPoint( 0.5, -0.5, 0.0)
438  if ( active == self.left_name ):
439  start = OpenMaya.MPoint(-0.5, 0.5, 0.0)
440  end = OpenMaya.MPoint(-0.5, -0.5, 0.0)
441 
442  if ( active ):
443  # Find a vector on the plane
444  a = OpenMaya.MPoint( start.x, start.y, start.z )
445  b = OpenMaya.MPoint( end.x, end.y, end.z )
446  vab = a - b
447 
448  # Define line with a point and a vector on the plane
449  line = LineMath()
450  line.set_line( start, vab )
451 
452  # Find the closest point so that we can get the
453  # delta change of the mouse in local space
454  cpt = line.closest_point( self.mouse_point_gl_name )
455  self.mouse_point_gl_name -= cpt
456 
457  min_change_value = min( self.mouse_point_gl_name.x, self.mouse_point_gl_name.y, self.mouse_point_gl_name.z )
458  max_change_value = max( self.mouse_point_gl_name.x, self.mouse_point_gl_name.y, self.mouse_point_gl_name.z )
459  if ( active == self.right_name ):
460  self.setDoubleValue( self.right_index, max_change_value )
461  if ( active == self.left_name ):
462  self.setDoubleValue( self.right_index, min_change_value )
463 
464 
465 # command
466 class SquareScaleManipContextCmd (OpenMayaUI.MPxContextCommand):
467  """
468  This command class is used to create instances of the SquareScaleManipContext class.
469  """
470  kPluginCmdName = "squareScaleManipContext"
471 
472  def __init__(self):
474 
475  @staticmethod
476  def creator():
477  return SquareScaleManipContextCmd()
478 
479  def makeObj(self):
480  """
481  Create and return an instance of the SquareScaleManipContext class.
482  """
483  return SquareScaleManipContext()
484 
485 class SquareScaleManipContext(OpenMayaUI.MPxSelectionContext):
486  """
487  This context handles all mouse interaction in the viewport when activated.
488  When activated, it creates and manages an instance of the SquareScaleManuplator
489  class on the selected objects.
490  """
491 
492  kContextName = 'SquareScaleManipContext'
493 
494  @classmethod
495  def creator(cls):
496  return cls()
497 
498  def __init__(self):
499  """
500  Initialize the members of the SquareScaleManipContext class.
501  """
503  self.setTitleString('Plug-in manipulator: ' + SquareScaleManipContext.kContextName)
504  self.manipulator_class_ptr = None
505  self.first_object_selected = None
506  self.active_list_modified_msg_id = -1
507 
508  # virtual
509  def toolOnSetup(self, event):
510  """
511  Set the help string and selection list callback.
512  """
513  self.setHelpString('Move the object using the manipulator')
514 
515  SquareScaleManipContext.update_manipulators_cb(self)
516  try:
517  self.active_list_modified_msg_id = OpenMaya.MModelMessage.addCallback( \
518  OpenMaya.MModelMessage.kActiveListModified, \
519  SquareScaleManipContext.update_manipulators_cb, self)
520  except:
521  OpenMaya.MGlobal.displayError("SquareScaleManipContext.toolOnSetup(): Model addCallback failed")
522 
523  # Removes the callback
524  # virtual
525  def toolOffCleanup(self):
526  """
527  Unregister the selection list callback.
528  """
529  try:
530  OpenMaya.MModelMessage.removeCallback(self.active_list_modified_msg_id)
531  self.active_list_modified_msg_id = -1
532  except:
533  OpenMaya.MGlobal.displayError("SquareScaleManipContext.toolOffCleanup(): Model remove callback failed")
534 
536 
537  # virtual
538  def namesOfAttributes(self, attribute_names):
539  """
540  Return the names of the attributes of the selected objects this context will be modifying.
541  """
542  attribute_names.append('scaleX')
543 
544  # virtual
545  def setInitialState(self):
546  """
547  Set the initial transform of the manipulator.
548  """
549  xform = OpenMaya.MFnTransform( self.first_object_selected )
550  xformMatrix = xform.transformation()
551  translation = xformMatrix.translation( OpenMaya.MSpace.kWorld )
552  rotation = xformMatrix.rotation(False)
553 
554  self.manipulator_class_ptr.set_draw_transform_info( rotation, translation )
555 
556  # Ensure that valid geometry is selected
557  def valid_geometry_selected(self):
558  """
559  Check to make sure the selected objects have transforms.
560  """
561  list = None
562  iter = None
563  try:
565  iter = OpenMaya.MItSelectionList(list)
566  except:
567  logger.info(" Could not get active selection")
568  return False
569 
570  if (not list) or (list.length() == 0):
571  return False
572 
573  while not iter.isDone():
574  depend_node = iter.getDependNode()
575  if (depend_node.isNull() or (not depend_node.hasFn(OpenMaya.MFn.kTransform))):
576  OpenMaya.MGlobal.displayWarning('Object in selection list is not right type of node')
577  return False
578  iter.next()
579  return True
580 
581  def update_manipulators_cb(ctx):
582  """
583  Callback that creates the manipulator if valid geometry is selected. Also removes
584  the manipulator if no geometry is selected. Handles connecting the manipulator to
585  multiply selected nodes.
586  """
587  try:
588  ctx.deleteManipulators()
589  except:
590  logger.info(" No manipulators to delete")
591 
592  try:
593  if not ctx.valid_geometry_selected():
594  return
595 
596  # Clear info
597  ctx.manipulator_class_ptr = None
598  ctx.first_object_selected = OpenMaya.MObject.kNullObj
599 
600  (manipulator, manip_object) = SquareScaleManipulator.newManipulator('SquareScaleContextManipulator')
601 
602  if manipulator:
603  # Save state
604  ctx.manipulator_class_ptr = manipulator
605 
606  # Add the manipulator
607  ctx.addManipulator(manip_object)
608 
610  iter = OpenMaya.MItSelectionList(list)
611 
612  while not iter.isDone():
613  depend_node = iter.getDependNode()
614  depend_node_fn = OpenMaya.MFnDependencyNode(depend_node)
615 
616  # Connect the manipulator to the object in the selection list.
617  if (not manipulator.connectToDependNode(depend_node)):
618  OpenMaya.MGlobal.displayWarning('Error connecting manipulator to object %s' % depend_node_fn.name())
619  iter.next()
620  continue
621 
622  if ( ctx.first_object_selected == OpenMaya.MObject.kNullObj ):
623  ctx.first_object_selected = depend_node
624  iter.next()
625 
626  # Allow the manipulator to set initial state
627  ctx.setInitialState()
628 
629  except:
630  OpenMaya.MGlobal.displayError('Failed to create new manipulator')
631  return
632 
633  update_manipulators_cb = staticmethod(update_manipulators_cb)
634 
635 
636 # Initialize the script plug-in
637 def initializePlugin(plugin):
638  pluginFn = OpenMaya.MFnPlugin(plugin)
639 
640  try:
641  pluginFn.registerContextCommand( SquareScaleManipContextCmd.kPluginCmdName, SquareScaleManipContextCmd.creator)
642  except:
643  sys.stderr.write("Failed to register context command: %s\n" % SquareScaleManipContextCmd.kPluginCmdName)
644  raise
645 
646  try:
647  pluginFn.registerNode( SquareScaleManipulator.kNodeName, SquareScaleManipulator.kTypeId, \
648  SquareScaleManipulator.creator, SquareScaleManipulator.initialize, \
649  OpenMaya.MPxNode.kManipulatorNode)
650  except:
651  sys.stderr.write("Failed to register node: %s\n" % SquareScaleManipulator.kNodeName)
652  raise
653 
654 
655 # Uninitialize the script plug-in
656 def uninitializePlugin(plugin):
657  pluginFn = OpenMaya.MFnPlugin(plugin)
658  try:
659  pluginFn.deregisterContextCommand(SquareScaleManipContextCmd.kPluginCmdName)
660  except:
661  sys.stderr.write(
662  "Failed to unregister command: %s\n" % SquareScaleManipContextCmd.kPluginCmdName)
663  raise
664 
665  try:
666  pluginFn.deregisterNode(SquareScaleManipulator.kTypeId)
667  except:
668  sys.stderr.write(
669  "Failed to unregister node: %s\n" % SquareScaleManipulator.kNodeName)
670  raise
671 
672 #-
673 # ==========================================================================
674 # Copyright (C) 2016 Autodesk, Inc. and/or its licensors. All
675 # rights reserved.
676 #
677 # The coded instructions, statements, computer programs, and/or related
678 # material (collectively the "Data") in these files contain unpublished
679 # information proprietary to Autodesk, Inc. ("Autodesk") and/or its
680 # licensors, which is protected by U.S. and Canadian federal copyright
681 # law and by international treaties.
682 #
683 # The Data is provided for use exclusively by You. You have the right
684 # to use, modify, and incorporate this Data into other products for
685 # purposes authorized by the Autodesk software license agreement,
686 # without fee.
687 #
688 # The copyright notices in the Software and this entire statement,
689 # including the above license grant, this restriction and the
690 # following disclaimer, must be included in all copies of the
691 # Software, in whole or in part, and all derivative works of
692 # the Software, unless such copies or derivative works are solely
693 # in the form of machine-executable object code generated by a
694 # source language processor.
695 #
696 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
697 # AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
698 # WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
699 # NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
700 # PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
701 # TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
702 # BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
703 # DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
704 # AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
705 # OR PROBABILITY OF SUCH DAMAGES.
706 #
707 # ==========================================================================
708 #+