Python API 2.0 Reference: scripted/pySquareScaleManipContext.py

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