Python API 2.0 Reference
python/api2/py2LocatorHelperShape.py
1 from __future__ import division
2 ###############################################################################
3 #
4 # pyLocatorHelperShape.py
5 #
6 # A plug-in locator with configurable shape representations. The locator
7 # supports reading in a library of shape representations serialized as
8 # JSON files, that populates its 'shapes' enumeration attribute.
9 #
10 # The plug-in provides a command to serialize combinations of meshes
11 # and curves into the library folder.
12 #
13 ## Usage ##
14 #
15 # 1. Create a locatorHelperShape node. By default, the node will have
16 # a default representation consisting of a mesh cube and
17 # diamond shaped curve.
18 #
19 # import maya.cmds as cmds
20 # cmds.createNode('locatorHelperShape')
21 #
22 # 2. Populate the locatorHelperShape library.
23 #
24 # By default, the library is located at $(PLUGIN_PATH)/library/.
25 # Alternately, you can set the library location using the
26 # 'MAYA_LOCATOR_HELPER_SHAPE_LIB' environment variable. This must be set
27 # prior to loading the plug-in.
28 #
29 # import maya.cmds as cmds
30 #
31 # # Create a sphere representation
32 # sph = cmds.polySphere(name='sphere')[0]
33 # cmds.locatorHelperShapeExport(sph, force=True, name=sph)
34 #
35 # # Create a representation with multiple shapes
36 # c = cmds.circle(name='circle')[0]
37 # s = cmds.nurbsSquare(name='square')[0]
38 # cmds.locatorHelperShapeExport(s, c, force=True, name='combo')
39 #
40 # # Can alternately create using the selection list
41 # cmds.select(c, s, r=True)
42 # cmds.locatorHelperShapeExport(force=True, name='combo')
43 #
44 # # Can adjust the number of samples to take from the curve
45 # # when tessellating them into line segments. Default is 50.
46 # cmds.locatorHelperShapeExport(force=True, name='combo', curveSamples=100)
47 #
48 # # Shapes are exported in object space. Freeze
49 # # transforms as necessary to get the desired output.
50 # cmds.scale(1, 2, 1, c)
51 # cmds.makeIdentity(c, apply=True,t=1,r=1,s=1,n=0,pn=1)
52 # cmds.locatorHelperShapeExport(c, force=True, name='oval')
53 #
54 # 3. Reload the plug-in to see the new shapes. You may need to
55 # reload the Attribute Editor to view the updated enumeration.
56 # You can reload the AE by:
57 #
58 # a. Select a locatorHelperShape node.
59 # b. In the AE, navigate the AE menu: 'Show > Set Global View > Lookdev'
60 # b. Restore the AE global view: 'Show > Set Global View > Default'
61 #
62 ## Overview ##
63 #
64 # The plug-in can be broken down into the following components:
65 #
66 # 1. helperShapeRep / helperShapeRepItem
67 #
68 # Defines the shape representations and are responsible for
69 # serialization and generation of the vertex/index buffers
70 # for display.
71 #
72 # 2. helperShapeExportCmd
73 #
74 # Defines the command to convert a list or selection of meshes
75 # and/or curves into helperShapeRepItems for serialization.
76 #
77 # 3. helperShapeNode
78 #
79 # Defines the plug-in locator node.
80 #
81 # 4. helperShapeSubSceneOverride
82 #
83 # Defines the VP2 override implementation for rendering the
84 # helperShapeNode.
85 #
86 from builtins import range
87 from builtins import object
88 from builtins import zip
89 from builtins import next
90 import os
91 import sys
92 import ctypes
93 import json
94 
95 import maya.cmds as cmds
96 import maya.mel as mel
97 import maya.api.OpenMaya as om
98 import maya.api.OpenMayaRender as omr
99 import maya.api.OpenMayaUI as omui
100 
101 maya_useNewAPI = True
102 
103 ###############################################################################
104 #
105 # Constants
106 #
107 DEFAULT_COLOR = (0.0, 0.7, 0.15)
108 DEFAULT_LINE_WIDTH = 1.0
109 
110 HELPER_SHAPE_SELECTION_MASK = 'locatorHelperShapeSelection'
111 HELPER_SHAPE_SELECTION_PRIORITY = 9
112 
113 # SubScene Consolidation support
114 ENABLE_CONSOLIDATION = True
115 consolidation_func = getattr(omr.MRenderItem, 'wantSubSceneConsolidation', None)
116 SUPPORTS_CONSOLIDATION = callable(consolidation_func)
117 WANT_CONSOLIDATION = SUPPORTS_CONSOLIDATION and ENABLE_CONSOLIDATION
118 
119 ###############################################################################
120 #
121 # Utility functions
122 #
123 def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
124  return abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
125 
126 def isclose_tuple(a, b, rel_tol=1e-9, abs_tol=0.0):
127  for (aa, bb) in zip(a, b):
128  if not isclose(aa, bb):
129  return False
130  return True
131 
132 
133 ###############################################################################
134 #
135 # helperShapeRep / helperShapeRepItem
136 #
137 # The helperShapeRep* classes define the abstraction for the shapes that
138 # helperShapeNode can display. These classes are responsible for:
139 #
140 # 1. Owning the data that defines the shapes.
141 # 2. Computing the associated vertex and index buffers.
142 # 3. Serializing/Deserializing to Python dictionaries.
143 #
144 class helperShapeRepItemType(object):
145  ''' helperShape representation item type definition. '''
146  kMesh = None
147  kCurve = None
148 
149  def __init__(self, index, name):
150  self.index = index
151  self.name = name
152 
153  def __eq__(self, other):
154  return self.index == other.index
155 
156  def __ne__(self, other):
157  return not(self == other)
158 
159  def __int__(self):
160  return self.index
161 
162  def __str__(self):
163  return self.name
164 
165 # Populate our helperShapeRepItem type variables.
166 helperShapeRepItemType.kMesh = helperShapeRepItemType(0, 'mesh')
167 helperShapeRepItemType.kCurve = helperShapeRepItemType(1, 'curve')
168 
169 class helperShapeRepItem(object):
170  ''' Abstract base class for a helperShape representation item. '''
171  kTypeKey = 'type'
172 
173  def __init__(self, type):
174  ''' Constructor. '''
175  self.type = type
176 
177  @classmethod
178  def deserialize_type(cls, data):
179  ''' Deserialize the item type from a Python dictionary '''
180  if data[cls.kTypeKey] == helperShapeRepItemType.kMesh.name:
181  return helperShapeRepItemType.kMesh
182  elif data[cls.kTypeKey] == helperShapeRepItemType.kCurve.name:
183  return helperShapeRepItemType.kCurve
184  raise RuntimeError('Invalid helperShapeRepItemType: {}'.format(data[cls.kTypeKey]))
185 
186  def deserialize(self, data):
187  ''' Deserialize the item from a Python dictionary '''
188  raise NotImplementedError
189 
190  def serialize(self, data):
191  ''' Serialize the item to a Python dictionary '''
192  if self.type == helperShapeRepItemType.kMesh:
193  data[self.kTypeKey] = helperShapeRepItemType.kMesh.name
194  elif self.type == helperShapeRepItemType.kCurve:
195  data[self.kTypeKey] = helperShapeRepItemType.kCurve.name
196 
197  def vertex_buffer(self):
198  ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
199  raise NotImplementedError
200 
201  def wire_index_buffer(self):
202  ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
203  raise NotImplementedError
204 
205  def shaded_index_buffer(self):
206  ''' Returns an MIndexBuffer containing the index buffer for shaded draw. '''
207  raise NotImplementedError
208 
209  def clear_buffers(self):
210  ''' Clears cached vertex/index buffers for this item. '''
211  raise NotImplementedError
212 
213  def bounding_box(self):
214  ''' Returns an MBoundingBox for this item. '''
215  raise NotImplementedError
216 
217  @classmethod
218  def _create_index_buffer(cls, indices):
219  ''' Returns an MIndexBuffer populated with the given indices. '''
220  num_indices = len(indices)
221  buffer = omr.MIndexBuffer(omr.MGeometry.kUnsignedInt32)
222  address = buffer.acquire(num_indices, True)
223  data = ((ctypes.c_uint)*num_indices).from_address(address)
224  for (i,val) in enumerate(indices):
225  data[i] = val
226  buffer.commit(address)
227  return buffer
228 
229  @classmethod
230  def _create_vertex_buffer(cls, desc, vertex_data):
231  ''' Returns an MVertexBuffer populated with the given vertex data. '''
232  num_vertices = len(vertex_data)
233  buffer = omr.MVertexBuffer(desc)
234  address = buffer.acquire(num_vertices, True)
235  if desc.dataType == omr.MGeometry.kFloat and desc.dimension == 3:
236  data = ((ctypes.c_float * 3)*num_vertices).from_address(address)
237  for (i,val) in enumerate(vertex_data):
238  data[i][0] = val[0]
239  data[i][1] = val[1]
240  data[i][2] = val[2]
241  buffer.commit(address)
242  else:
243  # This plug-in only requires working with float3 data.
244  raise NotImplementedError
245  return buffer
246 
247 class helperShapeRepItemMesh(helperShapeRepItem):
248  ''' Defines a mesh based helperShape representation item. '''
249 
250  kVerticesKey = 'vertices'
251  kNormalsKey = 'normals'
252  kWireIndicesKey = 'wireIndices'
253  kShadedIndicesKey = 'shadedIndices'
254 
255  def __init__(self):
256  ''' Constructor. '''
257  super(helperShapeRepItemMesh, self).__init__(helperShapeRepItemType.kMesh)
258 
259  self.position_desc = omr.MVertexBufferDescriptor('', omr.MGeometry.kPosition, omr.MGeometry.kFloat, 3)
260  self.normal_desc = omr.MVertexBufferDescriptor('', omr.MGeometry.kNormal, omr.MGeometry.kFloat, 3)
261 
262  self.vertices = None
263  self.normals = None
264 
265  self.wire_indices = None
266  self.shaded_indices = None
267 
268  self.position_buffer = None
269  self.normal_buffer = None
270  self.index_buffer_wire = None
271  self.index_buffer_shaded = None
272 
273  def deserialize(self, data):
274  ''' Deserialize the item from a Python dictionary '''
275  self.vertices = data[self.kVerticesKey]
276  self.normals = data[self.kNormalsKey]
277  self.wire_indices = data[self.kWireIndicesKey]
278  self.shaded_indices = data[self.kShadedIndicesKey]
279 
280  def serialize(self, data):
281  ''' Serialize the item to a Python dictionary '''
282  super(helperShapeRepItemMesh, self).serialize(data)
283  data[self.kVerticesKey] = self.vertices
284  data[self.kNormalsKey] = self.normals
285  data[self.kWireIndicesKey] = self.wire_indices
286  data[self.kShadedIndicesKey] = self.shaded_indices
287 
288  def vertex_buffer(self):
289  ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
290  if not self.position_buffer:
291  self.position_buffer = self._create_vertex_buffer(self.position_desc, self.vertices)
292  if not self.normal_buffer:
293  self.normal_buffer = self._create_vertex_buffer(self.normal_desc, self.normals)
294  vertex_buffer = omr.MVertexBufferArray()
295  vertex_buffer.append(self.position_buffer, 'positions')
296  vertex_buffer.append(self.normal_buffer, 'normals')
297  return vertex_buffer
298 
299  def wire_index_buffer(self):
300  ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
301  if not self.index_buffer_wire:
302  self.index_buffer_wire = self._create_index_buffer(self.wire_indices)
303  return self.index_buffer_wire
304 
305  def shaded_index_buffer(self):
306  ''' Returns an MIndexBuffer containing the index buffer for shaded draw. '''
307  if not self.index_buffer_shaded:
308  self.index_buffer_shaded = self._create_index_buffer(self.shaded_indices)
309  return self.index_buffer_shaded
310 
311  def clear_buffers(self):
312  ''' Clears cached vertex/index buffers for this item. '''
313  self.position_buffer = None
314  self.normal_buffer = None
315  self.index_buffer_wire = None
316  self.index_buffer_shaded = None
317 
318  def bounding_box(self):
319  ''' Returns an MBoundingBox for this item. '''
320  box = om.MBoundingBox()
321  for v in self.vertices:
322  box.expand(om.MPoint(v[0], v[1], v[2]))
323  return box
324 
325 class helperShapeRepItemCurve(helperShapeRepItem):
326  ''' Defines a curve based helperShape representation item. '''
327 
328  kVerticesKey = 'vertices'
329  kIndicesKey = 'indices'
330 
331  def __init__(self):
332  ''' Constructor. '''
333  super(helperShapeRepItemCurve, self).__init__(helperShapeRepItemType.kCurve)
334 
335  self.position_desc = omr.MVertexBufferDescriptor('', omr.MGeometry.kPosition, omr.MGeometry.kFloat, 3)
336 
337  self.vertices = None
338  self.indices = None
339 
340  self.position_buffer = None
341  self.index_buffer = None
342 
343  def deserialize(self, data):
344  ''' Deserialize the item from a Python dictionary '''
345  self.vertices = data[self.kVerticesKey]
346  self.indices = data[self.kIndicesKey]
347 
348  def serialize(self, data):
349  ''' Serialize the item to a Python dictionary '''
350  super(helperShapeRepItemCurve, self).serialize(data)
351  data[self.kVerticesKey] = self.vertices
352  data[self.kIndicesKey] = self.indices
353 
354  def vertex_buffer(self):
355  ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
356  if not self.position_buffer:
357  self.position_buffer = self._create_vertex_buffer(self.position_desc, self.vertices)
358  vertex_buffer = omr.MVertexBufferArray()
359  vertex_buffer.append(self.position_buffer, 'positions')
360  return vertex_buffer
361 
362  def wire_index_buffer(self):
363  ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
364  if not self.index_buffer:
365  self.index_buffer = self._create_index_buffer(self.indices)
366  return self.index_buffer
367 
368  def clear_buffers(self):
369  ''' Clears cached vertex/index buffers for this item. '''
370  self.position_buffer = None
371  self.index_buffer = None
372 
373  def bounding_box(self):
374  ''' Returns an MBoundingBox for this item. '''
375  box = om.MBoundingBox()
376  for v in self.vertices:
377  box.expand(om.MPoint(v[0], v[1], v[2]))
378  return box
379 
380 class helperShapeRepItemCube(helperShapeRepItemMesh):
381  ''' A sample default cube mesh representation item. '''
382  def __init__(self):
383  super(helperShapeRepItemCube, self).__init__()
384 
385  # Exploded vertex layout (unshared normals)
386  self.vertices = [
387  #top
388  (-0.5, 0.5,-0.5),
389  (-0.5, 0.5, 0.5),
390  ( 0.5, 0.5, 0.5),
391  ( 0.5, 0.5,-0.5),
392 
393  #left
394  (-0.5,-0.5,-0.5),
395  (-0.5,-0.5, 0.5),
396  (-0.5, 0.5, 0.5),
397  (-0.5, 0.5,-0.5),
398 
399  #bottom
400  ( 0.5,-0.5, 0.5),
401  (-0.5,-0.5, 0.5),
402  (-0.5,-0.5,-0.5),
403  ( 0.5,-0.5,-0.5),
404 
405  #right
406  ( 0.5,-0.5,-0.5),
407  ( 0.5, 0.5,-0.5),
408  ( 0.5, 0.5, 0.5),
409  ( 0.5,-0.5, 0.5),
410 
411  #front
412  (-0.5,-0.5, 0.5),
413  ( 0.5,-0.5, 0.5),
414  ( 0.5, 0.5, 0.5),
415  (-0.5, 0.5, 0.5),
416 
417  #back
418  (-0.5,-0.5,-0.5),
419  (-0.5, 0.5,-0.5),
420  ( 0.5, 0.5,-0.5),
421  ( 0.5,-0.5,-0.5)
422  ]
423 
424  self.normals = [
425  # top
426  ( 0.0, 1.0, 0.0),
427  ( 0.0, 1.0, 0.0),
428  ( 0.0, 1.0, 0.0),
429  ( 0.0, 1.0, 0.0),
430 
431  #left
432  (-1.0, 0.0, 0.0),
433  (-1.0, 0.0, 0.0),
434  (-1.0, 0.0, 0.0),
435  (-1.0, 0.0, 0.0),
436 
437  #bottom
438  ( 0.0,-1.0, 0.0),
439  ( 0.0,-1.0, 0.0),
440  ( 0.0,-1.0, 0.0),
441  ( 0.0,-1.0, 0.0),
442 
443  #right
444  ( 1.0, 0.0, 0.0),
445  ( 1.0, 0.0, 0.0),
446  ( 1.0, 0.0, 0.0),
447  ( 1.0, 0.0, 0.0),
448 
449  #front
450  ( 0.0, 0.0, 1.0),
451  ( 0.0, 0.0, 1.0),
452  ( 0.0, 0.0, 1.0),
453  ( 0.0, 0.0, 1.0),
454 
455  #back
456  ( 0.0, 0.0,-1.0),
457  ( 0.0, 0.0,-1.0),
458  ( 0.0, 0.0,-1.0),
459  ( 0.0, 0.0,-1.0)
460  ]
461 
462  num_prim = len(self.vertices)
463  num_index = num_prim * 2
464  self.wire_indices = [None] * num_index
465  i = 0
466  while i < num_index:
467  start_index = i // 8 * 4 # 8 indices per face. 4 verts per face in the vertexBuffer
468  pairs = [(0,1), (1,2), (2,3), (3,0)]
469  for (index, p) in enumerate(pairs):
470  self.wire_indices[i + index*2] = start_index + p[0]
471  self.wire_indices[i + index*2 + 1] = start_index + p[1]
472  i += 8
473 
474  num_prim = len(self.vertices)
475  num_index = num_prim // 4 * 6 # 4 verts per quad, 6 indices per quad (2 tris)
476  self.shaded_indices = [None] * num_index
477  i = 0
478  while i < num_index:
479  start_index = i // 6 * 4
480  tris = [(0, 1, 2), (0, 2, 3)]
481  for (index, t) in enumerate(tris):
482  self.shaded_indices[i + index*3] = start_index + t[0]
483  self.shaded_indices[i + index*3 + 1] = start_index + t[1]
484  self.shaded_indices[i + index*3 + 2] = start_index + t[2]
485  i += 6
486 
487 class helperShapeRepItemDiamond(helperShapeRepItemCurve):
488  ''' A sample default diamond curve representation item. '''
489  def __init__(self):
490  super(helperShapeRepItemDiamond, self).__init__()
491  self.vertices = [
492  ( 1.0, 0.0, 0.0),
493  ( 0.0, 0.0, 1.0),
494  (-1.0, 0.0, 0.0),
495  ( 0.0, 0.0,-1.0)
496  ]
497  self.indices = [0,1,1,2,2,3,3,0]
498 
499 class helperShapeRep(object):
500  '''
501  Defines the helperShape representation class.
502  A representation consists of a list of representation items.
503  '''
504 
505  kRenderItemsKey = 'renderItems'
506 
507  def __init__(self):
508  self.name = None
509  self.items = []
510 
511  def deserialize(self, file_path):
512  ''' Deserialize the representation from JSON. '''
513  self.name = os.path.splitext(os.path.basename(file_path))[0]
514  with open(file_path, 'r') as f:
515  data = json.load(f)
516 
517  # Validate data
518  if not isinstance(data, dict):
519  raise RuntimeError('Invalid JSON helperShape file: %s'.format(file_path))
520  if not self.kRenderItemsKey in data or not isinstance(data[self.kRenderItemsKey], list):
521  raise RuntimeError('Invalid [renderItems] key in file: %s'.format(file_path))
522 
523  # Read data
524  for item_data in data[self.kRenderItemsKey]:
525  type = helperShapeRepItem.deserialize_type(item_data)
526  if type == helperShapeRepItemType.kMesh:
527  item = helperShapeRepItemMesh()
528  elif type == helperShapeRepItemType.kCurve:
529  item = helperShapeRepItemCurve()
530  else:
531  continue
532  item.deserialize(item_data)
533  self.items.append(item)
534 
535  def serialize(self, file_path):
536  ''' Serialize the representation to JSON. '''
537  data = {}
538  data[self.kRenderItemsKey] = []
539  for item in self.items:
540  item_data = {}
541  item.serialize(item_data)
542  data[self.kRenderItemsKey].append(item_data)
543  with open(file_path, 'w') as f:
544  json.dump( data, f, sort_keys=True, indent=4, separators=[',', ': '] )
545 
546 class helperShapeRepDefault(helperShapeRep):
547  '''
548  A sample default helper representation consisting of a cube mesh
549  and diamond curve.
550  '''
551  def __init__(self):
552  super(helperShapeRepDefault, self).__init__()
553  self.name = 'default'
554  self.items.append( helperShapeRepItemCube() )
555  self.items.append( helperShapeRepItemDiamond() )
556 
557 
558 ###############################################################################
559 #
560 # helperShapeExportCmd (MPxCommand)
561 #
562 class helperShapeExportCmd(om.MPxCommand):
563  ''' helperShapeExportCmd command class. '''
564  name = 'locatorHelperShapeExport'
565 
566  kNameFlag = 'n'
567  kNameFlagLong = 'name'
568  kCurveSamplesFlag = 'cs'
569  kCurveSamplesFlagLong = 'curveSamples'
570  kForceFlag = 'f'
571  kForceFlagLong = 'force'
572 
573  def __init__(self):
574  ''' Constructor. '''
575  om.MPxCommand.__init__(self)
576 
577  self.name = None
578  self.curve_samples = 50
579  self.force = False
580 
581  @staticmethod
582  def creator():
583  ''' Creator method. '''
584  return helperShapeExportCmd()
585 
586  @staticmethod
587  def new_syntax():
588  ''' Command syntax definition. '''
589  syntax = om.MSyntax()
590  syntax.addFlag(helperShapeExportCmd.kNameFlag, helperShapeExportCmd.kNameFlagLong, om.MSyntax.kString)
591  syntax.addFlag(helperShapeExportCmd.kCurveSamplesFlag, helperShapeExportCmd.kCurveSamplesFlagLong, om.MSyntax.kUnsigned)
592  syntax.addFlag(helperShapeExportCmd.kForceFlag, helperShapeExportCmd.kForceFlagLong)
593  syntax.setObjectType(om.MSyntax.kStringObjects, 0)
594  return syntax
595 
596  @classmethod
597  def valid_name(cls, name):
598  '''
599  Returns true if the name does not already exist in the
600  shape library path. False otherwise.
601  '''
602  for (root, dirs, files) in os.walk(helperShapeNode.shapes_lib_path):
603  for f in files:
604  (file_name, file_ext) = os.path.splitext(f)
605  if not file_ext == '.json':
606  continue
607  if name == file_name:
608  return False
609  return True
610 
611  def get_objects(self, parser):
612  '''
613  Filters the command object list for meshes/curve shapes.
614  If the command object list is empty, filters the selection
615  list for meshes/curve shapes.
616 
617  Returns the meshes/curve shapes in a list of MObjects.
618  '''
619  obj_strs = parser.getObjectStrings()
620  sel_list = om.MSelectionList()
621  if len(obj_strs):
622  for o in obj_strs:
623  sel_list.add(o)
624  else:
625  sel_list = om.MGlobal.getActiveSelectionList()
626 
627  objects = []
628  for i in range(sel_list.length()):
629  try:
630  path = sel_list.getDagPath(i)
631  except TypeError:
632  continue
633 
634  multiple_shapes = False
635  try:
636  path.extendToShape()
637  except RuntimeError:
638  multiple_shapes = True
639 
640  if not multiple_shapes:
641  if path.apiType() == om.MFn.kMesh or path.apiType() == om.MFn.kNurbsCurve:
642  objects.append(path.node())
643  else:
644  dag_it = om.MItDag()
645  dag_it.reset(path)
646  while not dag_it.isDone():
647  item = dag_it.getPath()
648  if item.apiType() == om.MFn.kMesh or item.apiType() == om.MFn.kNurbsCurve:
649  objects.append(item.node())
650  next(dag_it)
651  return objects
652 
653  def generate_mesh(self, obj):
654  '''
655  Given a mesh shape MObject, generate a helperShapeRepItemMesh.
656  '''
657  if not obj.apiType() == om.MFn.kMesh:
658  return None
659 
660  # Store raw position/normal mesh data as lists of float3 tuples
661  mesh_fn = om.MFnMesh(obj)
662  raw_vertices_mpts = mesh_fn.getPoints()
663  raw_vertices = [ (p.x, p.y, p.z) for p in raw_vertices_mpts ]
664  raw_normals_mvcs = mesh_fn.getNormals()
665  raw_normals = [ (n.x, n.y, n.z) for n in raw_normals_mvcs ]
666 
667  # Output vertex data arrays
668  vertices = []
669  normals = []
670  wire_indices = []
671  shaded_indices = []
672 
673  # Dictionary with ((position, normal), index) key
674  # value pairs to test for duplicate vertex data.
675  unique_vertices = {}
676 
677  # Mesh vertex IDs to output vertices array
678  mesh_vertex_map = {}
679 
680  # Iterate over each face to generate kTriangles
681  # vertex data.
682  face_it = om.MItMeshPolygon(obj)
683  while not face_it.isDone():
684  count = len(vertices)
685  num_face_verts = face_it.polygonVertexCount()
686 
687  # Create a vertex id to local face vertex id map
688  # to help us correlate face vertex data.
689  mesh_to_face_vertex_map = {}
690  for i in range(num_face_verts):
691  mesh_vid = face_it.vertexIndex(i)
692  mesh_to_face_vertex_map[mesh_vid] = i
693 
694  # Iterate over each triangle in this face.
695  (tri_pts, tri_ids) = face_it.getTriangles()
696  num_tris = len(tri_ids) // 3
697  for i in range(num_tris):
698  for j in range(3):
699  vid = tri_ids[i*3 + j]
700  local_vid = mesh_to_face_vertex_map[vid]
701  nid = face_it.normalIndex(local_vid)
702 
703  # Check if this vertex data combination (position, normal)
704  # already exists in our vertices list. If so, reuse
705  # previous entry. Otherwise, add it and record it in our
706  # unique_vertices lookup map.
707  vertex_data = (raw_vertices[vid], raw_normals[nid])
708  if vertex_data in unique_vertices:
709  vertex_id = unique_vertices[vertex_data]
710  else:
711  vertex_id = count
712  count += 1
713  unique_vertices[vertex_data] = vertex_id
714  vertices.append(vertex_data[0])
715  normals.append(vertex_data[1])
716 
717  # Record the corresponding vertex_id into our
718  # shaded triangle index list.
719  shaded_indices.append(vertex_id)
720 
721  # Record mesh vertex id to vertices list index
722  # for later wireframe generation.
723  if vid in mesh_vertex_map:
724  mesh_vertex_map[vid].append(vertex_id)
725  else:
726  mesh_vertex_map[vid] = [vertex_id,]
727  next(face_it)
728 
729  # Iterate over each edge to generate wireframe data
730  # in kLines format.
731  edge_it = om.MItMeshEdge(obj)
732  while not edge_it.isDone():
733  mesh_vids = [edge_it.vertexId(0), edge_it.vertexId(1)]
734  vids = [mesh_vertex_map[mesh_vids[0]][0], mesh_vertex_map[mesh_vids[1]][0]]
735  for vid in vids:
736  wire_indices.append(vid)
737  next(edge_it)
738 
739  rep_mesh = helperShapeRepItemMesh()
740  rep_mesh.vertices = vertices
741  rep_mesh.normals = normals
742  rep_mesh.wire_indices = wire_indices
743  rep_mesh.shaded_indices = shaded_indices
744  return rep_mesh
745 
746  def generate_curve(self, obj):
747  '''
748  Given a curve shape MObject, generate a helperShapeRepItemCurve.
749  '''
750  if not obj.apiType() == om.MFn.kNurbsCurve:
751  return None
752  curve_fn = om.MFnNurbsCurve(obj)
753 
754  vertices = []
755  indices = []
756 
757  # Generate vertex positions
758  if curve_fn.degree == 1:
759  # For degree 1 curves, store the CVs.
760  cvs = curve_fn.cvPositions()
761  vertices = [ (v.x, v.y, v.z) for v in cvs ]
762  else:
763  # For degree >= 2 curves, sample the curve.
764  curve_len = curve_fn.length()
765  curve_sample_len = curve_len / (self.curve_samples)
766  curve_len_samples = [ i*curve_sample_len for i in range(self.curve_samples) ]
767  for s in curve_len_samples:
768  param = curve_fn.findParamFromLength(s)
769  pt = curve_fn.getPointAtParam(param)
770  vertices.append((pt.x, pt.y, pt.z))
771 
772  # Generate indices
773  for i in range(1, len(vertices)):
774  indices.append(i-1)
775  indices.append(i)
776  if curve_fn.form == om.MFnNurbsCurve.kClosed or curve_fn.form == om.MFnNurbsCurve.kPeriodic:
777  indices.append(len(vertices)-1)
778  indices.append(0)
779 
780  rep_curve = helperShapeRepItemCurve()
781  rep_curve.vertices = vertices
782  rep_curve.indices = indices
783  return rep_curve
784 
785  def doIt(self, arg_list):
786  ''' Perform the command. '''
787  parser = om.MArgParser(self.syntax(), arg_list)
788 
789  # Force
790  if parser.isFlagSet(self.kForceFlag):
791  self.force = True
792 
793  # Name
794  if not parser.isFlagSet(self.kNameFlag):
795  raise RuntimeError('The -{}/{} flag must be set.'.format(self.kNameFlag, self.kNameFlagLong))
796  self.name = parser.flagArgumentString(self.kNameFlag, 0)
797  if not self.force and not self.valid_name(self.name):
798  raise RuntimeError('The specified name already exists: {}'.format(self.name))
799 
800  # Curve samples
801  if parser.isFlagSet(self.kCurveSamplesFlag):
802  self.curve_samples = parser.flagArgumentInt(self.kCurveSamplesFlag, 0)
803 
804  # Parse the object list or selection for meshes/curves.
805  objects = self.get_objects(parser)
806 
807  # Generate representations from these objects.
808  rep = helperShapeRep()
809  rep.name = self.name
810  for o in objects:
811  if o.apiType() == om.MFn.kMesh:
812  item = self.generate_mesh(o)
813  elif o.apiType() == om.MFn.kNurbsCurve:
814  item = self.generate_curve(o)
815  else:
816  continue
817  if item:
818  rep.items.append(item)
819 
820  # Serialize the representation.
821  if len(rep.items):
822  lib_path = helperShapeNode.shapes_lib_path
823  if not os.path.exists(lib_path):
824  os.makedirs(lib_path)
825  file_name = self.name + '.json'
826  file_path = os.path.join(lib_path, file_name)
827  rep.serialize(file_path)
828 
829 
830 ###############################################################################
831 #
832 # helperShapeNode (MPxLocatorNode)
833 #
834 class helperShapeNode(omui.MPxLocatorNode):
835  ''' helperShapeNode locator class. '''
836  name = 'locatorHelperShape'
837  id = om.MTypeId( 0x00080041 )
838  drawDbClassification = 'drawdb/subscene/locatorHelperShape'
839  drawRegistrantId = 'locatorHelperShape_SubSceneOverride'
840 
841  aShape = om.MObject()
842  aColor = om.MObject()
843  aDrawOnTop = om.MObject()
844 
845  shapes_lib_path = None
846  shapes = []
847 
848  attrEditorTemplate = '''
849  global proc AElocatorHelperShapeColorNew(string $plug) {
850  AElocatorHelperShapeColorReplace($plug);
851  }
852 
853  global proc AElocatorHelperShapeColorReplace(string $plug) {
854  setUITemplate -pst attributeEditorTemplate;
855  string $parent = `setParent -q`;
856  string $frame = $parent + "|locatorHelperShapeColorFrame";
857  if (`frameLayout -ex $frame`) {
858  deleteUI $frame;
859  }
860  $frame = `frameLayout -l "Colors" -collapse false locatorHelperShapeColorFrame`;
861  string $column = `columnLayout -adj true`;
862  int $colorIds[] = python( "list(set(maya.cmds.getAttr(\\"" + $plug + "\\", mi=True)))" );
863  for ($id in $colorIds) {
864  string $index = "[" + $id + "]";
865  string $childPlug = $plug + $index;
866  string $childName = "color" + $index;
867  attrColorSliderGrp -l $childName -attribute $childPlug;
868  }
869  setUITemplate -ppt;
870  }
871 
872  global proc AElocatorHelperShapeTemplate(string $node) {
873  editorTemplate -beginScrollLayout;
874  editorTemplate -beginLayout "Shape Attributes" -collapse 0;
875  editorTemplate -addControl "shape";
876  editorTemplate -addControl "drawOnTop";
877  editorTemplate -callCustom "AElocatorHelperShapeColorNew"
878  "AElocatorHelperShapeColorReplace"
879  "color";
880  editorTemplate -endLayout;
881  editorTemplate -beginLayout "Locator Attributes";
882  AElocatorCommon $node;
883  editorTemplate -endLayout;
884  AElocatorInclude $node;
885  editorTemplate -addExtraControls;
886  editorTemplate -endScrollLayout;
887  }
888  '''
889 
890  def __init__(self):
891  ''' Constructor. '''
892  omui.MPxLocatorNode.__init__(self)
893 
894  def postConstructor(self):
895  ''' Post-constructor. '''
896  enum_attr_fn = om.MFnEnumAttribute(self.aShape)
897  shape_default = enum_attr_fn.default
898  shape_plug = om.MPlug(self.thisMObject(), self.aShape)
899  shape_plug.setShort(shape_default)
900 
901  @staticmethod
902  def creator():
903  ''' Creator method. '''
904  return helperShapeNode()
905 
906  @classmethod
907  def initialize(cls):
908  ''' Initialize node attribute layout. '''
909  num_attr_fn = om.MFnNumericAttribute()
910  enum_attr_fn = om.MFnEnumAttribute()
911 
912  default_index = 0
913  cls.aShape = enum_attr_fn.create('shape', 'sh')
914  for (i, shape) in enumerate(cls.shapes):
915  enum_attr_fn.addField(shape.name, i)
916  if shape.name == 'default':
917  default_index = i
918  enum_attr_fn.default = default_index
919  enum_attr_fn.internal = True
920 
921  cls.aColor = num_attr_fn.createColor('color', 'cl')
922  num_attr_fn.default = DEFAULT_COLOR
923  num_attr_fn.array = True
924 
925  cls.aDrawOnTop = num_attr_fn.create('drawOnTop', 'dot', om.MFnNumericData.kBoolean, False)
926 
927  cls.addAttribute( cls.aShape )
928  cls.addAttribute( cls.aColor )
929  cls.addAttribute( cls.aDrawOnTop )
930 
931  def compute(self, plug, data):
932  ''' Compute method. '''
933  return None
934 
935  def getShapeSelectionMask(self):
936  ''' helperShape selection mask. '''
937  return om.MSelectionMask(HELPER_SHAPE_SELECTION_MASK)
938 
939  def setInternalValueInContext(self, plug, data_handle, ctx):
940  ''' Callback to set internal attribute values. '''
941  # Use set internal callback to detect when the shape changes
942  # and update the number of color fields accordingly.
943  if plug == self.aShape:
944  # Persist color[0] between shapes.
945  color_plug = om.MPlug(self.thisMObject(), self.aColor)
946  base_color_plug = color_plug.elementByLogicalIndex(0)
947  base_color_obj = base_color_plug.asMObject()
948  base_color = om.MFnNumericData(base_color_obj).getData()
949 
950  shape_id = data_handle.asShort()
951  num_shape_items = len(self.shapes[shape_id].items)
952  data_block = self.forceCache()
953  color_builder = om.MArrayDataBuilder(data_block, self.aColor, num_shape_items)
954  for i in range(num_shape_items):
955  child = color_builder.addLast()
956  child.set3Float(base_color[0], base_color[1], base_color[2])
957 
958  color_ovr_hnd = data_block.outputArrayValue(self.aColor)
959  color_ovr_hnd.set(color_builder)
960 
961  # Force an AE refresh.
962  mel.eval('autoUpdateAttrEd;')
963  return False
964 
965  @classmethod
966  def init_shapes(cls, lib_path):
967  '''
968  Populate the list of helperShape representations from
969  the helperShape library path.
970 
971  This must be called prior to node registration since
972  the enumeration attribute to select the active shape
973  depends on the list of shapes.
974  '''
975  # Record lib_path
976  cls.shapes_lib_path = lib_path
977 
978  # Find all *.json files in lib_path
979  shape_files = []
980  if os.path.exists(lib_path):
981  for (root, dirs, files) in os.walk(lib_path):
982  for f in files:
983  (name, ext) = os.path.splitext(f)
984  if ext == '.json':
985  file_path = os.path.join(root, f)
986  shape_files.append(file_path)
987 
988  cls.shapes = []
989  if len(shape_files):
990  for f in shape_files:
991  rep = helperShapeRep()
992  rep.deserialize(f)
993  cls.shapes.append(rep)
994  else:
995  cls.shapes.append(helperShapeRepDefault())
996 
997 
998 ###############################################################################
999 #
1000 # helperShapeSubSceneOverride (MPxSubSceneOverride)
1001 #
1002 class helperShapeShaderItem(object):
1003  ''' helperShape shader class to help uniquely identify shaders. '''
1004  def __init__(self, shader_type, shader_color, transparent, pre_cb, post_cb):
1005  self.type = shader_type
1006  self.color = shader_color
1007  self.transparent = transparent
1008  self.pre_cb = pre_cb
1009  self.post_cb = post_cb
1010 
1011  def __eq__(self, other):
1012  return (isinstance(other, helperShapeShaderItem) and
1013  self.type == other.type and
1014  isclose_tuple(self.color, other.color) and
1015  self.transparent == other.transparent and
1016  self.pre_cb == other.pre_cb and
1017  self.post_cb == other.post_cb)
1018 
1019  def __ne__(self, other):
1020  return not(self == other)
1021 
1022  def __key(self):
1023  return (self.type, self.color, self.transparent, self.pre_cb, self.post_cb)
1024 
1025  def __hash__(self):
1026  return hash(self.__key())
1027 
1028 class helperShapeShaderCache(object):
1029  ''' helperShape cache of shader instances '''
1030  def __init__(self):
1031  ''' Constructor. Initialize the shader cache. '''
1032  self.cache = {}
1033 
1034  def __del__(self):
1035  ''' Destructor. Clear the shader cache. '''
1036  shader_mgr = omr.MRenderer.getShaderManager()
1037  if not shader_mgr:
1038  return
1039  for (_, shader) in list(self.cache.items()):
1040  shader_mgr.releaseShader(shader)
1041  self.cache = {}
1042 
1043  def get_wire_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1044  ''' Return a wire shader with the given parameters from the cache. '''
1045  shader_mgr = omr.MRenderer.getShaderManager()
1046  if not shader_mgr:
1047  return None
1048  shader_type = omr.MShaderManager.k3dThickLineShader
1049  item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1050  if not item in self.cache:
1051  shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1052  shader.setParameter('solidColor', shader_color)
1053  shader.setParameter('lineWidth', [DEFAULT_LINE_WIDTH]*2)
1054  shader.setIsTransparent(transparent)
1055  self.cache[item] = shader
1056  return self.cache[item]
1057 
1058  def get_shaded_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1059  ''' Return a shaded shader with the given parameters from the cache. '''
1060  shader_mgr = omr.MRenderer.getShaderManager()
1061  if not shader_mgr:
1062  return None
1063  shader_type = omr.MShaderManager.k3dBlinnShader
1064  item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1065  if not item in self.cache:
1066  shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1067  shader.setParameter('diffuseColor', shader_color)
1068  shader.setIsTransparent(transparent)
1069  self.cache[item] = shader
1070  return self.cache[item]
1071 
1072 class helperShapeSubSceneOverride(omr.MPxSubSceneOverride):
1073  ''' helperShape viewport subscene override class. '''
1074  kUpdateShaders = 1 << 0
1075  kUpdateGeometry = 1 << 1
1076  kUpdateMatrix = 1 << 2
1077  kUpdateAll = 0xf
1078 
1079  sLeadName = 'lead'
1080  sActiveName = 'active'
1081  sWireName = 'wire'
1082  sShadedName = 'shaded'
1083 
1084  shader_cache = helperShapeShaderCache()
1085 
1086  saved_depth_state = None
1087 
1088  def __init__(self, obj):
1089  ''' Constructor. '''
1090  omr.MPxSubSceneOverride.__init__(self, obj)
1091 
1092  node_fn = om.MFnDependencyNode(obj)
1093  self.node = node_fn.userNode()
1094  self.shape_index = 0
1095  self.shape_colors = []
1096  self.lead_color = (0.0, 0.0, 0.0, 1.0)
1097  self.active_color = (0.0, 0.0, 0.0, 1.0)
1098  self.draw_on_top = False
1099 
1100  self.wire_shaders = []
1101  self.shaded_shaders = []
1102  self.lead_select_shader = None
1103  self.active_select_shader = None
1104 
1105  self.update_mask = self.kUpdateAll
1106 
1107  # Cached instance data (transform_matrix, display_status)
1108  # keyed by instance number.
1109  self.instances = {}
1110 
1111  # Cached lists of render item names sorted by display_status
1112  self.lead_items = []
1113  self.active_items = []
1114  self.wire_items = []
1115  self.shaded_items = []
1116 
1117  @staticmethod
1118  def creator(obj):
1119  ''' Creator method. '''
1120  return helperShapeSubSceneOverride(obj)
1121 
1122  def supportedDrawAPIs(self):
1123  ''' Return the draw APIs supported by this override. '''
1124  return omr.MRenderer.kOpenGL | omr.MRenderer.kDirectX11 | omr.MRenderer.kOpenGLCoreProfile
1125 
1126  def hasUIDrawables(self):
1127  ''' Does this override have UI drawables? '''
1128  return False
1129 
1130  @classmethod
1131  def cleanup(cls):
1132  ''' Cleanup our shader cache. '''
1133  del(cls.shader_cache)
1134  cls.shader_cache = None
1135 
1136  @classmethod
1137  def pre_shader_cb(cls, context, render_items, shader):
1138  ''' Pre shader render callback. Used to facilitate drawOnTop feature. '''
1139  if len(render_items) > 0:
1140  state_mgr = context.getStateManager()
1141  cls.saved_depth_state = state_mgr.getDepthStencilState()
1142  depth_state_desc = omr.MDepthStencilStateDesc()
1143  depth_state_desc.depthEnable = False
1144  disabled_depth_state = state_mgr.acquireDepthStencilState(depth_state_desc)
1145  state_mgr.setDepthStencilState(disabled_depth_state)
1146 
1147  @classmethod
1148  def post_shader_cb(cls, context, render_items, shader):
1149  ''' Post shader render callback. Used to facilitate drawOnTop feature. '''
1150  if len(render_items) > 0:
1151  state_mgr = context.getStateManager()
1152  state_mgr.setDepthStencilState(cls.saved_depth_state)
1153  state_mgr.releaseDepthStencilState(cls.saved_depth_state)
1154  cls.saved_depth_state = None
1155 
1156  def requiresUpdate(self, container, frame_context):
1157  ''' Returns true if the update function should be called. '''
1158  self.requires_update_shaders()
1159  self.requires_update_geometry()
1160  self.requires_update_matrix()
1161  return self.update_mask > 0
1162 
1163  def requires_update_shaders(self):
1164  '''
1165  Checks if any attributes have changed that would affect
1166  this override's shaders. If so, sets the kUpdateShaders
1167  update bit.
1168  '''
1169  shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1170  shape_id = shape_plug.asShort()
1171  num_shapes = len(helperShapeNode.shapes[shape_id].items)
1172 
1173  old_colors = self.shape_colors
1174  new_colors = []
1175  color_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aColor)
1176  for i in range(num_shapes):
1177  color_elem = color_plug.elementByLogicalIndex(i)
1178  color_obj = color_elem.asMObject()
1179  num_data_fn = om.MFnNumericData(color_obj)
1180  new_color = num_data_fn.getData()
1181  # The stock shaders expect color as float4.
1182  new_colors.append( (new_color[0], new_color[1], new_color[2], 1.0) )
1183  self.shape_colors = new_colors
1184 
1185  # Check for changes to color values / num colors
1186  if len(old_colors) != len(new_colors):
1187  self.update_mask |= self.kUpdateShaders
1188  else:
1189  for (o,n) in zip(old_colors, new_colors):
1190  if not isclose_tuple(o, n):
1191  self.update_mask |= self.kUpdateShaders
1192  break
1193 
1194  # Check for changes to lead/active colors
1195  old_lead_color = self.lead_color
1196  old_active_color = self.active_color
1197  view = omui.M3dView()
1198  lead_color_index = cmds.displayColor('lead', q=True, active=True) - 1
1199  lead_color = view.colorAtIndex( lead_color_index, omui.M3dView.kActiveColors )
1200  self.lead_color = (lead_color.r, lead_color.g, lead_color.b, lead_color.a)
1201 
1202  active_color_index = cmds.displayColor('active', q=True, active=True) - 1
1203  active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1204  self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1205 
1206  old_sel_colors = (old_lead_color, old_active_color)
1207  new_sel_colors = (self.lead_color, self.active_color)
1208  for (o, n) in zip(old_sel_colors, new_sel_colors):
1209  if not isclose_tuple(o, n):
1210  self.update_mask |= self.kUpdateShaders
1211  break
1212 
1213  # Check drawOnTop changes
1214  draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1215  old_draw_on_top = self.draw_on_top
1216  self.draw_on_top = draw_on_top_plug.asBool()
1217  if self.draw_on_top != old_draw_on_top:
1218  self.update_mask |= self.kUpdateShaders
1219 
1220  def requires_update_geometry(self):
1221  '''
1222  Checks if any attributes have changed that would affect
1223  the geometry of any render items. If so, sets the kUpdateGeometry
1224  and kUpdateMatrix update bits.
1225  '''
1226  shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1227  old_shape_index = self.shape_index
1228  self.shape_index = shape_plug.asInt()
1229  if self.shape_index >= len(helperShapeNode.shapes):
1230  self.shape_index = old_shape_index
1231  if not self.shape_index == old_shape_index:
1232  old_shape = helperShapeNode.shapes[old_shape_index]
1233  for item in old_shape.items:
1234  item.clear_buffers()
1235 
1236  # If geometry is requires update, then render items
1237  # will be rebuilt and matrices will also require
1238  # updates.
1239  self.update_mask |= self.kUpdateGeometry
1240  self.update_mask |= self.kUpdateMatrix
1241 
1242  def requires_update_matrix(self):
1243  '''
1244  Checks if any attributes have changed that would affect
1245  the matrices of any render items. If so, sets the
1246  kUpdateMatrix update bit.
1247  '''
1248  old_instances = self.instances
1249 
1250  # Update cached instance data.
1251  dag_fn = om.MFnDagNode(self.node.thisMObject())
1252  paths = dag_fn.getAllPaths()
1253  self.instances = {}
1254  for path in paths:
1255  if not path.isVisible():
1256  continue
1257  matrix = path.inclusiveMatrix()
1258  display_status = omr.MGeometryUtilities.displayStatus(path)
1259  self.instances[path.instanceNumber()] = (matrix, display_status)
1260 
1261  # Check for changes in total number of instances.
1262  old_instance_nums = set(old_instances.keys())
1263  new_instance_nums = set(self.instances.keys())
1264  instance_num_diff = old_instance_nums ^ new_instance_nums
1265  if len(instance_num_diff):
1266  # If the number of instances changes, rebuild all items.
1267  self.update_mask |= self.kUpdateGeometry
1268  self.update_mask |= self.kUpdateMatrix
1269  return
1270 
1271  # Check for changes in matrix or display_status.
1272  for i in list(self.instances.keys()):
1273  (old_matrix, old_display) = old_instances[i]
1274  (new_matrix, new_display) = self.instances[i]
1275 
1276  if old_display != new_display:
1277  self.update_mask |= self.kUpdateMatrix
1278  return
1279  if not old_matrix.isEquivalent(new_matrix):
1280  self.update_mask |= self.kUpdateMatrix
1281  return
1282 
1283  def update(self, container, frame_context):
1284  '''
1285  Updates only the portions of the override that require
1286  updating.
1287  '''
1288  if self.update_mask & self.kUpdateShaders:
1289  self.update_shaders()
1290  if self.update_mask & self.kUpdateGeometry:
1291  self.update_geometry(container, frame_context)
1292  if self.update_mask & self.kUpdateMatrix:
1293  self.update_matrix(container, frame_context)
1294  if self.update_mask & self.kUpdateShaders and not self.update_mask & self.kUpdateGeometry:
1295  self.update_shaders_assign(container)
1296  self.update_mask = 0
1297 
1298  def update_shaders(self):
1299  '''
1300  Initialize shaders if uninitialized and set their
1301  parameters (ex. color)
1302  '''
1303  shader_mgr = omr.MRenderer.getShaderManager()
1304  if not shader_mgr:
1305  return
1306 
1307  if self.draw_on_top:
1308  pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1309  post_cb = helperShapeSubSceneOverride.post_shader_cb
1310  else:
1311  pre_cb = None
1312  post_cb = None
1313 
1314  num_colors = len(self.shape_colors)
1315  self.wire_shaders = []
1316  self.shaded_shaders = []
1317  for i in range(num_colors):
1318  shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1319  self.wire_shaders.append(shader)
1320  for i in range(num_colors):
1321  shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1322  self.shaded_shaders.append(shader)
1323  self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1324  self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1325 
1326  def update_shaders_assign(self, container):
1327  '''
1328  Update shader assignments on geometry items.
1329 
1330  This is required for the case when shaders require updating,
1331  but geometry does not. In these cases we must update the
1332  assigned shaders on the existing render items.
1333 
1334  A separate routine is required for assignment so that both
1335  shaders and render item lists are properly initialized prior
1336  to assignment.
1337  '''
1338  for (i, item_name) in self.wire_items:
1339  item = container.find(item_name)
1340  shader = self.wire_shaders[i]
1341  if not shader is item.getShader():
1342  item.setShader(shader)
1343  for (i, item_name) in self.shaded_items:
1344  item = container.find(item_name)
1345  shader = self.shaded_shaders[i]
1346  if not shader is item.getShader():
1347  item.setShader(shader)
1348  for (_, item_name) in self.lead_items:
1349  item = container.find(item_name)
1350  if not shader is item.getShader():
1351  item.setShader(self.lead_select_shader)
1352  for (_, item_name) in self.active_items:
1353  item = container.find(item_name)
1354  if not shader is item.getShader():
1355  item.setShader(self.active_select_shader)
1356 
1357  def update_geometry(self, container, frame_context):
1358  '''
1359  Update the subscene container with the render items to draw the shape.
1360  This method regenerates the render items from a clean slate.
1361  '''
1362  container.clear()
1363 
1364  # For later searches into the container, record the names
1365  # of each render item by its selection status.
1366  self.lead_items = []
1367  self.active_items = []
1368  self.wire_items = []
1369  self.shaded_items = []
1370 
1371  active_shape = helperShapeNode.shapes[self.shape_index]
1372  for (i,item) in enumerate(active_shape.items):
1373  bounds = item.bounding_box()
1374 
1375  # Shared render items (wire, lead, active)
1376  vertex_buffer = item.vertex_buffer()
1377  wire_index_buffer = item.wire_index_buffer()
1378 
1379  wire_name = '_'.join((active_shape.name, str(i), self.sWireName))
1380  wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1381  wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1382  if item.type == helperShapeRepItemType.kMesh:
1383  # Mesh wireframe only visible in wireframe mode.
1384  wire_item.setDrawMode(omr.MGeometry.kWireframe)
1385  wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1386  wire_item.setShader(self.wire_shaders[i])
1387  container.add(wire_item)
1388  self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1389  self.wire_items.append((i, wire_name))
1390 
1391  lead_name = '_'.join((active_shape.name, str(i), self.sLeadName))
1392  lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1393  lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1394  lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1395  lead_item.setShader(self.lead_select_shader)
1396  container.add(lead_item)
1397  self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1398  self.lead_items.append((i, lead_name))
1399 
1400  active_name = '_'.join((active_shape.name, str(i), self.sActiveName))
1401  active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1402  active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1403  active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1404  active_item.setShader(self.active_select_shader)
1405  container.add(active_item)
1406  self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1407  self.active_items.append((i, active_name))
1408 
1409  # Type specific render items (shaded)
1410  if item.type == helperShapeRepItemType.kMesh:
1411  shaded_index_buffer = item.shaded_index_buffer()
1412  shaded_name = '_'.join((active_shape.name, str(i), self.sShadedName))
1413  shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1414  shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1415  shaded_item.setExcludedFromPostEffects(True)
1416  shaded_item.setCastsShadows(False)
1417  shaded_item.setReceivesShadows(False)
1418  shaded_item.setShader(self.shaded_shaders[i])
1419  container.add(shaded_item)
1420  self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1421  self.shaded_items.append((i, shaded_name))
1422 
1423  def update_matrix(self, container, frame_context):
1424  '''
1425  Updates the matrices of the render items in the container.
1426  '''
1427  # Sort items by display status.
1428  num_instances = len(self.instances)
1429  lead_instances = om.MMatrixArray()
1430  active_instances = om.MMatrixArray()
1431  dormant_instances = om.MMatrixArray()
1432 
1433  for (_, instance) in list(self.instances.items()):
1434  (matrix, display_status) = instance
1435  if display_status == omr.MGeometryUtilities.kLead:
1436  lead_instances.append(matrix)
1437  elif display_status == omr.MGeometryUtilities.kActive:
1438  active_instances.append(matrix)
1439  # Always display dormant items
1440  dormant_instances.append(matrix)
1441 
1442  dormant_items = []
1443  dormant_items.extend(self.wire_items)
1444  dormant_items.extend(self.shaded_items)
1445 
1446  if num_instances == 0:
1447  # Disable all render items
1448  #
1449  # BUG: The API docs and function signature suggest this
1450  # expects only the self argument, but it errors expecting
1451  # 1 argument. Pass in a dummy value of 0 as a workaround.
1452  container_it = container.getIterator(0)
1453  item = next(container_it)
1454  while item is not None:
1455  item.enable(False)
1456  item = next(container_it)
1457  container_it.destroy()
1458  container_it = None
1459  elif num_instances == 1:
1460  # Set our render items directly.
1461  matrix = dormant_instances[0]
1462  for (_, item_name) in dormant_items:
1463  item = container.find(item_name)
1464  item.enable(True)
1465  item.setMatrix(matrix)
1466  if WANT_CONSOLIDATION:
1467  item.setWantSubSceneConsolidation(True)
1468  for (_, item_name) in self.lead_items:
1469  item = container.find(item_name)
1470  item.enable(len(lead_instances) > 0)
1471  item.setMatrix(matrix)
1472  if WANT_CONSOLIDATION:
1473  item.setWantSubSceneConsolidation(True)
1474  for (_, item_name) in self.active_items:
1475  item = container.find(item_name)
1476  item.enable(len(active_instances) > 0)
1477  item.setMatrix(matrix)
1478  if WANT_CONSOLIDATION:
1479  item.setWantSubSceneConsolidation(True)
1480  else:
1481  # Set instance transforms
1482  for (_, item_name) in dormant_items:
1483  item = container.find(item_name)
1484  item.enable(True)
1485  self.setInstanceTransformArray(item, dormant_instances)
1486  has_lead_instances = len(lead_instances) > 0
1487  for (_, item_name) in self.lead_items:
1488  item = container.find(item_name)
1489  item.enable(has_lead_instances)
1490  if has_lead_instances:
1491  self.setInstanceTransformArray(item, lead_instances)
1492  has_active_instances = len(active_instances) > 0
1493  for (_, item_name) in self.active_items:
1494  item = container.find(item_name)
1495  item.enable(has_active_instances)
1496  if has_active_instances:
1497  self.setInstanceTransformArray(item, active_instances)
1498 
1499 
1500 ###############################################################################
1501 #
1502 # helperShape plug-in initialize/uninitialize
1503 #
1504 def initializePlugin(obj):
1505  plugin = om.MFnPlugin( obj )
1506 
1507  plugin_path = os.path.normpath(plugin.loadPath())
1508  lib_path = os.path.join(plugin_path, 'library')
1509  lib_env_var = 'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1510  if lib_env_var in os.environ:
1511  env_path = os.environ[lib_env_var]
1512  lib_path = os.path.normpath(env_path)
1513  helperShapeNode.init_shapes(lib_path)
1514 
1515  try:
1516  plugin.registerNode(helperShapeNode.name,
1517  helperShapeNode.id,
1518  helperShapeNode.creator,
1519  helperShapeNode.initialize,
1520  om.MPxNode.kLocatorNode,
1521  helperShapeNode.drawDbClassification)
1522  except:
1523  sys.stderr.write('Failed to register locatorHelperShape node.\n')
1524  raise
1525 
1526  try:
1527  mel.eval(helperShapeNode.attrEditorTemplate)
1528  except:
1529  sys.stderr.write('Failed to load locatorHelperShape AETemplate script.\n')
1530  raise
1531 
1532  try:
1533  omr.MDrawRegistry.registerSubSceneOverrideCreator(
1534  helperShapeNode.drawDbClassification,
1535  helperShapeNode.drawRegistrantId,
1536  helperShapeSubSceneOverride.creator)
1537  except:
1538  sys.stderr.write('Failed to register locatorHelperShape SubSceneOverride.\n')
1539  raise
1540 
1541  try:
1542  plugin.registerCommand(helperShapeExportCmd.name,
1543  helperShapeExportCmd.creator,
1544  helperShapeExportCmd.new_syntax)
1545  except:
1546  sys.stderr.write('Failed to register locatorHelperShapeExportCmd.\n')
1547  raise
1548 
1549  # Register helperShape selection mask
1550  om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1551  cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK, True))
1552 
1553 def uninitializePlugin(obj):
1554  plugin = om.MFnPlugin( obj )
1555 
1556  try:
1557  plugin.deregisterNode(helperShapeNode.id)
1558  except:
1559  sys.stderr.write('Failed to deregister locatorHelperShape node.\n')
1560  pass
1561 
1562  try:
1563  omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1564  helperShapeNode.drawDbClassification,
1565  helperShapeNode.drawRegistrantId)
1566  except:
1567  sys.stderr.write('Failed to deregister locatorHelperShape SubSceneOverride.\n')
1568  pass
1569 
1570  try:
1571  plugin.deregisterCommand(helperShapeExportCmd.name)
1572  except:
1573  sys.stderr.write('Failed to deregister locatorHelperShapeExportCmd.\n')
1574  pass
1575 
1576  # Deregister helperShape selection mask
1577  om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)
1578