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_index = cmds.displayColor(
'lead', q=
True, active=
True) - 1
1199 lead_color = view.colorAtIndex( lead_color_index, omui.M3dView.kActiveColors )
1200 self.lead_color = (lead_color.r, lead_color.g, lead_color.b, lead_color.a)
1202 active_color_index = cmds.displayColor(
'active', q=
True, active=
True) - 1
1203 active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1204 self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1206 old_sel_colors = (old_lead_color, old_active_color)
1207 new_sel_colors = (self.lead_color, self.active_color)
1208 for (o, n)
in zip(old_sel_colors, new_sel_colors):
1209 if not isclose_tuple(o, n):
1210 self.update_mask |= self.kUpdateShaders
1214 draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1215 old_draw_on_top = self.draw_on_top
1216 self.draw_on_top = draw_on_top_plug.asBool()
1217 if self.draw_on_top != old_draw_on_top:
1218 self.update_mask |= self.kUpdateShaders
1220 def requires_update_geometry(self):
1222 Checks if any attributes have changed that would affect
1223 the geometry of any render items. If so, sets the kUpdateGeometry
1224 and kUpdateMatrix update bits.
1226 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1227 old_shape_index = self.shape_index
1228 self.shape_index = shape_plug.asInt()
1229 if self.shape_index >= len(helperShapeNode.shapes):
1230 self.shape_index = old_shape_index
1231 if not self.shape_index == old_shape_index:
1232 old_shape = helperShapeNode.shapes[old_shape_index]
1233 for item
in old_shape.items:
1234 item.clear_buffers()
1239 self.update_mask |= self.kUpdateGeometry
1240 self.update_mask |= self.kUpdateMatrix
1242 def requires_update_matrix(self):
1244 Checks if any attributes have changed that would affect
1245 the matrices of any render items. If so, sets the
1246 kUpdateMatrix update bit.
1248 old_instances = self.instances
1251 dag_fn = om.MFnDagNode(self.node.thisMObject())
1252 paths = dag_fn.getAllPaths()
1255 if not path.isVisible():
1257 matrix = path.inclusiveMatrix()
1258 display_status = omr.MGeometryUtilities.displayStatus(path)
1259 self.instances[path.instanceNumber()] = (matrix, display_status)
1262 old_instance_nums = set(old_instances.keys())
1263 new_instance_nums = set(self.instances.keys())
1264 instance_num_diff = old_instance_nums ^ new_instance_nums
1265 if len(instance_num_diff):
1267 self.update_mask |= self.kUpdateGeometry
1268 self.update_mask |= self.kUpdateMatrix
1272 for i
in list(self.instances.keys()):
1273 (old_matrix, old_display) = old_instances[i]
1274 (new_matrix, new_display) = self.instances[i]
1276 if old_display != new_display:
1277 self.update_mask |= self.kUpdateMatrix
1279 if not old_matrix.isEquivalent(new_matrix):
1280 self.update_mask |= self.kUpdateMatrix
1283 def update(self, container, frame_context):
1285 Updates only the portions of the override that require
1288 if self.update_mask & self.kUpdateShaders:
1289 self.update_shaders()
1290 if self.update_mask & self.kUpdateGeometry:
1291 self.update_geometry(container, frame_context)
1292 if self.update_mask & self.kUpdateMatrix:
1293 self.update_matrix(container, frame_context)
1294 if self.update_mask & self.kUpdateShaders
and not self.update_mask & self.kUpdateGeometry:
1295 self.update_shaders_assign(container)
1296 self.update_mask = 0
1298 def update_shaders(self):
1300 Initialize shaders if uninitialized and set their
1301 parameters (ex. color)
1303 shader_mgr = omr.MRenderer.getShaderManager()
1307 if self.draw_on_top:
1308 pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1309 post_cb = helperShapeSubSceneOverride.post_shader_cb
1314 num_colors = len(self.shape_colors)
1315 self.wire_shaders = []
1316 self.shaded_shaders = []
1317 for i
in range(num_colors):
1318 shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1319 self.wire_shaders.append(shader)
1320 for i
in range(num_colors):
1321 shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1322 self.shaded_shaders.append(shader)
1323 self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1324 self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1326 def update_shaders_assign(self, container):
1328 Update shader assignments on geometry items.
1330 This is required for the case when shaders require updating,
1331 but geometry does not. In these cases we must update the
1332 assigned shaders on the existing render items.
1334 A separate routine is required for assignment so that both
1335 shaders and render item lists are properly initialized prior
1338 for (i, item_name)
in self.wire_items:
1339 item = container.find(item_name)
1340 shader = self.wire_shaders[i]
1341 if not shader
is item.getShader():
1342 item.setShader(shader)
1343 for (i, item_name)
in self.shaded_items:
1344 item = container.find(item_name)
1345 shader = self.shaded_shaders[i]
1346 if not shader
is item.getShader():
1347 item.setShader(shader)
1348 for (_, item_name)
in self.lead_items:
1349 item = container.find(item_name)
1350 if not shader
is item.getShader():
1351 item.setShader(self.lead_select_shader)
1352 for (_, item_name)
in self.active_items:
1353 item = container.find(item_name)
1354 if not shader
is item.getShader():
1355 item.setShader(self.active_select_shader)
1357 def update_geometry(self, container, frame_context):
1359 Update the subscene container with the render items to draw the shape.
1360 This method regenerates the render items from a clean slate.
1366 self.lead_items = []
1367 self.active_items = []
1368 self.wire_items = []
1369 self.shaded_items = []
1371 active_shape = helperShapeNode.shapes[self.shape_index]
1372 for (i,item)
in enumerate(active_shape.items):
1373 bounds = item.bounding_box()
1376 vertex_buffer = item.vertex_buffer()
1377 wire_index_buffer = item.wire_index_buffer()
1379 wire_name =
'_'.join((active_shape.name, str(i), self.sWireName))
1380 wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1381 wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1382 if item.type == helperShapeRepItemType.kMesh:
1384 wire_item.setDrawMode(omr.MGeometry.kWireframe)
1385 wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1386 wire_item.setShader(self.wire_shaders[i])
1387 container.add(wire_item)
1388 self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1389 self.wire_items.append((i, wire_name))
1391 lead_name =
'_'.join((active_shape.name, str(i), self.sLeadName))
1392 lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1393 lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1394 lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1395 lead_item.setShader(self.lead_select_shader)
1396 container.add(lead_item)
1397 self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1398 self.lead_items.append((i, lead_name))
1400 active_name =
'_'.join((active_shape.name, str(i), self.sActiveName))
1401 active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1402 active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1403 active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1404 active_item.setShader(self.active_select_shader)
1405 container.add(active_item)
1406 self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1407 self.active_items.append((i, active_name))
1410 if item.type == helperShapeRepItemType.kMesh:
1411 shaded_index_buffer = item.shaded_index_buffer()
1412 shaded_name =
'_'.join((active_shape.name, str(i), self.sShadedName))
1413 shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1414 shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1415 shaded_item.setExcludedFromPostEffects(
True)
1416 shaded_item.setCastsShadows(
False)
1417 shaded_item.setReceivesShadows(
False)
1418 shaded_item.setShader(self.shaded_shaders[i])
1419 container.add(shaded_item)
1420 self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1421 self.shaded_items.append((i, shaded_name))
1423 def update_matrix(self, container, frame_context):
1425 Updates the matrices of the render items in the container.
1428 num_instances = len(self.instances)
1429 lead_instances = om.MMatrixArray()
1430 active_instances = om.MMatrixArray()
1431 dormant_instances = om.MMatrixArray()
1433 for (_, instance)
in list(self.instances.items()):
1434 (matrix, display_status) = instance
1435 if display_status == omr.MGeometryUtilities.kLead:
1436 lead_instances.append(matrix)
1437 elif display_status == omr.MGeometryUtilities.kActive:
1438 active_instances.append(matrix)
1440 dormant_instances.append(matrix)
1443 dormant_items.extend(self.wire_items)
1444 dormant_items.extend(self.shaded_items)
1446 if num_instances == 0:
1452 container_it = container.getIterator(0)
1453 item = next(container_it)
1454 while item
is not None:
1456 item = next(container_it)
1457 container_it.destroy()
1459 elif num_instances == 1:
1461 matrix = dormant_instances[0]
1462 for (_, item_name)
in dormant_items:
1463 item = container.find(item_name)
1465 item.setMatrix(matrix)
1466 if WANT_CONSOLIDATION:
1467 item.setWantSubSceneConsolidation(
True)
1468 for (_, item_name)
in self.lead_items:
1469 item = container.find(item_name)
1470 item.enable(len(lead_instances) > 0)
1471 item.setMatrix(matrix)
1472 if WANT_CONSOLIDATION:
1473 item.setWantSubSceneConsolidation(
True)
1474 for (_, item_name)
in self.active_items:
1475 item = container.find(item_name)
1476 item.enable(len(active_instances) > 0)
1477 item.setMatrix(matrix)
1478 if WANT_CONSOLIDATION:
1479 item.setWantSubSceneConsolidation(
True)
1482 for (_, item_name)
in dormant_items:
1483 item = container.find(item_name)
1485 self.setInstanceTransformArray(item, dormant_instances)
1486 has_lead_instances = len(lead_instances) > 0
1487 for (_, item_name)
in self.lead_items:
1488 item = container.find(item_name)
1489 item.enable(has_lead_instances)
1490 if has_lead_instances:
1491 self.setInstanceTransformArray(item, lead_instances)
1492 has_active_instances = len(active_instances) > 0
1493 for (_, item_name)
in self.active_items:
1494 item = container.find(item_name)
1495 item.enable(has_active_instances)
1496 if has_active_instances:
1497 self.setInstanceTransformArray(item, active_instances)
1504 def initializePlugin(obj):
1505 plugin = om.MFnPlugin( obj )
1507 plugin_path = os.path.normpath(plugin.loadPath())
1508 lib_path = os.path.join(plugin_path,
'library')
1509 lib_env_var =
'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1510 if lib_env_var
in os.environ:
1511 env_path = os.environ[lib_env_var]
1512 lib_path = os.path.normpath(env_path)
1513 helperShapeNode.init_shapes(lib_path)
1516 plugin.registerNode(helperShapeNode.name,
1518 helperShapeNode.creator,
1519 helperShapeNode.initialize,
1520 om.MPxNode.kLocatorNode,
1521 helperShapeNode.drawDbClassification)
1523 sys.stderr.write(
'Failed to register locatorHelperShape node.\n')
1527 mel.eval(helperShapeNode.attrEditorTemplate)
1529 sys.stderr.write(
'Failed to load locatorHelperShape AETemplate script.\n')
1533 omr.MDrawRegistry.registerSubSceneOverrideCreator(
1534 helperShapeNode.drawDbClassification,
1535 helperShapeNode.drawRegistrantId,
1536 helperShapeSubSceneOverride.creator)
1538 sys.stderr.write(
'Failed to register locatorHelperShape SubSceneOverride.\n')
1542 plugin.registerCommand(helperShapeExportCmd.name,
1543 helperShapeExportCmd.creator,
1544 helperShapeExportCmd.new_syntax)
1546 sys.stderr.write(
'Failed to register locatorHelperShapeExportCmd.\n')
1550 om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1551 cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK,
True))
1553 def uninitializePlugin(obj):
1554 plugin = om.MFnPlugin( obj )
1557 plugin.deregisterNode(helperShapeNode.id)
1559 sys.stderr.write(
'Failed to deregister locatorHelperShape node.\n')
1563 omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1564 helperShapeNode.drawDbClassification,
1565 helperShapeNode.drawRegistrantId)
1567 sys.stderr.write(
'Failed to deregister locatorHelperShape SubSceneOverride.\n')
1571 plugin.deregisterCommand(helperShapeExportCmd.name)
1573 sys.stderr.write(
'Failed to deregister locatorHelperShapeExportCmd.\n')
1577 om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)