90 import maya.cmds
as cmds
91 import maya.mel
as mel
92 import maya.api.OpenMaya
as om
93 import maya.api.OpenMayaRender
as omr
94 import maya.api.OpenMayaUI
as omui
102 DEFAULT_COLOR = (0.0, 0.7, 0.15)
103 DEFAULT_LINE_WIDTH = 1.0
105 HELPER_SHAPE_SELECTION_MASK =
'locatorHelperShapeSelection'
106 HELPER_SHAPE_SELECTION_PRIORITY = 9
109 ENABLE_CONSOLIDATION =
True
110 consolidation_func = getattr(omr.MRenderItem,
'wantSubSceneConsolidation',
None)
111 SUPPORTS_CONSOLIDATION = callable(consolidation_func)
112 WANT_CONSOLIDATION = SUPPORTS_CONSOLIDATION
and ENABLE_CONSOLIDATION
118 def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
119 return abs(a-b) <= max( rel_tol * max(abs(a), abs(b)), abs_tol )
121 def isclose_tuple(a, b, rel_tol=1e-9, abs_tol=0.0):
122 for (aa, bb)
in zip(a, b):
123 if not isclose(aa, bb):
139 class helperShapeRepItemType():
140 ''' helperShape representation item type definition. '''
144 def __init__(self, index, name):
148 def __eq__(self, other):
149 return self.index == other.index
151 def __ne__(self, other):
152 return not(self == other)
161 helperShapeRepItemType.kMesh = helperShapeRepItemType(0,
'mesh')
162 helperShapeRepItemType.kCurve = helperShapeRepItemType(1,
'curve')
164 class helperShapeRepItem(object):
165 ''' Abstract base class for a helperShape representation item. '''
168 def __init__(self, type):
173 def deserialize_type(cls, data):
174 ''' Deserialize the item type from a Python dictionary '''
175 if data[cls.kTypeKey] == helperShapeRepItemType.kMesh.name:
176 return helperShapeRepItemType.kMesh
177 elif data[cls.kTypeKey] == helperShapeRepItemType.kCurve.name:
178 return helperShapeRepItemType.kCurve
179 raise RuntimeError(
'Invalid helperShapeRepItemType: {}'.format(data[cls.kTypeKey]))
181 def deserialize(self, data):
182 ''' Deserialize the item from a Python dictionary '''
183 raise NotImplementedError
185 def serialize(self, data):
186 ''' Serialize the item to a Python dictionary '''
187 if self.type == helperShapeRepItemType.kMesh:
188 data[self.kTypeKey] = helperShapeRepItemType.kMesh.name
189 elif self.type == helperShapeRepItemType.kCurve:
190 data[self.kTypeKey] = helperShapeRepItemType.kCurve.name
192 def vertex_buffer(self):
193 ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
194 raise NotImplementedError
196 def wire_index_buffer(self):
197 ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
198 raise NotImplementedError
200 def shaded_index_buffer(self):
201 ''' Returns an MIndexBuffer containing the index buffer for shaded draw. '''
202 raise NotImplementedError
204 def clear_buffers(self):
205 ''' Clears cached vertex/index buffers for this item. '''
206 raise NotImplementedError
208 def bounding_box(self):
209 ''' Returns an MBoundingBox for this item. '''
210 raise NotImplementedError
213 def _create_index_buffer(cls, indices):
214 ''' Returns an MIndexBuffer populated with the given indices. '''
215 num_indices = len(indices)
216 buffer = omr.MIndexBuffer(omr.MGeometry.kUnsignedInt32)
217 address = buffer.acquire(num_indices,
True)
218 data = ((ctypes.c_uint)*num_indices).from_address(address)
219 for (i,val)
in enumerate(indices):
221 buffer.commit(address)
225 def _create_vertex_buffer(cls, desc, vertex_data):
226 ''' Returns an MVertexBuffer populated with the given vertex data. '''
227 num_vertices = len(vertex_data)
228 buffer = omr.MVertexBuffer(desc)
229 address = buffer.acquire(num_vertices,
True)
230 if desc.dataType == omr.MGeometry.kFloat
and desc.dimension == 3:
231 data = ((ctypes.c_float * 3)*num_vertices).from_address(address)
232 for (i,val)
in enumerate(vertex_data):
236 buffer.commit(address)
239 raise NotImplementedError
242 class helperShapeRepItemMesh(helperShapeRepItem):
243 ''' Defines a mesh based helperShape representation item. '''
245 kVerticesKey =
'vertices'
246 kNormalsKey =
'normals'
247 kWireIndicesKey =
'wireIndices'
248 kShadedIndicesKey =
'shadedIndices'
252 super(helperShapeRepItemMesh, self).__init__(helperShapeRepItemType.kMesh)
254 self.position_desc = omr.MVertexBufferDescriptor(
'', omr.MGeometry.kPosition, omr.MGeometry.kFloat, 3)
255 self.normal_desc = omr.MVertexBufferDescriptor(
'', omr.MGeometry.kNormal, omr.MGeometry.kFloat, 3)
260 self.wire_indices =
None
261 self.shaded_indices =
None
263 self.position_buffer =
None
264 self.normal_buffer =
None
265 self.index_buffer_wire =
None
266 self.index_buffer_shaded =
None
268 def deserialize(self, data):
269 ''' Deserialize the item from a Python dictionary '''
270 self.vertices = data[self.kVerticesKey]
271 self.normals = data[self.kNormalsKey]
272 self.wire_indices = data[self.kWireIndicesKey]
273 self.shaded_indices = data[self.kShadedIndicesKey]
275 def serialize(self, data):
276 ''' Serialize the item to a Python dictionary '''
277 super(helperShapeRepItemMesh, self).serialize(data)
278 data[self.kVerticesKey] = self.vertices
279 data[self.kNormalsKey] = self.normals
280 data[self.kWireIndicesKey] = self.wire_indices
281 data[self.kShadedIndicesKey] = self.shaded_indices
283 def vertex_buffer(self):
284 ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
285 if not self.position_buffer:
286 self.position_buffer = self._create_vertex_buffer(self.position_desc, self.vertices)
287 if not self.normal_buffer:
288 self.normal_buffer = self._create_vertex_buffer(self.normal_desc, self.normals)
289 vertex_buffer = omr.MVertexBufferArray()
290 vertex_buffer.append(self.position_buffer,
'positions')
291 vertex_buffer.append(self.normal_buffer,
'normals')
294 def wire_index_buffer(self):
295 ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
296 if not self.index_buffer_wire:
297 self.index_buffer_wire = self._create_index_buffer(self.wire_indices)
298 return self.index_buffer_wire
300 def shaded_index_buffer(self):
301 ''' Returns an MIndexBuffer containing the index buffer for shaded draw. '''
302 if not self.index_buffer_shaded:
303 self.index_buffer_shaded = self._create_index_buffer(self.shaded_indices)
304 return self.index_buffer_shaded
306 def clear_buffers(self):
307 ''' Clears cached vertex/index buffers for this item. '''
308 self.position_buffer =
None
309 self.normal_buffer =
None
310 self.index_buffer_wire =
None
311 self.index_buffer_shaded =
None
313 def bounding_box(self):
314 ''' Returns an MBoundingBox for this item. '''
315 box = om.MBoundingBox()
316 for v
in self.vertices:
317 box.expand(om.MPoint(v[0], v[1], v[2]))
320 class helperShapeRepItemCurve(helperShapeRepItem):
321 ''' Defines a curve based helperShape representation item. '''
323 kVerticesKey =
'vertices'
324 kIndicesKey =
'indices'
328 super(helperShapeRepItemCurve, self).__init__(helperShapeRepItemType.kCurve)
330 self.position_desc = omr.MVertexBufferDescriptor(
'', omr.MGeometry.kPosition, omr.MGeometry.kFloat, 3)
335 self.position_buffer =
None
336 self.index_buffer =
None
338 def deserialize(self, data):
339 ''' Deserialize the item from a Python dictionary '''
340 self.vertices = data[self.kVerticesKey]
341 self.indices = data[self.kIndicesKey]
343 def serialize(self, data):
344 ''' Serialize the item to a Python dictionary '''
345 super(helperShapeRepItemCurve, self).serialize(data)
346 data[self.kVerticesKey] = self.vertices
347 data[self.kIndicesKey] = self.indices
349 def vertex_buffer(self):
350 ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
351 if not self.position_buffer:
352 self.position_buffer = self._create_vertex_buffer(self.position_desc, self.vertices)
353 vertex_buffer = omr.MVertexBufferArray()
354 vertex_buffer.append(self.position_buffer,
'positions')
357 def wire_index_buffer(self):
358 ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
359 if not self.index_buffer:
360 self.index_buffer = self._create_index_buffer(self.indices)
361 return self.index_buffer
363 def clear_buffers(self):
364 ''' Clears cached vertex/index buffers for this item. '''
365 self.position_buffer =
None
366 self.index_buffer =
None
368 def bounding_box(self):
369 ''' Returns an MBoundingBox for this item. '''
370 box = om.MBoundingBox()
371 for v
in self.vertices:
372 box.expand(om.MPoint(v[0], v[1], v[2]))
375 class helperShapeRepItemCube(helperShapeRepItemMesh):
376 ''' A sample default cube mesh representation item. '''
378 super(helperShapeRepItemCube, self).__init__()
457 num_prim = len(self.vertices)
458 num_index = num_prim * 2
459 self.wire_indices = [
None] * num_index
462 start_index = i / 8 * 4
463 pairs = [(0,1), (1,2), (2,3), (3,0)]
464 for (index, p)
in enumerate(pairs):
465 self.wire_indices[i + index*2] = start_index + p[0]
466 self.wire_indices[i + index*2 + 1] = start_index + p[1]
469 num_prim = len(self.vertices)
470 num_index = num_prim / 4 * 6
471 self.shaded_indices = [
None] * num_index
474 start_index = i / 6 * 4
475 tris = [(0,1,2), (0,2,3)]
476 for (index, t)
in enumerate(tris):
477 self.shaded_indices[i + index*3] = start_index + t[0]
478 self.shaded_indices[i + index*3 + 1] = start_index + t[1]
479 self.shaded_indices[i + index*3 + 2] = start_index + t[2]
482 class helperShapeRepItemDiamond(helperShapeRepItemCurve):
483 ''' A sample default diamond curve representation item. '''
485 super(helperShapeRepItemDiamond, self).__init__()
492 self.indices = [0,1,1,2,2,3,3,0]
494 class helperShapeRep(object):
496 Defines the helperShape representation class.
497 A representation consists of a list of representation items.
500 kRenderItemsKey =
'renderItems'
506 def deserialize(self, file_path):
507 ''' Deserialize the representation from JSON. '''
508 self.name = os.path.splitext(os.path.basename(file_path))[0]
509 with open(file_path,
'r') as f:
513 if not isinstance(data, dict):
514 raise RuntimeError(
'Invalid JSON helperShape file: %s'.format(file_path))
515 if not self.kRenderItemsKey
in data
or not isinstance(data[self.kRenderItemsKey], list):
516 raise RuntimeError(
'Invalid [renderItems] key in file: %s'.format(file_path))
519 for item_data
in data[self.kRenderItemsKey]:
520 type = helperShapeRepItem.deserialize_type(item_data)
521 if type == helperShapeRepItemType.kMesh:
522 item = helperShapeRepItemMesh()
523 elif type == helperShapeRepItemType.kCurve:
524 item = helperShapeRepItemCurve()
527 item.deserialize(item_data)
528 self.items.append(item)
530 def serialize(self, file_path):
531 ''' Serialize the representation to JSON. '''
533 data[self.kRenderItemsKey] = []
534 for item
in self.items:
536 item.serialize(item_data)
537 data[self.kRenderItemsKey].append(item_data)
538 with open(file_path,
'w')
as f:
539 json.dump( data, f, sort_keys=
True, indent=4, separators=[
',',
': '] )
541 class helperShapeRepDefault(helperShapeRep):
543 A sample default helper representation consisting of a cube mesh
547 super(helperShapeRepDefault, self).__init__()
548 self.name =
'default'
549 self.items.append( helperShapeRepItemCube() )
550 self.items.append( helperShapeRepItemDiamond() )
557 class helperShapeExportCmd(om.MPxCommand):
558 ''' helperShapeExportCmd command class. '''
559 name =
'locatorHelperShapeExport'
562 kNameFlagLong =
'name'
563 kCurveSamplesFlag =
'cs'
564 kCurveSamplesFlagLong =
'curveSamples'
566 kForceFlagLong =
'force'
570 om.MPxCommand.__init__(self)
573 self.curve_samples = 50
578 ''' Creator method. '''
579 return helperShapeExportCmd()
583 ''' Command syntax definition. '''
584 syntax = om.MSyntax()
585 syntax.addFlag(helperShapeExportCmd.kNameFlag, helperShapeExportCmd.kNameFlagLong, om.MSyntax.kString)
586 syntax.addFlag(helperShapeExportCmd.kCurveSamplesFlag, helperShapeExportCmd.kCurveSamplesFlagLong, om.MSyntax.kUnsigned)
587 syntax.addFlag(helperShapeExportCmd.kForceFlag, helperShapeExportCmd.kForceFlagLong)
588 syntax.setObjectType(om.MSyntax.kStringObjects, 0)
592 def valid_name(cls, name):
594 Returns true if the name does not already exist in the
595 shape library path. False otherwise.
597 for (root, dirs, files)
in os.walk(helperShapeNode.shapes_lib_path):
599 (file_name, file_ext) = os.path.splitext(f)
600 if not file_ext ==
'.json':
602 if name == file_name:
606 def get_objects(self, parser):
608 Filters the command object list for meshes/curve shapes.
609 If the command object list is empty, filters the selection
610 list for meshes/curve shapes.
612 Returns the meshes/curve shapes in a list of MObjects.
614 obj_strs = parser.getObjectStrings()
615 sel_list = om.MSelectionList()
620 sel_list = om.MGlobal.getActiveSelectionList()
623 for i
in xrange(sel_list.length()):
625 path = sel_list.getDagPath(i)
629 multiple_shapes =
False
633 multiple_shapes =
True
635 if not multiple_shapes:
636 if path.apiType() == om.MFn.kMesh
or path.apiType() == om.MFn.kNurbsCurve:
637 objects.append(path.node())
641 while not dag_it.isDone():
642 item = dag_it.getPath()
643 if item.apiType() == om.MFn.kMesh
or item.apiType() == om.MFn.kNurbsCurve:
644 objects.append(item.node())
648 def generate_mesh(self, obj):
650 Given a mesh shape MObject, generate a helperShapeRepItemMesh.
652 if not obj.apiType() == om.MFn.kMesh:
656 mesh_fn = om.MFnMesh(obj)
657 raw_vertices_mpts = mesh_fn.getPoints()
658 raw_vertices = [ (p.x, p.y, p.z)
for p
in raw_vertices_mpts ]
659 raw_normals_mvcs = mesh_fn.getNormals()
660 raw_normals = [ (n.x, n.y, n.z)
for n
in raw_normals_mvcs ]
677 face_it = om.MItMeshPolygon(obj)
678 while not face_it.isDone():
679 count = len(vertices)
680 num_face_verts = face_it.polygonVertexCount()
684 mesh_to_face_vertex_map = {}
685 for i
in xrange(num_face_verts):
686 mesh_vid = face_it.vertexIndex(i)
687 mesh_to_face_vertex_map[mesh_vid] = i
690 (tri_pts, tri_ids) = face_it.getTriangles()
691 num_tris = len(tri_ids) / 3
692 for i
in xrange(num_tris):
694 vid = tri_ids[i*3 + j]
695 local_vid = mesh_to_face_vertex_map[vid]
696 nid = face_it.normalIndex(local_vid)
702 vertex_data = (raw_vertices[vid], raw_normals[nid])
703 if vertex_data
in unique_vertices:
704 vertex_id = unique_vertices[vertex_data]
708 unique_vertices[vertex_data] = vertex_id
709 vertices.append(vertex_data[0])
710 normals.append(vertex_data[1])
714 shaded_indices.append(vertex_id)
718 if vid
in mesh_vertex_map:
719 mesh_vertex_map[vid].append(vertex_id)
721 mesh_vertex_map[vid] = [vertex_id,]
726 edge_it = om.MItMeshEdge(obj)
727 while not edge_it.isDone():
728 mesh_vids = [edge_it.vertexId(0), edge_it.vertexId(1)]
729 vids = [mesh_vertex_map[mesh_vids[0]][0], mesh_vertex_map[mesh_vids[1]][0]]
731 wire_indices.append(vid)
734 rep_mesh = helperShapeRepItemMesh()
735 rep_mesh.vertices = vertices
736 rep_mesh.normals = normals
737 rep_mesh.wire_indices = wire_indices
738 rep_mesh.shaded_indices = shaded_indices
741 def generate_curve(self, obj):
743 Given a curve shape MObject, generate a helperShapeRepItemCurve.
745 if not obj.apiType() == om.MFn.kNurbsCurve:
747 curve_fn = om.MFnNurbsCurve(obj)
753 if curve_fn.degree == 1:
755 cvs = curve_fn.cvPositions()
756 vertices = [ (v.x, v.y, v.z)
for v
in cvs ]
759 curve_len = curve_fn.length()
760 curve_sample_len = curve_len / (self.curve_samples)
761 curve_len_samples = [ i*curve_sample_len
for i
in xrange(self.curve_samples) ]
762 for s
in curve_len_samples:
763 param = curve_fn.findParamFromLength(s)
764 pt = curve_fn.getPointAtParam(param)
765 vertices.append((pt.x, pt.y, pt.z))
768 for i
in xrange(1, len(vertices)):
771 if curve_fn.form == om.MFnNurbsCurve.kClosed
or curve_fn.form == om.MFnNurbsCurve.kPeriodic:
772 indices.append(len(vertices)-1)
775 rep_curve = helperShapeRepItemCurve()
776 rep_curve.vertices = vertices
777 rep_curve.indices = indices
780 def doIt(self, arg_list):
781 ''' Perform the command. '''
782 parser = om.MArgParser(self.syntax(), arg_list)
785 if parser.isFlagSet(self.kForceFlag):
789 if not parser.isFlagSet(self.kNameFlag):
790 raise RuntimeError(
'The -{}/{} flag must be set.'.format(self.kNameFlag, self.kNameFlagLong))
791 self.name = parser.flagArgumentString(self.kNameFlag, 0)
792 if not self.force
and not self.valid_name(self.name):
793 raise RuntimeError(
'The specified name already exists: {}'.format(self.name))
796 if parser.isFlagSet(self.kCurveSamplesFlag):
797 self.curve_samples = parser.flagArgumentInt(self.kCurveSamplesFlag, 0)
800 objects = self.get_objects(parser)
803 rep = helperShapeRep()
806 if o.apiType() == om.MFn.kMesh:
807 item = self.generate_mesh(o)
808 elif o.apiType() == om.MFn.kNurbsCurve:
809 item = self.generate_curve(o)
813 rep.items.append(item)
817 lib_path = helperShapeNode.shapes_lib_path
818 if not os.path.exists(lib_path):
819 os.makedirs(lib_path)
820 file_name = self.name +
'.json'
821 file_path = os.path.join(lib_path, file_name)
822 rep.serialize(file_path)
829 class helperShapeNode(omui.MPxLocatorNode):
830 ''' helperShapeNode locator class. '''
831 name =
'locatorHelperShape'
832 id = om.MTypeId( 0x00080041 )
833 drawDbClassification =
'drawdb/subscene/locatorHelperShape'
834 drawRegistrantId =
'locatorHelperShape_SubSceneOverride'
836 aShape = om.MObject()
837 aColor = om.MObject()
838 aDrawOnTop = om.MObject()
840 shapes_lib_path =
None
843 attrEditorTemplate =
'''
844 global proc AElocatorHelperShapeColorNew(string $plug) {
845 AElocatorHelperShapeColorReplace($plug);
848 global proc AElocatorHelperShapeColorReplace(string $plug) {
849 setUITemplate -pst attributeEditorTemplate;
850 string $parent = `setParent -q`;
851 string $frame = $parent + "|locatorHelperShapeColorFrame";
852 if (`frameLayout -ex $frame`) {
855 $frame = `frameLayout -l "Colors" -collapse false locatorHelperShapeColorFrame`;
856 string $column = `columnLayout -adj true`;
857 int $colorIds[] = python( "list(set(maya.cmds.getAttr(\\"" + $plug + "\\", mi=True)))" );
858 for ($id in $colorIds) {
859 string $index = "[" + $id + "]";
860 string $childPlug = $plug + $index;
861 string $childName = "color" + $index;
862 attrColorSliderGrp -l $childName -attribute $childPlug;
867 global proc AElocatorHelperShapeTemplate(string $node) {
868 editorTemplate -beginScrollLayout;
869 editorTemplate -beginLayout "Shape Attributes" -collapse 0;
870 editorTemplate -addControl "shape";
871 editorTemplate -addControl "drawOnTop";
872 editorTemplate -callCustom "AElocatorHelperShapeColorNew"
873 "AElocatorHelperShapeColorReplace"
875 editorTemplate -endLayout;
876 editorTemplate -beginLayout "Locator Attributes";
877 AElocatorCommon $node;
878 editorTemplate -endLayout;
879 AElocatorInclude $node;
880 editorTemplate -addExtraControls;
881 editorTemplate -endScrollLayout;
887 omui.MPxLocatorNode.__init__(self)
889 def postConstructor(self):
890 ''' Post-constructor. '''
891 enum_attr_fn = om.MFnEnumAttribute(self.aShape)
892 shape_default = enum_attr_fn.default
893 shape_plug = om.MPlug(self.thisMObject(), self.aShape)
894 shape_plug.setShort(shape_default)
898 ''' Creator method. '''
899 return helperShapeNode()
903 ''' Initialize node attribute layout. '''
904 num_attr_fn = om.MFnNumericAttribute()
905 enum_attr_fn = om.MFnEnumAttribute()
908 cls.aShape = enum_attr_fn.create(
'shape',
'sh')
909 for (i, shape)
in enumerate(cls.shapes):
910 enum_attr_fn.addField(shape.name, i)
911 if shape.name ==
'default':
913 enum_attr_fn.default = default_index
914 enum_attr_fn.internal =
True
916 cls.aColor = num_attr_fn.createColor(
'color',
'cl')
917 num_attr_fn.default = DEFAULT_COLOR
918 num_attr_fn.array =
True
920 cls.aDrawOnTop = num_attr_fn.create(
'drawOnTop',
'dot', om.MFnNumericData.kBoolean,
False)
922 cls.addAttribute( cls.aShape )
923 cls.addAttribute( cls.aColor )
924 cls.addAttribute( cls.aDrawOnTop )
926 def compute(self, plug, data):
927 ''' Compute method. '''
930 def getShapeSelectionMask(self):
931 ''' helperShape selection mask. '''
932 return om.MSelectionMask(HELPER_SHAPE_SELECTION_MASK)
934 def setInternalValueInContext(self, plug, data_handle, ctx):
935 ''' Callback to set internal attribute values. '''
938 if plug == self.aShape:
940 color_plug = om.MPlug(self.thisMObject(), self.aColor)
941 base_color_plug = color_plug.elementByLogicalIndex(0)
942 base_color_obj = base_color_plug.asMObject()
943 base_color = om.MFnNumericData(base_color_obj).getData()
945 shape_id = data_handle.asShort()
946 num_shape_items = len(self.shapes[shape_id].items)
947 data_block = self.forceCache()
948 color_builder = om.MArrayDataBuilder(data_block, self.aColor, num_shape_items)
949 for i
in xrange(num_shape_items):
950 child = color_builder.addLast()
951 child.set3Float(base_color[0], base_color[1], base_color[2])
953 color_ovr_hnd = data_block.outputArrayValue(self.aColor)
954 color_ovr_hnd.set(color_builder)
957 mel.eval(
'autoUpdateAttrEd;')
961 def init_shapes(cls, lib_path):
963 Populate the list of helperShape representations from
964 the helperShape library path.
966 This must be called prior to node registration since
967 the enumeration attribute to select the active shape
968 depends on the list of shapes.
971 cls.shapes_lib_path = lib_path
975 if os.path.exists(lib_path):
976 for (root, dirs, files)
in os.walk(lib_path):
978 (name, ext) = os.path.splitext(f)
980 file_path = os.path.join(root, f)
981 shape_files.append(file_path)
985 for f
in shape_files:
986 rep = helperShapeRep()
988 cls.shapes.append(rep)
990 cls.shapes.append(helperShapeRepDefault())
997 class helperShapeShaderItem(object):
998 ''' helperShape shader class to help uniquely identify shaders. '''
999 def __init__(self, shader_type, shader_color, transparent, pre_cb, post_cb):
1000 self.type = shader_type
1001 self.color = shader_color
1002 self.transparent = transparent
1003 self.pre_cb = pre_cb
1004 self.post_cb = post_cb
1006 def __eq__(self, other):
1007 return (isinstance(other, helperShapeShaderItem)
and
1008 self.type == other.type
and
1009 isclose_tuple(self.color, other.color)
and
1010 self.transparent == other.transparent
and
1011 self.pre_cb == other.pre_cb
and
1012 self.post_cb == other.post_cb)
1014 def __ne__(self, other):
1015 return not(self == other)
1018 return (self.type, self.color, self.transparent, self.pre_cb, self.post_cb)
1021 return hash(self.__key())
1023 class helperShapeShaderCache(object):
1024 ''' helperShape cache of shader instances '''
1026 ''' Constructor. Initialize the shader cache. '''
1030 ''' Destructor. Clear the shader cache. '''
1031 shader_mgr = omr.MRenderer.getShaderManager()
1034 for (_, shader)
in self.cache.items():
1035 shader_mgr.releaseShader(shader)
1038 def get_wire_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1039 ''' Return a wire shader with the given parameters from the cache. '''
1040 shader_mgr = omr.MRenderer.getShaderManager()
1043 shader_type = omr.MShaderManager.k3dThickLineShader
1044 item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1045 if not item
in self.cache:
1046 shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1047 shader.setParameter(
'solidColor', shader_color)
1048 shader.setParameter(
'lineWidth', [DEFAULT_LINE_WIDTH]*2)
1049 shader.setIsTransparent(transparent)
1050 self.cache[item] = shader
1051 return self.cache[item]
1053 def get_shaded_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1054 ''' Return a shaded shader with the given parameters from the cache. '''
1055 shader_mgr = omr.MRenderer.getShaderManager()
1058 shader_type = omr.MShaderManager.k3dBlinnShader
1059 item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1060 if not item
in self.cache:
1061 shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1062 shader.setParameter(
'diffuseColor', shader_color)
1063 shader.setIsTransparent(transparent)
1064 self.cache[item] = shader
1065 return self.cache[item]
1067 class helperShapeSubSceneOverride(omr.MPxSubSceneOverride):
1068 ''' helperShape viewport subscene override class. '''
1069 kUpdateShaders = 1 << 0
1070 kUpdateGeometry = 1 << 1
1071 kUpdateMatrix = 1 << 2
1075 sActiveName =
'active'
1077 sShadedName =
'shaded'
1079 shader_cache = helperShapeShaderCache()
1081 saved_depth_state =
None
1083 def __init__(self, obj):
1084 ''' Constructor. '''
1085 omr.MPxSubSceneOverride.__init__(self, obj)
1087 node_fn = om.MFnDependencyNode(obj)
1088 self.node = node_fn.userNode()
1089 self.shape_index = 0
1090 self.shape_colors = []
1091 self.lead_color = (0.0, 0.0, 0.0, 1.0)
1092 self.active_color = (0.0, 0.0, 0.0, 1.0)
1093 self.draw_on_top =
False
1095 self.wire_shaders = []
1096 self.shaded_shaders = []
1097 self.lead_select_shader =
None
1098 self.active_select_shader =
None
1100 self.update_mask = self.kUpdateAll
1107 self.lead_items = []
1108 self.active_items = []
1109 self.wire_items = []
1110 self.shaded_items = []
1114 ''' Creator method. '''
1115 return helperShapeSubSceneOverride(obj)
1117 def supportedDrawAPIs(self):
1118 ''' Return the draw APIs supported by this override. '''
1119 return omr.MRenderer.kOpenGL | omr.MRenderer.kDirectX11 | omr.MRenderer.kOpenGLCoreProfile
1121 def hasUIDrawables(self):
1122 ''' Does this override have UI drawables? '''
1127 ''' Cleanup our shader cache. '''
1128 del(cls.shader_cache)
1129 cls.shader_cache =
None
1132 def pre_shader_cb(cls, context, render_items, shader):
1133 ''' Pre shader render callback. Used to facilitate drawOnTop feature. '''
1134 if len(render_items) > 0:
1135 state_mgr = context.getStateManager()
1136 cls.saved_depth_state = state_mgr.getDepthStencilState()
1137 depth_state_desc = omr.MDepthStencilStateDesc()
1138 depth_state_desc.depthEnable =
False
1139 disabled_depth_state = state_mgr.acquireDepthStencilState(depth_state_desc)
1140 state_mgr.setDepthStencilState(disabled_depth_state)
1143 def post_shader_cb(cls, context, render_items, shader):
1144 ''' Post shader render callback. Used to facilitate drawOnTop feature. '''
1145 if len(render_items) > 0:
1146 state_mgr = context.getStateManager()
1147 state_mgr.setDepthStencilState(cls.saved_depth_state)
1148 state_mgr.releaseDepthStencilState(cls.saved_depth_state)
1149 cls.saved_depth_state =
None
1151 def requiresUpdate(self, container, frame_context):
1152 ''' Returns true if the update function should be called. '''
1153 self.requires_update_shaders()
1154 self.requires_update_geometry()
1155 self.requires_update_matrix()
1156 return self.update_mask > 0
1158 def requires_update_shaders(self):
1160 Checks if any attributes have changed that would affect
1161 this override's shaders. If so, sets the kUpdateShaders
1164 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1165 shape_id = shape_plug.asShort()
1166 num_shapes = len(helperShapeNode.shapes[shape_id].items)
1168 old_colors = self.shape_colors
1170 color_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aColor)
1171 for i
in xrange(num_shapes):
1172 color_elem = color_plug.elementByLogicalIndex(i)
1173 color_obj = color_elem.asMObject()
1174 num_data_fn = om.MFnNumericData(color_obj)
1175 new_color = num_data_fn.getData()
1177 new_colors.append( (new_color[0], new_color[1], new_color[2], 1.0) )
1178 self.shape_colors = new_colors
1181 if len(old_colors) != len(new_colors):
1182 self.update_mask |= self.kUpdateShaders
1184 for (o,n)
in zip(old_colors, new_colors):
1185 if not isclose_tuple(o, n):
1186 self.update_mask |= self.kUpdateShaders
1190 old_lead_color = self.lead_color
1191 old_active_color = self.active_color
1192 view = omui.M3dView()
1193 lead_color_index = cmds.displayColor(
'lead', q=
True, active=
True) - 1
1194 lead_color = view.colorAtIndex( lead_color_index, omui.M3dView.kActiveColors )
1195 self.lead_color = (lead_color.r, lead_color.g, lead_color.b, lead_color.a)
1197 active_color_index = cmds.displayColor(
'active', q=
True, active=
True) - 1
1198 active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1199 self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1201 old_sel_colors = (old_lead_color, old_active_color)
1202 new_sel_colors = (self.lead_color, self.active_color)
1203 for (o, n)
in zip(old_sel_colors, new_sel_colors):
1204 if not isclose_tuple(o, n):
1205 self.update_mask |= self.kUpdateShaders
1209 draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1210 old_draw_on_top = self.draw_on_top
1211 self.draw_on_top = draw_on_top_plug.asBool()
1212 if self.draw_on_top != old_draw_on_top:
1213 self.update_mask |= self.kUpdateShaders
1215 def requires_update_geometry(self):
1217 Checks if any attributes have changed that would affect
1218 the geometry of any render items. If so, sets the kUpdateGeometry
1219 and kUpdateMatrix update bits.
1221 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1222 old_shape_index = self.shape_index
1223 self.shape_index = shape_plug.asInt()
1224 if self.shape_index >= len(helperShapeNode.shapes):
1225 self.shape_index = old_shape_index
1226 if not self.shape_index == old_shape_index:
1227 old_shape = helperShapeNode.shapes[old_shape_index]
1228 for item
in old_shape.items:
1229 item.clear_buffers()
1234 self.update_mask |= self.kUpdateGeometry
1235 self.update_mask |= self.kUpdateMatrix
1237 def requires_update_matrix(self):
1239 Checks if any attributes have changed that would affect
1240 the matrices of any render items. If so, sets the
1241 kUpdateMatrix update bit.
1243 old_instances = self.instances
1246 dag_fn = om.MFnDagNode(self.node.thisMObject())
1247 paths = dag_fn.getAllPaths()
1250 if not path.isVisible():
1252 matrix = path.inclusiveMatrix()
1253 display_status = omr.MGeometryUtilities.displayStatus(path)
1254 self.instances[path.instanceNumber()] = (matrix, display_status)
1257 old_instance_nums = set(old_instances.keys())
1258 new_instance_nums = set(self.instances.keys())
1259 instance_num_diff = old_instance_nums ^ new_instance_nums
1260 if len(instance_num_diff):
1262 self.update_mask |= self.kUpdateGeometry
1263 self.update_mask |= self.kUpdateMatrix
1267 for i
in self.instances.keys():
1268 (old_matrix, old_display) = old_instances[i]
1269 (new_matrix, new_display) = self.instances[i]
1271 if old_display != new_display:
1272 self.update_mask |= self.kUpdateMatrix
1274 if not old_matrix.isEquivalent(new_matrix):
1275 self.update_mask |= self.kUpdateMatrix
1278 def update(self, container, frame_context):
1280 Updates only the portions of the override that require
1283 if self.update_mask & self.kUpdateShaders:
1284 self.update_shaders()
1285 if self.update_mask & self.kUpdateGeometry:
1286 self.update_geometry(container, frame_context)
1287 if self.update_mask & self.kUpdateMatrix:
1288 self.update_matrix(container, frame_context)
1289 if self.update_mask & self.kUpdateShaders
and not self.update_mask & self.kUpdateGeometry:
1290 self.update_shaders_assign(container)
1291 self.update_mask = 0
1293 def update_shaders(self):
1295 Initialize shaders if uninitialized and set their
1296 parameters (ex. color)
1298 shader_mgr = omr.MRenderer.getShaderManager()
1302 if self.draw_on_top:
1303 pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1304 post_cb = helperShapeSubSceneOverride.post_shader_cb
1309 num_colors = len(self.shape_colors)
1310 self.wire_shaders = []
1311 self.shaded_shaders = []
1312 for i
in xrange(num_colors):
1313 shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1314 self.wire_shaders.append(shader)
1315 for i
in xrange(num_colors):
1316 shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1317 self.shaded_shaders.append(shader)
1318 self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1319 self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1321 def update_shaders_assign(self, container):
1323 Update shader assignments on geometry items.
1325 This is required for the case when shaders require updating,
1326 but geometry does not. In these cases we must update the
1327 assigned shaders on the existing render items.
1329 A separate routine is required for assignment so that both
1330 shaders and render item lists are properly initialized prior
1333 for (i, item_name)
in self.wire_items:
1334 item = container.find(item_name)
1335 shader = self.wire_shaders[i]
1336 if not shader
is item.getShader():
1337 item.setShader(shader)
1338 for (i, item_name)
in self.shaded_items:
1339 item = container.find(item_name)
1340 shader = self.shaded_shaders[i]
1341 if not shader
is item.getShader():
1342 item.setShader(shader)
1343 for (_, item_name)
in self.lead_items:
1344 item = container.find(item_name)
1345 if not shader
is item.getShader():
1346 item.setShader(self.lead_select_shader)
1347 for (_, item_name)
in self.active_items:
1348 item = container.find(item_name)
1349 if not shader
is item.getShader():
1350 item.setShader(self.active_select_shader)
1352 def update_geometry(self, container, frame_context):
1354 Update the subscene container with the render items to draw the shape.
1355 This method regenerates the render items from a clean slate.
1361 self.lead_items = []
1362 self.active_items = []
1363 self.wire_items = []
1364 self.shaded_items = []
1366 active_shape = helperShapeNode.shapes[self.shape_index]
1367 for (i,item)
in enumerate(active_shape.items):
1368 bounds = item.bounding_box()
1371 vertex_buffer = item.vertex_buffer()
1372 wire_index_buffer = item.wire_index_buffer()
1374 wire_name =
'_'.join((active_shape.name, str(i), self.sWireName))
1375 wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1376 wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1377 if item.type == helperShapeRepItemType.kMesh:
1379 wire_item.setDrawMode(omr.MGeometry.kWireframe)
1380 wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1381 wire_item.setShader(self.wire_shaders[i])
1382 container.add(wire_item)
1383 self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1384 self.wire_items.append((i, wire_name))
1386 lead_name =
'_'.join((active_shape.name, str(i), self.sLeadName))
1387 lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1388 lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1389 lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1390 lead_item.setShader(self.lead_select_shader)
1391 container.add(lead_item)
1392 self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1393 self.lead_items.append((i, lead_name))
1395 active_name =
'_'.join((active_shape.name, str(i), self.sActiveName))
1396 active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1397 active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1398 active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1399 active_item.setShader(self.active_select_shader)
1400 container.add(active_item)
1401 self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1402 self.active_items.append((i, active_name))
1405 if item.type == helperShapeRepItemType.kMesh:
1406 shaded_index_buffer = item.shaded_index_buffer()
1407 shaded_name =
'_'.join((active_shape.name, str(i), self.sShadedName))
1408 shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1409 shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1410 shaded_item.setExcludedFromPostEffects(
True)
1411 shaded_item.setCastsShadows(
False)
1412 shaded_item.setReceivesShadows(
False)
1413 shaded_item.setShader(self.shaded_shaders[i])
1414 container.add(shaded_item)
1415 self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1416 self.shaded_items.append((i, shaded_name))
1418 def update_matrix(self, container, frame_context):
1420 Updates the matrices of the render items in the container.
1423 num_instances = len(self.instances)
1424 lead_instances = om.MMatrixArray()
1425 active_instances = om.MMatrixArray()
1426 dormant_instances = om.MMatrixArray()
1428 for (_, instance)
in self.instances.items():
1429 (matrix, display_status) = instance
1430 if display_status == omr.MGeometryUtilities.kLead:
1431 lead_instances.append(matrix)
1432 elif display_status == omr.MGeometryUtilities.kActive:
1433 active_instances.append(matrix)
1435 dormant_instances.append(matrix)
1438 dormant_items.extend(self.wire_items)
1439 dormant_items.extend(self.shaded_items)
1441 if num_instances == 0:
1447 container_it = container.getIterator(0)
1448 item = container_it.next()
1449 while item
is not None:
1451 item = container_it.next()
1452 container_it.destroy()
1454 elif num_instances == 1:
1456 matrix = dormant_instances[0]
1457 for (_, item_name)
in dormant_items:
1458 item = container.find(item_name)
1460 item.setMatrix(matrix)
1461 if WANT_CONSOLIDATION:
1462 item.setWantSubSceneConsolidation(
True)
1463 for (_, item_name)
in self.lead_items:
1464 item = container.find(item_name)
1465 item.enable(len(lead_instances) > 0)
1466 item.setMatrix(matrix)
1467 if WANT_CONSOLIDATION:
1468 item.setWantSubSceneConsolidation(
True)
1469 for (_, item_name)
in self.active_items:
1470 item = container.find(item_name)
1471 item.enable(len(active_instances) > 0)
1472 item.setMatrix(matrix)
1473 if WANT_CONSOLIDATION:
1474 item.setWantSubSceneConsolidation(
True)
1477 for (_, item_name)
in dormant_items:
1478 item = container.find(item_name)
1480 self.setInstanceTransformArray(item, dormant_instances)
1481 has_lead_instances = len(lead_instances) > 0
1482 for (_, item_name)
in self.lead_items:
1483 item = container.find(item_name)
1484 item.enable(has_lead_instances)
1485 if has_lead_instances:
1486 self.setInstanceTransformArray(item, lead_instances)
1487 has_active_instances = len(active_instances) > 0
1488 for (_, item_name)
in self.active_items:
1489 item = container.find(item_name)
1490 item.enable(has_active_instances)
1491 if has_active_instances:
1492 self.setInstanceTransformArray(item, active_instances)
1499 def initializePlugin(obj):
1500 plugin = om.MFnPlugin( obj )
1502 plugin_path = os.path.normpath(plugin.loadPath())
1503 lib_path = os.path.join(plugin_path,
'library')
1504 lib_env_var =
'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1505 if lib_env_var
in os.environ:
1506 env_path = os.environ[lib_env_var]
1507 lib_path = os.path.normpath(env_path)
1508 helperShapeNode.init_shapes(lib_path)
1511 plugin.registerNode(helperShapeNode.name,
1513 helperShapeNode.creator,
1514 helperShapeNode.initialize,
1515 om.MPxNode.kLocatorNode,
1516 helperShapeNode.drawDbClassification)
1518 sys.stderr.write(
'Failed to register locatorHelperShape node.\n')
1522 mel.eval(helperShapeNode.attrEditorTemplate)
1524 sys.stderr.write(
'Failed to load locatorHelperShape AETemplate script.\n')
1528 omr.MDrawRegistry.registerSubSceneOverrideCreator(
1529 helperShapeNode.drawDbClassification,
1530 helperShapeNode.drawRegistrantId,
1531 helperShapeSubSceneOverride.creator)
1533 sys.stderr.write(
'Failed to register locatorHelperShape SubSceneOverride.\n')
1537 plugin.registerCommand(helperShapeExportCmd.name,
1538 helperShapeExportCmd.creator,
1539 helperShapeExportCmd.new_syntax)
1541 sys.stderr.write(
'Failed to register locatorHelperShapeExportCmd.\n')
1545 om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1546 cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK,
True))
1548 def uninitializePlugin(obj):
1549 plugin = om.MFnPlugin( obj )
1552 plugin.deregisterNode(helperShapeNode.id)
1554 sys.stderr.write(
'Failed to deregister locatorHelperShape node.\n')
1558 omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1559 helperShapeNode.drawDbClassification,
1560 helperShapeNode.drawRegistrantId)
1562 sys.stderr.write(
'Failed to deregister locatorHelperShape SubSceneOverride.\n')
1566 plugin.deregisterCommand(helperShapeExportCmd.name)
1568 sys.stderr.write(
'Failed to deregister locatorHelperShapeExportCmd.\n')
1572 om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)