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,]
729 edge_it = om.MItMeshEdge(obj)
730 while not edge_it.isDone():
731 mesh_vids = [edge_it.vertexId(0), edge_it.vertexId(1)]
732 vids = [mesh_vertex_map[mesh_vids[0]][0], mesh_vertex_map[mesh_vids[1]][0]]
734 wire_indices.append(vid)
737 rep_mesh = helperShapeRepItemMesh()
738 rep_mesh.vertices = vertices
739 rep_mesh.normals = normals
740 rep_mesh.wire_indices = wire_indices
741 rep_mesh.shaded_indices = shaded_indices
744 def generate_curve(self, obj):
746 Given a curve shape MObject, generate a helperShapeRepItemCurve.
748 if not obj.apiType() == om.MFn.kNurbsCurve:
750 curve_fn = om.MFnNurbsCurve(obj)
756 if curve_fn.degree == 1:
758 cvs = curve_fn.cvPositions()
759 vertices = [ (v.x, v.y, v.z)
for v
in cvs ]
762 curve_len = curve_fn.length()
763 curve_sample_len = curve_len / (self.curve_samples)
764 curve_len_samples = [ i*curve_sample_len
for i
in xrange(self.curve_samples) ]
765 for s
in curve_len_samples:
766 param = curve_fn.findParamFromLength(s)
767 pt = curve_fn.getPointAtParam(param)
768 vertices.append((pt.x, pt.y, pt.z))
771 for i
in xrange(1, len(vertices)):
774 if curve_fn.form == om.MFnNurbsCurve.kClosed
or curve_fn.form == om.MFnNurbsCurve.kPeriodic:
775 indices.append(len(vertices)-1)
778 rep_curve = helperShapeRepItemCurve()
779 rep_curve.vertices = vertices
780 rep_curve.indices = indices
783 def doIt(self, arg_list):
784 ''' Perform the command. '''
785 parser = om.MArgParser(self.syntax(), arg_list)
788 if parser.isFlagSet(self.kForceFlag):
792 if not parser.isFlagSet(self.kNameFlag):
793 raise RuntimeError(
'The -{}/{} flag must be set.'.format(self.kNameFlag, self.kNameFlagLong))
794 self.name = parser.flagArgumentString(self.kNameFlag, 0)
795 if not self.force
and not self.valid_name(self.name):
796 raise RuntimeError(
'The specified name already exists: {}'.format(self.name))
799 if parser.isFlagSet(self.kCurveSamplesFlag):
800 self.curve_samples = parser.flagArgumentInt(self.kCurveSamplesFlag, 0)
803 objects = self.get_objects(parser)
806 rep = helperShapeRep()
809 if o.apiType() == om.MFn.kMesh:
810 item = self.generate_mesh(o)
811 elif o.apiType() == om.MFn.kNurbsCurve:
812 item = self.generate_curve(o)
816 rep.items.append(item)
820 lib_path = helperShapeNode.shapes_lib_path
821 if not os.path.exists(lib_path):
822 os.makedirs(lib_path)
823 file_name = self.name +
'.json'
824 file_path = os.path.join(lib_path, file_name)
825 rep.serialize(file_path)
832 class helperShapeNode(omui.MPxLocatorNode):
833 ''' helperShapeNode locator class. '''
834 name =
'locatorHelperShape'
835 id = om.MTypeId( 0x00080041 )
836 drawDbClassification =
'drawdb/subscene/locatorHelperShape'
837 drawRegistrantId =
'locatorHelperShape_SubSceneOverride'
839 aShape = om.MObject()
840 aColor = om.MObject()
841 aDrawOnTop = om.MObject()
843 shapes_lib_path =
None
846 attrEditorTemplate =
'''
847 global proc AElocatorHelperShapeColorNew(string $plug) {
848 AElocatorHelperShapeColorReplace($plug);
851 global proc AElocatorHelperShapeColorReplace(string $plug) {
852 setUITemplate -pst attributeEditorTemplate;
853 string $parent = `setParent -q`;
854 string $frame = $parent + "|locatorHelperShapeColorFrame";
855 if (`frameLayout -ex $frame`) {
858 $frame = `frameLayout -l "Colors" -collapse false locatorHelperShapeColorFrame`;
859 string $column = `columnLayout -adj true`;
860 int $colorIds[] = python( "list(set(maya.cmds.getAttr(\\"" + $plug + "\\", mi=True)))" );
861 for ($id in $colorIds) {
862 string $index = "[" + $id + "]";
863 string $childPlug = $plug + $index;
864 string $childName = "color" + $index;
865 attrColorSliderGrp -l $childName -attribute $childPlug;
870 global proc AElocatorHelperShapeTemplate(string $node) {
871 editorTemplate -beginScrollLayout;
872 editorTemplate -beginLayout "Shape Attributes" -collapse 0;
873 editorTemplate -addControl "shape";
874 editorTemplate -addControl "drawOnTop";
875 editorTemplate -callCustom "AElocatorHelperShapeColorNew"
876 "AElocatorHelperShapeColorReplace"
878 editorTemplate -endLayout;
879 editorTemplate -beginLayout "Locator Attributes";
880 AElocatorCommon $node;
881 editorTemplate -endLayout;
882 AElocatorInclude $node;
883 editorTemplate -addExtraControls;
884 editorTemplate -endScrollLayout;
890 omui.MPxLocatorNode.__init__(self)
892 def postConstructor(self):
893 ''' Post-constructor. '''
894 enum_attr_fn = om.MFnEnumAttribute(self.aShape)
895 shape_default = enum_attr_fn.default
896 shape_plug = om.MPlug(self.thisMObject(), self.aShape)
897 shape_plug.setShort(shape_default)
901 ''' Creator method. '''
902 return helperShapeNode()
906 ''' Initialize node attribute layout. '''
907 num_attr_fn = om.MFnNumericAttribute()
908 enum_attr_fn = om.MFnEnumAttribute()
911 cls.aShape = enum_attr_fn.create(
'shape',
'sh')
912 for (i, shape)
in enumerate(cls.shapes):
913 enum_attr_fn.addField(shape.name, i)
914 if shape.name ==
'default':
916 enum_attr_fn.default = default_index
917 enum_attr_fn.internal =
True
919 cls.aColor = num_attr_fn.createColor(
'color',
'cl')
920 num_attr_fn.default = DEFAULT_COLOR
921 num_attr_fn.array =
True
923 cls.aDrawOnTop = num_attr_fn.create(
'drawOnTop',
'dot', om.MFnNumericData.kBoolean,
False)
925 cls.addAttribute( cls.aShape )
926 cls.addAttribute( cls.aColor )
927 cls.addAttribute( cls.aDrawOnTop )
929 def compute(self, plug, data):
930 ''' Compute method. '''
933 def getShapeSelectionMask(self):
934 ''' helperShape selection mask. '''
935 return om.MSelectionMask(HELPER_SHAPE_SELECTION_MASK)
937 def setInternalValueInContext(self, plug, data_handle, ctx):
938 ''' Callback to set internal attribute values. '''
941 if plug == self.aShape:
943 color_plug = om.MPlug(self.thisMObject(), self.aColor)
944 base_color_plug = color_plug.elementByLogicalIndex(0)
945 base_color_obj = base_color_plug.asMObject()
946 base_color = om.MFnNumericData(base_color_obj).getData()
948 shape_id = data_handle.asShort()
949 num_shape_items = len(self.shapes[shape_id].items)
950 data_block = self.forceCache()
951 color_builder = om.MArrayDataBuilder(data_block, self.aColor, num_shape_items)
952 for i
in xrange(num_shape_items):
953 child = color_builder.addLast()
954 child.set3Float(base_color[0], base_color[1], base_color[2])
956 color_ovr_hnd = data_block.outputArrayValue(self.aColor)
957 color_ovr_hnd.set(color_builder)
960 mel.eval(
'autoUpdateAttrEd;')
964 def init_shapes(cls, lib_path):
966 Populate the list of helperShape representations from
967 the helperShape library path.
969 This must be called prior to node registration since
970 the enumeration attribute to select the active shape
971 depends on the list of shapes.
974 cls.shapes_lib_path = lib_path
978 if os.path.exists(lib_path):
979 for (root, dirs, files)
in os.walk(lib_path):
981 (name, ext) = os.path.splitext(f)
983 file_path = os.path.join(root, f)
984 shape_files.append(file_path)
988 for f
in shape_files:
989 rep = helperShapeRep()
991 cls.shapes.append(rep)
993 cls.shapes.append(helperShapeRepDefault())
1000 class helperShapeShaderItem(object):
1001 ''' helperShape shader class to help uniquely identify shaders. '''
1002 def __init__(self, shader_type, shader_color, transparent, pre_cb, post_cb):
1003 self.type = shader_type
1004 self.color = shader_color
1005 self.transparent = transparent
1006 self.pre_cb = pre_cb
1007 self.post_cb = post_cb
1009 def __eq__(self, other):
1010 return (isinstance(other, helperShapeShaderItem)
and
1011 self.type == other.type
and
1012 isclose_tuple(self.color, other.color)
and
1013 self.transparent == other.transparent
and
1014 self.pre_cb == other.pre_cb
and
1015 self.post_cb == other.post_cb)
1017 def __ne__(self, other):
1018 return not(self == other)
1021 return (self.type, self.color, self.transparent, self.pre_cb, self.post_cb)
1024 return hash(self.__key())
1026 class helperShapeShaderCache(object):
1027 ''' helperShape cache of shader instances '''
1029 ''' Constructor. Initialize the shader cache. '''
1033 ''' Destructor. Clear the shader cache. '''
1034 shader_mgr = omr.MRenderer.getShaderManager()
1037 for (_, shader)
in self.cache.items():
1038 shader_mgr.releaseShader(shader)
1041 def get_wire_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1042 ''' Return a wire shader with the given parameters from the cache. '''
1043 shader_mgr = omr.MRenderer.getShaderManager()
1046 shader_type = omr.MShaderManager.k3dThickLineShader
1047 item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1048 if not item
in self.cache:
1049 shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1050 shader.setParameter(
'solidColor', shader_color)
1051 shader.setParameter(
'lineWidth', [DEFAULT_LINE_WIDTH]*2)
1052 shader.setIsTransparent(transparent)
1053 self.cache[item] = shader
1054 return self.cache[item]
1056 def get_shaded_shader(self, shader_color, transparent, pre_cb=None, post_cb=None):
1057 ''' Return a shaded shader with the given parameters from the cache. '''
1058 shader_mgr = omr.MRenderer.getShaderManager()
1061 shader_type = omr.MShaderManager.k3dBlinnShader
1062 item = helperShapeShaderItem(shader_type, shader_color, transparent, pre_cb, post_cb)
1063 if not item
in self.cache:
1064 shader = shader_mgr.getStockShader(shader_type, pre_cb, post_cb)
1065 shader.setParameter(
'diffuseColor', shader_color)
1066 shader.setIsTransparent(transparent)
1067 self.cache[item] = shader
1068 return self.cache[item]
1070 class helperShapeSubSceneOverride(omr.MPxSubSceneOverride):
1071 ''' helperShape viewport subscene override class. '''
1072 kUpdateShaders = 1 << 0
1073 kUpdateGeometry = 1 << 1
1074 kUpdateMatrix = 1 << 2
1078 sActiveName =
'active'
1080 sShadedName =
'shaded'
1082 shader_cache = helperShapeShaderCache()
1084 saved_depth_state =
None
1086 def __init__(self, obj):
1087 ''' Constructor. '''
1088 omr.MPxSubSceneOverride.__init__(self, obj)
1090 node_fn = om.MFnDependencyNode(obj)
1091 self.node = node_fn.userNode()
1092 self.shape_index = 0
1093 self.shape_colors = []
1094 self.lead_color = (0.0, 0.0, 0.0, 1.0)
1095 self.active_color = (0.0, 0.0, 0.0, 1.0)
1096 self.draw_on_top =
False
1098 self.wire_shaders = []
1099 self.shaded_shaders = []
1100 self.lead_select_shader =
None
1101 self.active_select_shader =
None
1103 self.update_mask = self.kUpdateAll
1110 self.lead_items = []
1111 self.active_items = []
1112 self.wire_items = []
1113 self.shaded_items = []
1117 ''' Creator method. '''
1118 return helperShapeSubSceneOverride(obj)
1120 def supportedDrawAPIs(self):
1121 ''' Return the draw APIs supported by this override. '''
1122 return omr.MRenderer.kOpenGL | omr.MRenderer.kDirectX11 | omr.MRenderer.kOpenGLCoreProfile
1124 def hasUIDrawables(self):
1125 ''' Does this override have UI drawables? '''
1130 ''' Cleanup our shader cache. '''
1131 del(cls.shader_cache)
1132 cls.shader_cache =
None
1135 def pre_shader_cb(cls, context, render_items, shader):
1136 ''' Pre shader render callback. Used to facilitate drawOnTop feature. '''
1137 if len(render_items) > 0:
1138 state_mgr = context.getStateManager()
1139 cls.saved_depth_state = state_mgr.getDepthStencilState()
1140 depth_state_desc = omr.MDepthStencilStateDesc()
1141 depth_state_desc.depthEnable =
False
1142 disabled_depth_state = state_mgr.acquireDepthStencilState(depth_state_desc)
1143 state_mgr.setDepthStencilState(disabled_depth_state)
1146 def post_shader_cb(cls, context, render_items, shader):
1147 ''' Post shader render callback. Used to facilitate drawOnTop feature. '''
1148 if len(render_items) > 0:
1149 state_mgr = context.getStateManager()
1150 state_mgr.setDepthStencilState(cls.saved_depth_state)
1151 state_mgr.releaseDepthStencilState(cls.saved_depth_state)
1152 cls.saved_depth_state =
None
1154 def requiresUpdate(self, container, frame_context):
1155 ''' Returns true if the update function should be called. '''
1156 self.requires_update_shaders()
1157 self.requires_update_geometry()
1158 self.requires_update_matrix()
1159 return self.update_mask > 0
1161 def requires_update_shaders(self):
1163 Checks if any attributes have changed that would affect
1164 this override's shaders. If so, sets the kUpdateShaders
1167 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1168 shape_id = shape_plug.asShort()
1169 num_shapes = len(helperShapeNode.shapes[shape_id].items)
1171 old_colors = self.shape_colors
1173 color_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aColor)
1174 for i
in xrange(num_shapes):
1175 color_elem = color_plug.elementByLogicalIndex(i)
1176 color_obj = color_elem.asMObject()
1177 num_data_fn = om.MFnNumericData(color_obj)
1178 new_color = num_data_fn.getData()
1180 new_colors.append( (new_color[0], new_color[1], new_color[2], 1.0) )
1181 self.shape_colors = new_colors
1184 if len(old_colors) != len(new_colors):
1185 self.update_mask |= self.kUpdateShaders
1187 for (o,n)
in zip(old_colors, new_colors):
1188 if not isclose_tuple(o, n):
1189 self.update_mask |= self.kUpdateShaders
1193 old_lead_color = self.lead_color
1194 old_active_color = self.active_color
1195 view = omui.M3dView()
1196 lead_color_index = cmds.displayColor(
'lead', q=
True, active=
True) - 1
1197 lead_color = view.colorAtIndex( lead_color_index, omui.M3dView.kActiveColors )
1198 self.lead_color = (lead_color.r, lead_color.g, lead_color.b, lead_color.a)
1200 active_color_index = cmds.displayColor(
'active', q=
True, active=
True) - 1
1201 active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1202 self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1204 old_sel_colors = (old_lead_color, old_active_color)
1205 new_sel_colors = (self.lead_color, self.active_color)
1206 for (o, n)
in zip(old_sel_colors, new_sel_colors):
1207 if not isclose_tuple(o, n):
1208 self.update_mask |= self.kUpdateShaders
1212 draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1213 old_draw_on_top = self.draw_on_top
1214 self.draw_on_top = draw_on_top_plug.asBool()
1215 if self.draw_on_top != old_draw_on_top:
1216 self.update_mask |= self.kUpdateShaders
1218 def requires_update_geometry(self):
1220 Checks if any attributes have changed that would affect
1221 the geometry of any render items. If so, sets the kUpdateGeometry
1222 and kUpdateMatrix update bits.
1224 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1225 old_shape_index = self.shape_index
1226 self.shape_index = shape_plug.asInt()
1227 if self.shape_index >= len(helperShapeNode.shapes):
1228 self.shape_index = old_shape_index
1229 if not self.shape_index == old_shape_index:
1230 old_shape = helperShapeNode.shapes[old_shape_index]
1231 for item
in old_shape.items:
1232 item.clear_buffers()
1237 self.update_mask |= self.kUpdateGeometry
1238 self.update_mask |= self.kUpdateMatrix
1240 def requires_update_matrix(self):
1242 Checks if any attributes have changed that would affect
1243 the matrices of any render items. If so, sets the
1244 kUpdateMatrix update bit.
1246 old_instances = self.instances
1249 dag_fn = om.MFnDagNode(self.node.thisMObject())
1250 paths = dag_fn.getAllPaths()
1253 if not path.isVisible():
1255 matrix = path.inclusiveMatrix()
1256 display_status = omr.MGeometryUtilities.displayStatus(path)
1257 self.instances[path.instanceNumber()] = (matrix, display_status)
1260 old_instance_nums = set(old_instances.keys())
1261 new_instance_nums = set(self.instances.keys())
1262 instance_num_diff = old_instance_nums ^ new_instance_nums
1263 if len(instance_num_diff):
1265 self.update_mask |= self.kUpdateGeometry
1266 self.update_mask |= self.kUpdateMatrix
1270 for i
in self.instances.keys():
1271 (old_matrix, old_display) = old_instances[i]
1272 (new_matrix, new_display) = self.instances[i]
1274 if old_display != new_display:
1275 self.update_mask |= self.kUpdateMatrix
1277 if not old_matrix.isEquivalent(new_matrix):
1278 self.update_mask |= self.kUpdateMatrix
1281 def update(self, container, frame_context):
1283 Updates only the portions of the override that require
1286 if self.update_mask & self.kUpdateShaders:
1287 self.update_shaders()
1288 if self.update_mask & self.kUpdateGeometry:
1289 self.update_geometry(container, frame_context)
1290 if self.update_mask & self.kUpdateMatrix:
1291 self.update_matrix(container, frame_context)
1292 if self.update_mask & self.kUpdateShaders
and not self.update_mask & self.kUpdateGeometry:
1293 self.update_shaders_assign(container)
1294 self.update_mask = 0
1296 def update_shaders(self):
1298 Initialize shaders if uninitialized and set their
1299 parameters (ex. color)
1301 shader_mgr = omr.MRenderer.getShaderManager()
1305 if self.draw_on_top:
1306 pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1307 post_cb = helperShapeSubSceneOverride.post_shader_cb
1312 num_colors = len(self.shape_colors)
1313 self.wire_shaders = []
1314 self.shaded_shaders = []
1315 for i
in xrange(num_colors):
1316 shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1317 self.wire_shaders.append(shader)
1318 for i
in xrange(num_colors):
1319 shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1320 self.shaded_shaders.append(shader)
1321 self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1322 self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1324 def update_shaders_assign(self, container):
1326 Update shader assignments on geometry items.
1328 This is required for the case when shaders require updating,
1329 but geometry does not. In these cases we must update the
1330 assigned shaders on the existing render items.
1332 A separate routine is required for assignment so that both
1333 shaders and render item lists are properly initialized prior
1336 for (i, item_name)
in self.wire_items:
1337 item = container.find(item_name)
1338 shader = self.wire_shaders[i]
1339 if not shader
is item.getShader():
1340 item.setShader(shader)
1341 for (i, item_name)
in self.shaded_items:
1342 item = container.find(item_name)
1343 shader = self.shaded_shaders[i]
1344 if not shader
is item.getShader():
1345 item.setShader(shader)
1346 for (_, item_name)
in self.lead_items:
1347 item = container.find(item_name)
1348 if not shader
is item.getShader():
1349 item.setShader(self.lead_select_shader)
1350 for (_, item_name)
in self.active_items:
1351 item = container.find(item_name)
1352 if not shader
is item.getShader():
1353 item.setShader(self.active_select_shader)
1355 def update_geometry(self, container, frame_context):
1357 Update the subscene container with the render items to draw the shape.
1358 This method regenerates the render items from a clean slate.
1364 self.lead_items = []
1365 self.active_items = []
1366 self.wire_items = []
1367 self.shaded_items = []
1369 active_shape = helperShapeNode.shapes[self.shape_index]
1370 for (i,item)
in enumerate(active_shape.items):
1371 bounds = item.bounding_box()
1374 vertex_buffer = item.vertex_buffer()
1375 wire_index_buffer = item.wire_index_buffer()
1377 wire_name =
'_'.join((active_shape.name, str(i), self.sWireName))
1378 wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1379 wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1380 if item.type == helperShapeRepItemType.kMesh:
1382 wire_item.setDrawMode(omr.MGeometry.kWireframe)
1383 wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1384 wire_item.setShader(self.wire_shaders[i])
1385 container.add(wire_item)
1386 self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1387 self.wire_items.append((i, wire_name))
1389 lead_name =
'_'.join((active_shape.name, str(i), self.sLeadName))
1390 lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1391 lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1392 lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1393 lead_item.setShader(self.lead_select_shader)
1394 container.add(lead_item)
1395 self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1396 self.lead_items.append((i, lead_name))
1398 active_name =
'_'.join((active_shape.name, str(i), self.sActiveName))
1399 active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1400 active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1401 active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1402 active_item.setShader(self.active_select_shader)
1403 container.add(active_item)
1404 self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1405 self.active_items.append((i, active_name))
1408 if item.type == helperShapeRepItemType.kMesh:
1409 shaded_index_buffer = item.shaded_index_buffer()
1410 shaded_name =
'_'.join((active_shape.name, str(i), self.sShadedName))
1411 shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1412 shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1413 shaded_item.setExcludedFromPostEffects(
True)
1414 shaded_item.setCastsShadows(
False)
1415 shaded_item.setReceivesShadows(
False)
1416 shaded_item.setShader(self.shaded_shaders[i])
1417 container.add(shaded_item)
1418 self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1419 self.shaded_items.append((i, shaded_name))
1421 def update_matrix(self, container, frame_context):
1423 Updates the matrices of the render items in the container.
1426 num_instances = len(self.instances)
1427 lead_instances = om.MMatrixArray()
1428 active_instances = om.MMatrixArray()
1429 dormant_instances = om.MMatrixArray()
1431 for (_, instance)
in self.instances.items():
1432 (matrix, display_status) = instance
1433 if display_status == omr.MGeometryUtilities.kLead:
1434 lead_instances.append(matrix)
1435 elif display_status == omr.MGeometryUtilities.kActive:
1436 active_instances.append(matrix)
1438 dormant_instances.append(matrix)
1441 dormant_items.extend(self.wire_items)
1442 dormant_items.extend(self.shaded_items)
1444 if num_instances == 0:
1450 container_it = container.getIterator(0)
1451 item = container_it.next()
1452 while item
is not None:
1454 item = container_it.next()
1455 container_it.destroy()
1457 elif num_instances == 1:
1459 matrix = dormant_instances[0]
1460 for (_, item_name)
in dormant_items:
1461 item = container.find(item_name)
1463 item.setMatrix(matrix)
1464 if WANT_CONSOLIDATION:
1465 item.setWantSubSceneConsolidation(
True)
1466 for (_, item_name)
in self.lead_items:
1467 item = container.find(item_name)
1468 item.enable(len(lead_instances) > 0)
1469 item.setMatrix(matrix)
1470 if WANT_CONSOLIDATION:
1471 item.setWantSubSceneConsolidation(
True)
1472 for (_, item_name)
in self.active_items:
1473 item = container.find(item_name)
1474 item.enable(len(active_instances) > 0)
1475 item.setMatrix(matrix)
1476 if WANT_CONSOLIDATION:
1477 item.setWantSubSceneConsolidation(
True)
1480 for (_, item_name)
in dormant_items:
1481 item = container.find(item_name)
1483 self.setInstanceTransformArray(item, dormant_instances)
1484 has_lead_instances = len(lead_instances) > 0
1485 for (_, item_name)
in self.lead_items:
1486 item = container.find(item_name)
1487 item.enable(has_lead_instances)
1488 if has_lead_instances:
1489 self.setInstanceTransformArray(item, lead_instances)
1490 has_active_instances = len(active_instances) > 0
1491 for (_, item_name)
in self.active_items:
1492 item = container.find(item_name)
1493 item.enable(has_active_instances)
1494 if has_active_instances:
1495 self.setInstanceTransformArray(item, active_instances)
1502 def initializePlugin(obj):
1503 plugin = om.MFnPlugin( obj )
1505 plugin_path = os.path.normpath(plugin.loadPath())
1506 lib_path = os.path.join(plugin_path,
'library')
1507 lib_env_var =
'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1508 if lib_env_var
in os.environ:
1509 env_path = os.environ[lib_env_var]
1510 lib_path = os.path.normpath(env_path)
1511 helperShapeNode.init_shapes(lib_path)
1514 plugin.registerNode(helperShapeNode.name,
1516 helperShapeNode.creator,
1517 helperShapeNode.initialize,
1518 om.MPxNode.kLocatorNode,
1519 helperShapeNode.drawDbClassification)
1521 sys.stderr.write(
'Failed to register locatorHelperShape node.\n')
1525 mel.eval(helperShapeNode.attrEditorTemplate)
1527 sys.stderr.write(
'Failed to load locatorHelperShape AETemplate script.\n')
1531 omr.MDrawRegistry.registerSubSceneOverrideCreator(
1532 helperShapeNode.drawDbClassification,
1533 helperShapeNode.drawRegistrantId,
1534 helperShapeSubSceneOverride.creator)
1536 sys.stderr.write(
'Failed to register locatorHelperShape SubSceneOverride.\n')
1540 plugin.registerCommand(helperShapeExportCmd.name,
1541 helperShapeExportCmd.creator,
1542 helperShapeExportCmd.new_syntax)
1544 sys.stderr.write(
'Failed to register locatorHelperShapeExportCmd.\n')
1548 om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1549 cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK,
True))
1551 def uninitializePlugin(obj):
1552 plugin = om.MFnPlugin( obj )
1555 plugin.deregisterNode(helperShapeNode.id)
1557 sys.stderr.write(
'Failed to deregister locatorHelperShape node.\n')
1561 omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1562 helperShapeNode.drawDbClassification,
1563 helperShapeNode.drawRegistrantId)
1565 sys.stderr.write(
'Failed to deregister locatorHelperShape SubSceneOverride.\n')
1569 plugin.deregisterCommand(helperShapeExportCmd.name)
1571 sys.stderr.write(
'Failed to deregister locatorHelperShapeExportCmd.\n')
1575 om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)