1 from __future__
import division
86 from builtins
import range
87 from builtins
import object
88 from builtins
import zip
89 from builtins
import next
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
101 maya_useNewAPI =
True
107 DEFAULT_COLOR = (0.0, 0.7, 0.15)
108 DEFAULT_LINE_WIDTH = 1.0
110 HELPER_SHAPE_SELECTION_MASK =
'locatorHelperShapeSelection'
111 HELPER_SHAPE_SELECTION_PRIORITY = 9
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
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 )
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):
144 class helperShapeRepItemType(object):
145 ''' helperShape representation item type definition. '''
149 def __init__(self, index, name):
153 def __eq__(self, other):
154 return self.index == other.index
156 def __ne__(self, other):
157 return not(self == other)
166 helperShapeRepItemType.kMesh = helperShapeRepItemType(0,
'mesh')
167 helperShapeRepItemType.kCurve = helperShapeRepItemType(1,
'curve')
169 class helperShapeRepItem(object):
170 ''' Abstract base class for a helperShape representation item. '''
173 def __init__(self, type):
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]))
186 def deserialize(self, data):
187 ''' Deserialize the item from a Python dictionary '''
188 raise NotImplementedError
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
197 def vertex_buffer(self):
198 ''' Returns an MVertexBufferArray containing the vertex buffers for this item. '''
199 raise NotImplementedError
201 def wire_index_buffer(self):
202 ''' Returns an MIndexBuffer containing the index buffer for wireframe draw. '''
203 raise NotImplementedError
205 def shaded_index_buffer(self):
206 ''' Returns an MIndexBuffer containing the index buffer for shaded draw. '''
207 raise NotImplementedError
209 def clear_buffers(self):
210 ''' Clears cached vertex/index buffers for this item. '''
211 raise NotImplementedError
213 def bounding_box(self):
214 ''' Returns an MBoundingBox for this item. '''
215 raise NotImplementedError
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):
226 buffer.commit(address)
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):
241 buffer.commit(address)
244 raise NotImplementedError
247 class helperShapeRepItemMesh(helperShapeRepItem):
248 ''' Defines a mesh based helperShape representation item. '''
250 kVerticesKey =
'vertices'
251 kNormalsKey =
'normals'
252 kWireIndicesKey =
'wireIndices'
253 kShadedIndicesKey =
'shadedIndices'
257 super(helperShapeRepItemMesh, self).__init__(helperShapeRepItemType.kMesh)
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)
265 self.wire_indices =
None
266 self.shaded_indices =
None
268 self.position_buffer =
None
269 self.normal_buffer =
None
270 self.index_buffer_wire =
None
271 self.index_buffer_shaded =
None
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]
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
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')
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
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
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
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]))
325 class helperShapeRepItemCurve(helperShapeRepItem):
326 ''' Defines a curve based helperShape representation item. '''
328 kVerticesKey =
'vertices'
329 kIndicesKey =
'indices'
333 super(helperShapeRepItemCurve, self).__init__(helperShapeRepItemType.kCurve)
335 self.position_desc = omr.MVertexBufferDescriptor(
'', omr.MGeometry.kPosition, omr.MGeometry.kFloat, 3)
340 self.position_buffer =
None
341 self.index_buffer =
None
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]
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
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')
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
368 def clear_buffers(self):
369 ''' Clears cached vertex/index buffers for this item. '''
370 self.position_buffer =
None
371 self.index_buffer =
None
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]))
380 class helperShapeRepItemCube(helperShapeRepItemMesh):
381 ''' A sample default cube mesh representation item. '''
383 super(helperShapeRepItemCube, self).__init__()
462 num_prim = len(self.vertices)
463 num_index = num_prim * 2
464 self.wire_indices = [
None] * num_index
467 start_index = i // 8 * 4
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]
474 num_prim = len(self.vertices)
475 num_index = num_prim // 4 * 6
476 self.shaded_indices = [
None] * 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]
487 class helperShapeRepItemDiamond(helperShapeRepItemCurve):
488 ''' A sample default diamond curve representation item. '''
490 super(helperShapeRepItemDiamond, self).__init__()
497 self.indices = [0,1,1,2,2,3,3,0]
499 class helperShapeRep(object):
501 Defines the helperShape representation class.
502 A representation consists of a list of representation items.
505 kRenderItemsKey =
'renderItems'
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:
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))
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()
532 item.deserialize(item_data)
533 self.items.append(item)
535 def serialize(self, file_path):
536 ''' Serialize the representation to JSON. '''
538 data[self.kRenderItemsKey] = []
539 for item
in self.items:
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=[
',',
': '] )
546 class helperShapeRepDefault(helperShapeRep):
548 A sample default helper representation consisting of a cube mesh
552 super(helperShapeRepDefault, self).__init__()
553 self.name =
'default'
554 self.items.append( helperShapeRepItemCube() )
555 self.items.append( helperShapeRepItemDiamond() )
562 class helperShapeExportCmd(om.MPxCommand):
563 ''' helperShapeExportCmd command class. '''
564 name =
'locatorHelperShapeExport'
567 kNameFlagLong =
'name'
568 kCurveSamplesFlag =
'cs'
569 kCurveSamplesFlagLong =
'curveSamples'
571 kForceFlagLong =
'force'
575 om.MPxCommand.__init__(self)
578 self.curve_samples = 50
583 ''' Creator method. '''
584 return helperShapeExportCmd()
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)
597 def valid_name(cls, name):
599 Returns true if the name does not already exist in the
600 shape library path. False otherwise.
602 for (root, dirs, files)
in os.walk(helperShapeNode.shapes_lib_path):
604 (file_name, file_ext) = os.path.splitext(f)
605 if not file_ext ==
'.json':
607 if name == file_name:
611 def get_objects(self, parser):
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.
617 Returns the meshes/curve shapes in a list of MObjects.
619 obj_strs = parser.getObjectStrings()
620 sel_list = om.MSelectionList()
625 sel_list = om.MGlobal.getActiveSelectionList()
628 for i
in range(sel_list.length()):
630 path = sel_list.getDagPath(i)
634 multiple_shapes =
False
638 multiple_shapes =
True
640 if not multiple_shapes:
641 if path.apiType() == om.MFn.kMesh
or path.apiType() == om.MFn.kNurbsCurve:
642 objects.append(path.node())
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())
653 def generate_mesh(self, obj):
655 Given a mesh shape MObject, generate a helperShapeRepItemMesh.
657 if not obj.apiType() == om.MFn.kMesh:
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 ]
682 face_it = om.MItMeshPolygon(obj)
683 while not face_it.isDone():
684 count = len(vertices)
685 num_face_verts = face_it.polygonVertexCount()
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
695 (tri_pts, tri_ids) = face_it.getTriangles()
696 num_tris = len(tri_ids) // 3
697 for i
in range(num_tris):
699 vid = tri_ids[i*3 + j]
700 local_vid = mesh_to_face_vertex_map[vid]
701 nid = face_it.normalIndex(local_vid)
707 vertex_data = (raw_vertices[vid], raw_normals[nid])
708 if vertex_data
in unique_vertices:
709 vertex_id = unique_vertices[vertex_data]
713 unique_vertices[vertex_data] = vertex_id
714 vertices.append(vertex_data[0])
715 normals.append(vertex_data[1])
719 shaded_indices.append(vertex_id)
723 if vid
in mesh_vertex_map:
724 mesh_vertex_map[vid].append(vertex_id)
726 mesh_vertex_map[vid] = [vertex_id,]
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]]
736 wire_indices.append(vid)
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
746 def generate_curve(self, obj):
748 Given a curve shape MObject, generate a helperShapeRepItemCurve.
750 if not obj.apiType() == om.MFn.kNurbsCurve:
752 curve_fn = om.MFnNurbsCurve(obj)
758 if curve_fn.degree == 1:
760 cvs = curve_fn.cvPositions()
761 vertices = [ (v.x, v.y, v.z)
for v
in cvs ]
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))
773 for i
in range(1, len(vertices)):
776 if curve_fn.form == om.MFnNurbsCurve.kClosed
or curve_fn.form == om.MFnNurbsCurve.kPeriodic:
777 indices.append(len(vertices)-1)
780 rep_curve = helperShapeRepItemCurve()
781 rep_curve.vertices = vertices
782 rep_curve.indices = indices
785 def doIt(self, arg_list):
786 ''' Perform the command. '''
787 parser = om.MArgParser(self.syntax(), arg_list)
790 if parser.isFlagSet(self.kForceFlag):
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))
801 if parser.isFlagSet(self.kCurveSamplesFlag):
802 self.curve_samples = parser.flagArgumentInt(self.kCurveSamplesFlag, 0)
805 objects = self.get_objects(parser)
808 rep = helperShapeRep()
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)
818 rep.items.append(item)
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)
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'
841 aShape = om.MObject()
842 aColor = om.MObject()
843 aDrawOnTop = om.MObject()
845 shapes_lib_path =
None
848 attrEditorTemplate =
'''
849 global proc AElocatorHelperShapeColorNew(string $plug) {
850 AElocatorHelperShapeColorReplace($plug);
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`) {
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;
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"
880 editorTemplate -endLayout;
881 editorTemplate -beginLayout "Locator Attributes";
882 AElocatorCommon $node;
883 editorTemplate -endLayout;
884 AElocatorInclude $node;
885 editorTemplate -addExtraControls;
886 editorTemplate -endScrollLayout;
892 omui.MPxLocatorNode.__init__(self)
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)
903 ''' Creator method. '''
904 return helperShapeNode()
908 ''' Initialize node attribute layout. '''
909 num_attr_fn = om.MFnNumericAttribute()
910 enum_attr_fn = om.MFnEnumAttribute()
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':
918 enum_attr_fn.default = default_index
919 enum_attr_fn.internal =
True
921 cls.aColor = num_attr_fn.createColor(
'color',
'cl')
922 num_attr_fn.default = DEFAULT_COLOR
923 num_attr_fn.array =
True
925 cls.aDrawOnTop = num_attr_fn.create(
'drawOnTop',
'dot', om.MFnNumericData.kBoolean,
False)
927 cls.addAttribute( cls.aShape )
928 cls.addAttribute( cls.aColor )
929 cls.addAttribute( cls.aDrawOnTop )
931 def compute(self, plug, data):
932 ''' Compute method. '''
935 def getShapeSelectionMask(self):
936 ''' helperShape selection mask. '''
937 return om.MSelectionMask(HELPER_SHAPE_SELECTION_MASK)
939 def setInternalValueInContext(self, plug, data_handle, ctx):
940 ''' Callback to set internal attribute values. '''
943 if plug == self.aShape:
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()
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])
958 color_ovr_hnd = data_block.outputArrayValue(self.aColor)
959 color_ovr_hnd.set(color_builder)
962 mel.eval(
'autoUpdateAttrEd;')
966 def init_shapes(cls, lib_path):
968 Populate the list of helperShape representations from
969 the helperShape library path.
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.
976 cls.shapes_lib_path = lib_path
980 if os.path.exists(lib_path):
981 for (root, dirs, files)
in os.walk(lib_path):
983 (name, ext) = os.path.splitext(f)
985 file_path = os.path.join(root, f)
986 shape_files.append(file_path)
990 for f
in shape_files:
991 rep = helperShapeRep()
993 cls.shapes.append(rep)
995 cls.shapes.append(helperShapeRepDefault())
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
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)
1019 def __ne__(self, other):
1020 return not(self == other)
1023 return (self.type, self.color, self.transparent, self.pre_cb, self.post_cb)
1026 return hash(self.__key())
1028 class helperShapeShaderCache(object):
1029 ''' helperShape cache of shader instances '''
1031 ''' Constructor. Initialize the shader cache. '''
1035 ''' Destructor. Clear the shader cache. '''
1036 shader_mgr = omr.MRenderer.getShaderManager()
1039 for (_, shader)
in list(self.cache.items()):
1040 shader_mgr.releaseShader(shader)
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()
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]
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()
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]
1072 class helperShapeSubSceneOverride(omr.MPxSubSceneOverride):
1073 ''' helperShape viewport subscene override class. '''
1074 kUpdateShaders = 1 << 0
1075 kUpdateGeometry = 1 << 1
1076 kUpdateMatrix = 1 << 2
1080 sActiveName =
'active'
1082 sShadedName =
'shaded'
1084 shader_cache = helperShapeShaderCache()
1086 saved_depth_state =
None
1088 def __init__(self, obj):
1089 ''' Constructor. '''
1090 omr.MPxSubSceneOverride.__init__(self, obj)
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
1100 self.wire_shaders = []
1101 self.shaded_shaders = []
1102 self.lead_select_shader =
None
1103 self.active_select_shader =
None
1105 self.update_mask = self.kUpdateAll
1112 self.lead_items = []
1113 self.active_items = []
1114 self.wire_items = []
1115 self.shaded_items = []
1119 ''' Creator method. '''
1120 return helperShapeSubSceneOverride(obj)
1122 def supportedDrawAPIs(self):
1123 ''' Return the draw APIs supported by this override. '''
1124 return omr.MRenderer.kOpenGL | omr.MRenderer.kDirectX11 | omr.MRenderer.kOpenGLCoreProfile
1126 def hasUIDrawables(self):
1127 ''' Does this override have UI drawables? '''
1132 ''' Cleanup our shader cache. '''
1133 del(cls.shader_cache)
1134 cls.shader_cache =
None
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)
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
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
1163 def requires_update_shaders(self):
1165 Checks if any attributes have changed that would affect
1166 this override's shaders. If so, sets the kUpdateShaders
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)
1173 old_colors = self.shape_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()
1182 new_colors.append( (new_color[0], new_color[1], new_color[2], 1.0) )
1183 self.shape_colors = new_colors
1186 if len(old_colors) != len(new_colors):
1187 self.update_mask |= self.kUpdateShaders
1189 for (o,n)
in zip(old_colors, new_colors):
1190 if not isclose_tuple(o, n):
1191 self.update_mask |= self.kUpdateShaders
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])
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)
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
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
1219 def requires_update_geometry(self):
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.
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()
1238 self.update_mask |= self.kUpdateGeometry
1239 self.update_mask |= self.kUpdateMatrix
1241 def requires_update_matrix(self):
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.
1247 old_instances = self.instances
1250 dag_fn = om.MFnDagNode(self.node.thisMObject())
1251 paths = dag_fn.getAllPaths()
1254 if not path.isVisible():
1256 matrix = path.inclusiveMatrix()
1257 display_status = omr.MGeometryUtilities.displayStatus(path)
1258 self.instances[path.instanceNumber()] = (matrix, display_status)
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):
1266 self.update_mask |= self.kUpdateGeometry
1267 self.update_mask |= self.kUpdateMatrix
1271 for i
in list(self.instances.keys()):
1272 (old_matrix, old_display) = old_instances[i]
1273 (new_matrix, new_display) = self.instances[i]
1275 if old_display != new_display:
1276 self.update_mask |= self.kUpdateMatrix
1278 if not old_matrix.isEquivalent(new_matrix):
1279 self.update_mask |= self.kUpdateMatrix
1282 def update(self, container, frame_context):
1284 Updates only the portions of the override that require
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
1297 def update_shaders(self):
1299 Initialize shaders if uninitialized and set their
1300 parameters (ex. color)
1302 shader_mgr = omr.MRenderer.getShaderManager()
1306 if self.draw_on_top:
1307 pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1308 post_cb = helperShapeSubSceneOverride.post_shader_cb
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)
1325 def update_shaders_assign(self, container):
1327 Update shader assignments on geometry items.
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.
1333 A separate routine is required for assignment so that both
1334 shaders and render item lists are properly initialized prior
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)
1356 def update_geometry(self, container, frame_context):
1358 Update the subscene container with the render items to draw the shape.
1359 This method regenerates the render items from a clean slate.
1365 self.lead_items = []
1366 self.active_items = []
1367 self.wire_items = []
1368 self.shaded_items = []
1370 active_shape = helperShapeNode.shapes[self.shape_index]
1371 for (i,item)
in enumerate(active_shape.items):
1372 bounds = item.bounding_box()
1375 vertex_buffer = item.vertex_buffer()
1376 wire_index_buffer = item.wire_index_buffer()
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:
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))
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))
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))
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))
1422 def update_matrix(self, container, frame_context):
1424 Updates the matrices of the render items in the container.
1427 num_instances = len(self.instances)
1428 lead_instances = om.MMatrixArray()
1429 active_instances = om.MMatrixArray()
1430 dormant_instances = om.MMatrixArray()
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)
1439 dormant_instances.append(matrix)
1442 dormant_items.extend(self.wire_items)
1443 dormant_items.extend(self.shaded_items)
1445 if num_instances == 0:
1451 container_it = container.getIterator(0)
1452 item = next(container_it)
1453 while item
is not None:
1455 item = next(container_it)
1456 container_it.destroy()
1458 elif num_instances == 1:
1460 matrix = dormant_instances[0]
1461 for (_, item_name)
in dormant_items:
1462 item = container.find(item_name)
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)
1481 for (_, item_name)
in dormant_items:
1482 item = container.find(item_name)
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)
1503 def initializePlugin(obj):
1504 plugin = om.MFnPlugin( obj )
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)
1515 plugin.registerNode(helperShapeNode.name,
1517 helperShapeNode.creator,
1518 helperShapeNode.initialize,
1519 om.MPxNode.kLocatorNode,
1520 helperShapeNode.drawDbClassification)
1522 sys.stderr.write(
'Failed to register locatorHelperShape node.\n')
1526 mel.eval(helperShapeNode.attrEditorTemplate)
1528 sys.stderr.write(
'Failed to load locatorHelperShape AETemplate script.\n')
1532 omr.MDrawRegistry.registerSubSceneOverrideCreator(
1533 helperShapeNode.drawDbClassification,
1534 helperShapeNode.drawRegistrantId,
1535 helperShapeSubSceneOverride.creator)
1537 sys.stderr.write(
'Failed to register locatorHelperShape SubSceneOverride.\n')
1541 plugin.registerCommand(helperShapeExportCmd.name,
1542 helperShapeExportCmd.creator,
1543 helperShapeExportCmd.new_syntax)
1545 sys.stderr.write(
'Failed to register locatorHelperShapeExportCmd.\n')
1549 om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1550 cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK,
True))
1552 def uninitializePlugin(obj):
1553 plugin = om.MFnPlugin( obj )
1556 plugin.deregisterNode(helperShapeNode.id)
1558 sys.stderr.write(
'Failed to deregister locatorHelperShape node.\n')
1562 omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1563 helperShapeNode.drawDbClassification,
1564 helperShapeNode.drawRegistrantId)
1566 sys.stderr.write(
'Failed to deregister locatorHelperShape SubSceneOverride.\n')
1570 plugin.deregisterCommand(helperShapeExportCmd.name)
1572 sys.stderr.write(
'Failed to deregister locatorHelperShapeExportCmd.\n')
1576 om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)