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 = cmds.displayRGBColor('lead', q=True, alpha=True)
1199  self.lead_color = (lead_color[0], lead_color[1], lead_color[2], lead_color[3])
1200 
1201  active_color_index = cmds.displayColor('active', q=True, active=True) - 1
1202  active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1203  self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1204 
1205  old_sel_colors = (old_lead_color, old_active_color)
1206  new_sel_colors = (self.lead_color, self.active_color)
1207  for (o, n) in zip(old_sel_colors, new_sel_colors):
1208  if not isclose_tuple(o, n):
1209  self.update_mask |= self.kUpdateShaders
1210  break
1211 
1212  # Check drawOnTop changes
1213  draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1214  old_draw_on_top = self.draw_on_top
1215  self.draw_on_top = draw_on_top_plug.asBool()
1216  if self.draw_on_top != old_draw_on_top:
1217  self.update_mask |= self.kUpdateShaders
1218 
1219  def requires_update_geometry(self):
1220  '''
1221  Checks if any attributes have changed that would affect
1222  the geometry of any render items. If so, sets the kUpdateGeometry
1223  and kUpdateMatrix update bits.
1224  '''
1225  shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1226  old_shape_index = self.shape_index
1227  self.shape_index = shape_plug.asInt()
1228  if self.shape_index >= len(helperShapeNode.shapes):
1229  self.shape_index = old_shape_index
1230  if not self.shape_index == old_shape_index:
1231  old_shape = helperShapeNode.shapes[old_shape_index]
1232  for item in old_shape.items:
1233  item.clear_buffers()
1234 
1235  # If geometry is requires update, then render items
1236  # will be rebuilt and matrices will also require
1237  # updates.
1238  self.update_mask |= self.kUpdateGeometry
1239  self.update_mask |= self.kUpdateMatrix
1240 
1241  def requires_update_matrix(self):
1242  '''
1243  Checks if any attributes have changed that would affect
1244  the matrices of any render items. If so, sets the
1245  kUpdateMatrix update bit.
1246  '''
1247  old_instances = self.instances
1248 
1249  # Update cached instance data.
1250  dag_fn = om.MFnDagNode(self.node.thisMObject())
1251  paths = dag_fn.getAllPaths()
1252  self.instances = {}
1253  for path in paths:
1254  if not path.isVisible():
1255  continue
1256  matrix = path.inclusiveMatrix()
1257  display_status = omr.MGeometryUtilities.displayStatus(path)
1258  self.instances[path.instanceNumber()] = (matrix, display_status)
1259 
1260  # Check for changes in total number of instances.
1261  old_instance_nums = set(old_instances.keys())
1262  new_instance_nums = set(self.instances.keys())
1263  instance_num_diff = old_instance_nums ^ new_instance_nums
1264  if len(instance_num_diff):
1265  # If the number of instances changes, rebuild all items.
1266  self.update_mask |= self.kUpdateGeometry
1267  self.update_mask |= self.kUpdateMatrix
1268  return
1269 
1270  # Check for changes in matrix or display_status.
1271  for i in list(self.instances.keys()):
1272  (old_matrix, old_display) = old_instances[i]
1273  (new_matrix, new_display) = self.instances[i]
1274 
1275  if old_display != new_display:
1276  self.update_mask |= self.kUpdateMatrix
1277  return
1278  if not old_matrix.isEquivalent(new_matrix):
1279  self.update_mask |= self.kUpdateMatrix
1280  return
1281 
1282  def update(self, container, frame_context):
1283  '''
1284  Updates only the portions of the override that require
1285  updating.
1286  '''
1287  if self.update_mask & self.kUpdateShaders:
1288  self.update_shaders()
1289  if self.update_mask & self.kUpdateGeometry:
1290  self.update_geometry(container, frame_context)
1291  if self.update_mask & self.kUpdateMatrix:
1292  self.update_matrix(container, frame_context)
1293  if self.update_mask & self.kUpdateShaders and not self.update_mask & self.kUpdateGeometry:
1294  self.update_shaders_assign(container)
1295  self.update_mask = 0
1296 
1297  def update_shaders(self):
1298  '''
1299  Initialize shaders if uninitialized and set their
1300  parameters (ex. color)
1301  '''
1302  shader_mgr = omr.MRenderer.getShaderManager()
1303  if not shader_mgr:
1304  return
1305 
1306  if self.draw_on_top:
1307  pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1308  post_cb = helperShapeSubSceneOverride.post_shader_cb
1309  else:
1310  pre_cb = None
1311  post_cb = None
1312 
1313  num_colors = len(self.shape_colors)
1314  self.wire_shaders = []
1315  self.shaded_shaders = []
1316  for i in range(num_colors):
1317  shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1318  self.wire_shaders.append(shader)
1319  for i in range(num_colors):
1320  shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1321  self.shaded_shaders.append(shader)
1322  self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1323  self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1324 
1325  def update_shaders_assign(self, container):
1326  '''
1327  Update shader assignments on geometry items.
1328 
1329  This is required for the case when shaders require updating,
1330  but geometry does not. In these cases we must update the
1331  assigned shaders on the existing render items.
1332 
1333  A separate routine is required for assignment so that both
1334  shaders and render item lists are properly initialized prior
1335  to assignment.
1336  '''
1337  for (i, item_name) in self.wire_items:
1338  item = container.find(item_name)
1339  shader = self.wire_shaders[i]
1340  if not shader is item.getShader():
1341  item.setShader(shader)
1342  for (i, item_name) in self.shaded_items:
1343  item = container.find(item_name)
1344  shader = self.shaded_shaders[i]
1345  if not shader is item.getShader():
1346  item.setShader(shader)
1347  for (_, item_name) in self.lead_items:
1348  item = container.find(item_name)
1349  if not shader is item.getShader():
1350  item.setShader(self.lead_select_shader)
1351  for (_, item_name) in self.active_items:
1352  item = container.find(item_name)
1353  if not shader is item.getShader():
1354  item.setShader(self.active_select_shader)
1355 
1356  def update_geometry(self, container, frame_context):
1357  '''
1358  Update the subscene container with the render items to draw the shape.
1359  This method regenerates the render items from a clean slate.
1360  '''
1361  container.clear()
1362 
1363  # For later searches into the container, record the names
1364  # of each render item by its selection status.
1365  self.lead_items = []
1366  self.active_items = []
1367  self.wire_items = []
1368  self.shaded_items = []
1369 
1370  active_shape = helperShapeNode.shapes[self.shape_index]
1371  for (i,item) in enumerate(active_shape.items):
1372  bounds = item.bounding_box()
1373 
1374  # Shared render items (wire, lead, active)
1375  vertex_buffer = item.vertex_buffer()
1376  wire_index_buffer = item.wire_index_buffer()
1377 
1378  wire_name = '_'.join((active_shape.name, str(i), self.sWireName))
1379  wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1380  wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1381  if item.type == helperShapeRepItemType.kMesh:
1382  # Mesh wireframe only visible in wireframe mode.
1383  wire_item.setDrawMode(omr.MGeometry.kWireframe)
1384  wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1385  wire_item.setShader(self.wire_shaders[i])
1386  container.add(wire_item)
1387  self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1388  self.wire_items.append((i, wire_name))
1389 
1390  lead_name = '_'.join((active_shape.name, str(i), self.sLeadName))
1391  lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1392  lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1393  lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1394  lead_item.setShader(self.lead_select_shader)
1395  container.add(lead_item)
1396  self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1397  self.lead_items.append((i, lead_name))
1398 
1399  active_name = '_'.join((active_shape.name, str(i), self.sActiveName))
1400  active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1401  active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1402  active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1403  active_item.setShader(self.active_select_shader)
1404  container.add(active_item)
1405  self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1406  self.active_items.append((i, active_name))
1407 
1408  # Type specific render items (shaded)
1409  if item.type == helperShapeRepItemType.kMesh:
1410  shaded_index_buffer = item.shaded_index_buffer()
1411  shaded_name = '_'.join((active_shape.name, str(i), self.sShadedName))
1412  shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1413  shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1414  shaded_item.setExcludedFromPostEffects(True)
1415  shaded_item.setCastsShadows(False)
1416  shaded_item.setReceivesShadows(False)
1417  shaded_item.setShader(self.shaded_shaders[i])
1418  container.add(shaded_item)
1419  self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1420  self.shaded_items.append((i, shaded_name))
1421 
1422  def update_matrix(self, container, frame_context):
1423  '''
1424  Updates the matrices of the render items in the container.
1425  '''
1426  # Sort items by display status.
1427  num_instances = len(self.instances)
1428  lead_instances = om.MMatrixArray()
1429  active_instances = om.MMatrixArray()
1430  dormant_instances = om.MMatrixArray()
1431 
1432  for (_, instance) in list(self.instances.items()):
1433  (matrix, display_status) = instance
1434  if display_status == omr.MGeometryUtilities.kLead:
1435  lead_instances.append(matrix)
1436  elif display_status == omr.MGeometryUtilities.kActive:
1437  active_instances.append(matrix)
1438  # Always display dormant items
1439  dormant_instances.append(matrix)
1440 
1441  dormant_items = []
1442  dormant_items.extend(self.wire_items)
1443  dormant_items.extend(self.shaded_items)
1444 
1445  if num_instances == 0:
1446  # Disable all render items
1447  #
1448  # BUG: The API docs and function signature suggest this
1449  # expects only the self argument, but it errors expecting
1450  # 1 argument. Pass in a dummy value of 0 as a workaround.
1451  container_it = container.getIterator(0)
1452  item = next(container_it)
1453  while item is not None:
1454  item.enable(False)
1455  item = next(container_it)
1456  container_it.destroy()
1457  container_it = None
1458  elif num_instances == 1:
1459  # Set our render items directly.
1460  matrix = dormant_instances[0]
1461  for (_, item_name) in dormant_items:
1462  item = container.find(item_name)
1463  item.enable(True)
1464  item.setMatrix(matrix)
1465  if WANT_CONSOLIDATION:
1466  item.setWantSubSceneConsolidation(True)
1467  for (_, item_name) in self.lead_items:
1468  item = container.find(item_name)
1469  item.enable(len(lead_instances) > 0)
1470  item.setMatrix(matrix)
1471  if WANT_CONSOLIDATION:
1472  item.setWantSubSceneConsolidation(True)
1473  for (_, item_name) in self.active_items:
1474  item = container.find(item_name)
1475  item.enable(len(active_instances) > 0)
1476  item.setMatrix(matrix)
1477  if WANT_CONSOLIDATION:
1478  item.setWantSubSceneConsolidation(True)
1479  else:
1480  # Set instance transforms
1481  for (_, item_name) in dormant_items:
1482  item = container.find(item_name)
1483  item.enable(True)
1484  self.setInstanceTransformArray(item, dormant_instances)
1485  has_lead_instances = len(lead_instances) > 0
1486  for (_, item_name) in self.lead_items:
1487  item = container.find(item_name)
1488  item.enable(has_lead_instances)
1489  if has_lead_instances:
1490  self.setInstanceTransformArray(item, lead_instances)
1491  has_active_instances = len(active_instances) > 0
1492  for (_, item_name) in self.active_items:
1493  item = container.find(item_name)
1494  item.enable(has_active_instances)
1495  if has_active_instances:
1496  self.setInstanceTransformArray(item, active_instances)
1497 
1498 
1499 ###############################################################################
1500 #
1501 # helperShape plug-in initialize/uninitialize
1502 #
1503 def initializePlugin(obj):
1504  plugin = om.MFnPlugin( obj )
1505 
1506  plugin_path = os.path.normpath(plugin.loadPath())
1507  lib_path = os.path.join(plugin_path, 'library')
1508  lib_env_var = 'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1509  if lib_env_var in os.environ:
1510  env_path = os.environ[lib_env_var]
1511  lib_path = os.path.normpath(env_path)
1512  helperShapeNode.init_shapes(lib_path)
1513 
1514  try:
1515  plugin.registerNode(helperShapeNode.name,
1516  helperShapeNode.id,
1517  helperShapeNode.creator,
1518  helperShapeNode.initialize,
1519  om.MPxNode.kLocatorNode,
1520  helperShapeNode.drawDbClassification)
1521  except:
1522  sys.stderr.write('Failed to register locatorHelperShape node.\n')
1523  raise
1524 
1525  try:
1526  mel.eval(helperShapeNode.attrEditorTemplate)
1527  except:
1528  sys.stderr.write('Failed to load locatorHelperShape AETemplate script.\n')
1529  raise
1530 
1531  try:
1532  omr.MDrawRegistry.registerSubSceneOverrideCreator(
1533  helperShapeNode.drawDbClassification,
1534  helperShapeNode.drawRegistrantId,
1535  helperShapeSubSceneOverride.creator)
1536  except:
1537  sys.stderr.write('Failed to register locatorHelperShape SubSceneOverride.\n')
1538  raise
1539 
1540  try:
1541  plugin.registerCommand(helperShapeExportCmd.name,
1542  helperShapeExportCmd.creator,
1543  helperShapeExportCmd.new_syntax)
1544  except:
1545  sys.stderr.write('Failed to register locatorHelperShapeExportCmd.\n')
1546  raise
1547 
1548  # Register helperShape selection mask
1549  om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1550  cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK, True))
1551 
1552 def uninitializePlugin(obj):
1553  plugin = om.MFnPlugin( obj )
1554 
1555  try:
1556  plugin.deregisterNode(helperShapeNode.id)
1557  except:
1558  sys.stderr.write('Failed to deregister locatorHelperShape node.\n')
1559  pass
1560 
1561  try:
1562  omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1563  helperShapeNode.drawDbClassification,
1564  helperShapeNode.drawRegistrantId)
1565  except:
1566  sys.stderr.write('Failed to deregister locatorHelperShape SubSceneOverride.\n')
1567  pass
1568 
1569  try:
1570  plugin.deregisterCommand(helperShapeExportCmd.name)
1571  except:
1572  sys.stderr.write('Failed to deregister locatorHelperShapeExportCmd.\n')
1573  pass
1574 
1575  # Deregister helperShape selection mask
1576  om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)
1577