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 if lead_color ==
None:
1200 raise RuntimeError(
"displayRGBColor failed")
1201 self.lead_color = (lead_color[0], lead_color[1], lead_color[2], lead_color[3])
1203 active_color_index = cmds.displayColor(
'active', q=
True, active=
True) - 1
1204 active_color = view.colorAtIndex( active_color_index, omui.M3dView.kActiveColors )
1205 self.active_color = (active_color.r, active_color.g, active_color.b, active_color.a)
1207 old_sel_colors = (old_lead_color, old_active_color)
1208 new_sel_colors = (self.lead_color, self.active_color)
1209 for (o, n)
in zip(old_sel_colors, new_sel_colors):
1210 if not isclose_tuple(o, n):
1211 self.update_mask |= self.kUpdateShaders
1215 draw_on_top_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aDrawOnTop)
1216 old_draw_on_top = self.draw_on_top
1217 self.draw_on_top = draw_on_top_plug.asBool()
1218 if self.draw_on_top != old_draw_on_top:
1219 self.update_mask |= self.kUpdateShaders
1221 def requires_update_geometry(self):
1223 Checks if any attributes have changed that would affect
1224 the geometry of any render items. If so, sets the kUpdateGeometry
1225 and kUpdateMatrix update bits.
1227 shape_plug = om.MPlug(self.node.thisMObject(), helperShapeNode.aShape)
1228 old_shape_index = self.shape_index
1229 self.shape_index = shape_plug.asInt()
1230 if self.shape_index >= len(helperShapeNode.shapes):
1231 self.shape_index = old_shape_index
1232 if not self.shape_index == old_shape_index:
1233 old_shape = helperShapeNode.shapes[old_shape_index]
1234 for item
in old_shape.items:
1235 item.clear_buffers()
1240 self.update_mask |= self.kUpdateGeometry
1241 self.update_mask |= self.kUpdateMatrix
1243 def requires_update_matrix(self):
1245 Checks if any attributes have changed that would affect
1246 the matrices of any render items. If so, sets the
1247 kUpdateMatrix update bit.
1249 old_instances = self.instances
1252 dag_fn = om.MFnDagNode(self.node.thisMObject())
1253 paths = dag_fn.getAllPaths()
1256 if not path.isVisible():
1258 matrix = path.inclusiveMatrix()
1259 display_status = omr.MGeometryUtilities.displayStatus(path)
1260 self.instances[path.instanceNumber()] = (matrix, display_status)
1263 old_instance_nums = set(old_instances.keys())
1264 new_instance_nums = set(self.instances.keys())
1265 instance_num_diff = old_instance_nums ^ new_instance_nums
1266 if len(instance_num_diff):
1268 self.update_mask |= self.kUpdateGeometry
1269 self.update_mask |= self.kUpdateMatrix
1273 for i
in list(self.instances.keys()):
1274 (old_matrix, old_display) = old_instances[i]
1275 (new_matrix, new_display) = self.instances[i]
1277 if old_display != new_display:
1278 self.update_mask |= self.kUpdateMatrix
1280 if not old_matrix.isEquivalent(new_matrix):
1281 self.update_mask |= self.kUpdateMatrix
1284 def update(self, container, frame_context):
1286 Updates only the portions of the override that require
1289 if self.update_mask & self.kUpdateShaders:
1290 self.update_shaders()
1291 if self.update_mask & self.kUpdateGeometry:
1292 self.update_geometry(container, frame_context)
1293 if self.update_mask & self.kUpdateMatrix:
1294 self.update_matrix(container, frame_context)
1295 if self.update_mask & self.kUpdateShaders
and not self.update_mask & self.kUpdateGeometry:
1296 self.update_shaders_assign(container)
1297 self.update_mask = 0
1299 def update_shaders(self):
1301 Initialize shaders if uninitialized and set their
1302 parameters (ex. color)
1304 shader_mgr = omr.MRenderer.getShaderManager()
1308 if self.draw_on_top:
1309 pre_cb = helperShapeSubSceneOverride.pre_shader_cb
1310 post_cb = helperShapeSubSceneOverride.post_shader_cb
1315 num_colors = len(self.shape_colors)
1316 self.wire_shaders = []
1317 self.shaded_shaders = []
1318 for i
in range(num_colors):
1319 shader = self.shader_cache.get_wire_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1320 self.wire_shaders.append(shader)
1321 for i
in range(num_colors):
1322 shader = self.shader_cache.get_shaded_shader(self.shape_colors[i], self.draw_on_top, pre_cb, post_cb)
1323 self.shaded_shaders.append(shader)
1324 self.lead_select_shader = self.shader_cache.get_wire_shader(self.lead_color, self.draw_on_top, pre_cb, post_cb)
1325 self.active_select_shader = self.shader_cache.get_wire_shader(self.active_color, self.draw_on_top, pre_cb, post_cb)
1327 def update_shaders_assign(self, container):
1329 Update shader assignments on geometry items.
1331 This is required for the case when shaders require updating,
1332 but geometry does not. In these cases we must update the
1333 assigned shaders on the existing render items.
1335 A separate routine is required for assignment so that both
1336 shaders and render item lists are properly initialized prior
1339 for (i, item_name)
in self.wire_items:
1340 item = container.find(item_name)
1341 shader = self.wire_shaders[i]
1342 if not shader
is item.getShader():
1343 item.setShader(shader)
1344 for (i, item_name)
in self.shaded_items:
1345 item = container.find(item_name)
1346 shader = self.shaded_shaders[i]
1347 if not shader
is item.getShader():
1348 item.setShader(shader)
1349 for (_, item_name)
in self.lead_items:
1350 item = container.find(item_name)
1351 if not shader
is item.getShader():
1352 item.setShader(self.lead_select_shader)
1353 for (_, item_name)
in self.active_items:
1354 item = container.find(item_name)
1355 if not shader
is item.getShader():
1356 item.setShader(self.active_select_shader)
1358 def update_geometry(self, container, frame_context):
1360 Update the subscene container with the render items to draw the shape.
1361 This method regenerates the render items from a clean slate.
1367 self.lead_items = []
1368 self.active_items = []
1369 self.wire_items = []
1370 self.shaded_items = []
1372 active_shape = helperShapeNode.shapes[self.shape_index]
1373 for (i,item)
in enumerate(active_shape.items):
1374 bounds = item.bounding_box()
1377 vertex_buffer = item.vertex_buffer()
1378 wire_index_buffer = item.wire_index_buffer()
1380 wire_name =
'_'.join((active_shape.name, str(i), self.sWireName))
1381 wire_item = omr.MRenderItem.create( wire_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kLines )
1382 wire_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1383 if item.type == helperShapeRepItemType.kMesh:
1385 wire_item.setDrawMode(omr.MGeometry.kWireframe)
1386 wire_item.setDepthPriority(omr.MRenderItem.sDormantWireDepthPriority)
1387 wire_item.setShader(self.wire_shaders[i])
1388 container.add(wire_item)
1389 self.setGeometryForRenderItem(wire_item, vertex_buffer, wire_index_buffer, bounds)
1390 self.wire_items.append((i, wire_name))
1392 lead_name =
'_'.join((active_shape.name, str(i), self.sLeadName))
1393 lead_item = omr.MRenderItem.create( lead_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1394 lead_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1395 lead_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1396 lead_item.setShader(self.lead_select_shader)
1397 container.add(lead_item)
1398 self.setGeometryForRenderItem(lead_item, vertex_buffer, wire_index_buffer, bounds)
1399 self.lead_items.append((i, lead_name))
1401 active_name =
'_'.join((active_shape.name, str(i), self.sActiveName))
1402 active_item = omr.MRenderItem.create( active_name, omr.MRenderItem.DecorationItem, omr.MGeometry.kLines )
1403 active_item.setDrawMode(omr.MGeometry.kWireframe | omr.MGeometry.kShaded | omr.MGeometry.kTextured)
1404 active_item.setDepthPriority(omr.MRenderItem.sActiveWireDepthPriority)
1405 active_item.setShader(self.active_select_shader)
1406 container.add(active_item)
1407 self.setGeometryForRenderItem(active_item, vertex_buffer, wire_index_buffer, bounds)
1408 self.active_items.append((i, active_name))
1411 if item.type == helperShapeRepItemType.kMesh:
1412 shaded_index_buffer = item.shaded_index_buffer()
1413 shaded_name =
'_'.join((active_shape.name, str(i), self.sShadedName))
1414 shaded_item = omr.MRenderItem.create( shaded_name, omr.MRenderItem.NonMaterialSceneItem, omr.MGeometry.kTriangles )
1415 shaded_item.setDrawMode( omr.MGeometry.kShaded | omr.MGeometry.kTextured )
1416 shaded_item.setExcludedFromPostEffects(
True)
1417 shaded_item.setCastsShadows(
False)
1418 shaded_item.setReceivesShadows(
False)
1419 shaded_item.setShader(self.shaded_shaders[i])
1420 container.add(shaded_item)
1421 self.setGeometryForRenderItem(shaded_item, vertex_buffer, shaded_index_buffer, bounds)
1422 self.shaded_items.append((i, shaded_name))
1424 def update_matrix(self, container, frame_context):
1426 Updates the matrices of the render items in the container.
1429 num_instances = len(self.instances)
1430 lead_instances = om.MMatrixArray()
1431 active_instances = om.MMatrixArray()
1432 dormant_instances = om.MMatrixArray()
1434 for (_, instance)
in list(self.instances.items()):
1435 (matrix, display_status) = instance
1436 if display_status == omr.MGeometryUtilities.kLead:
1437 lead_instances.append(matrix)
1438 elif display_status == omr.MGeometryUtilities.kActive:
1439 active_instances.append(matrix)
1441 dormant_instances.append(matrix)
1444 dormant_items.extend(self.wire_items)
1445 dormant_items.extend(self.shaded_items)
1447 if num_instances == 0:
1453 container_it = container.getIterator(0)
1454 item = next(container_it)
1455 while item
is not None:
1457 item = next(container_it)
1458 container_it.destroy()
1460 elif num_instances == 1:
1462 matrix = dormant_instances[0]
1463 for (_, item_name)
in dormant_items:
1464 item = container.find(item_name)
1466 item.setMatrix(matrix)
1467 if WANT_CONSOLIDATION:
1468 item.setWantSubSceneConsolidation(
True)
1469 for (_, item_name)
in self.lead_items:
1470 item = container.find(item_name)
1471 item.enable(len(lead_instances) > 0)
1472 item.setMatrix(matrix)
1473 if WANT_CONSOLIDATION:
1474 item.setWantSubSceneConsolidation(
True)
1475 for (_, item_name)
in self.active_items:
1476 item = container.find(item_name)
1477 item.enable(len(active_instances) > 0)
1478 item.setMatrix(matrix)
1479 if WANT_CONSOLIDATION:
1480 item.setWantSubSceneConsolidation(
True)
1483 for (_, item_name)
in dormant_items:
1484 item = container.find(item_name)
1486 self.setInstanceTransformArray(item, dormant_instances)
1487 has_lead_instances = len(lead_instances) > 0
1488 for (_, item_name)
in self.lead_items:
1489 item = container.find(item_name)
1490 item.enable(has_lead_instances)
1491 if has_lead_instances:
1492 self.setInstanceTransformArray(item, lead_instances)
1493 has_active_instances = len(active_instances) > 0
1494 for (_, item_name)
in self.active_items:
1495 item = container.find(item_name)
1496 item.enable(has_active_instances)
1497 if has_active_instances:
1498 self.setInstanceTransformArray(item, active_instances)
1505 def initializePlugin(obj):
1506 plugin = om.MFnPlugin( obj )
1508 plugin_path = os.path.normpath(plugin.loadPath())
1509 lib_path = os.path.join(plugin_path,
'library')
1510 lib_env_var =
'MAYA_LOCATOR_HELPER_SHAPE_LIB'
1511 if lib_env_var
in os.environ:
1512 env_path = os.environ[lib_env_var]
1513 lib_path = os.path.normpath(env_path)
1514 helperShapeNode.init_shapes(lib_path)
1517 plugin.registerNode(helperShapeNode.name,
1519 helperShapeNode.creator,
1520 helperShapeNode.initialize,
1521 om.MPxNode.kLocatorNode,
1522 helperShapeNode.drawDbClassification)
1524 sys.stderr.write(
'Failed to register locatorHelperShape node.\n')
1528 mel.eval(helperShapeNode.attrEditorTemplate)
1530 sys.stderr.write(
'Failed to load locatorHelperShape AETemplate script.\n')
1534 omr.MDrawRegistry.registerSubSceneOverrideCreator(
1535 helperShapeNode.drawDbClassification,
1536 helperShapeNode.drawRegistrantId,
1537 helperShapeSubSceneOverride.creator)
1539 sys.stderr.write(
'Failed to register locatorHelperShape SubSceneOverride.\n')
1543 plugin.registerCommand(helperShapeExportCmd.name,
1544 helperShapeExportCmd.creator,
1545 helperShapeExportCmd.new_syntax)
1547 sys.stderr.write(
'Failed to register locatorHelperShapeExportCmd.\n')
1551 om.MSelectionMask.registerSelectionType(HELPER_SHAPE_SELECTION_MASK, HELPER_SHAPE_SELECTION_PRIORITY)
1552 cmds.selectType(byName=(HELPER_SHAPE_SELECTION_MASK,
True))
1554 def uninitializePlugin(obj):
1555 plugin = om.MFnPlugin( obj )
1558 plugin.deregisterNode(helperShapeNode.id)
1560 sys.stderr.write(
'Failed to deregister locatorHelperShape node.\n')
1564 omr.MDrawRegistry.deregisterSubSceneOverrideCreator(
1565 helperShapeNode.drawDbClassification,
1566 helperShapeNode.drawRegistrantId)
1568 sys.stderr.write(
'Failed to deregister locatorHelperShape SubSceneOverride.\n')
1572 plugin.deregisterCommand(helperShapeExportCmd.name)
1574 sys.stderr.write(
'Failed to deregister locatorHelperShapeExportCmd.\n')
1578 om.MSelectionMask.deregisterSelectionType(HELPER_SHAPE_SELECTION_MASK)