From d818dd78ac694b58ffbc62de2f4dcfe690fa0c9e Mon Sep 17 00:00:00 2001 From: Patrick Boutot Date: Thu, 22 Aug 2019 12:23:12 -0400 Subject: [PATCH] Update the Maya plugin with LiveLink role. With the role, you can now stream the camera information automaticly. Programmers can now create their own Role and send specific data. --- LICENSE | 2 +- README.md | 2 - ReadMe.txt | 2 + Source/BuildMayaPlugin.Bat | 4 + Source/BuildMayaPlugin.xml | 53 + Source/LiveLink.mod | 1 + Source/MayaLiveLinkPlugin.cpp | 1641 +++++++++++++++++++++++ Source/MayaLiveLinkPlugin2016.Build.cs | 78 ++ Source/MayaLiveLinkPlugin2016.Target.cs | 47 + Source/MayaLiveLinkPlugin2017.Build.cs | 11 + Source/MayaLiveLinkPlugin2017.Target.cs | 9 + Source/MayaLiveLinkPlugin2018.Build.cs | 11 + Source/MayaLiveLinkPlugin2018.Target.cs | 9 + Source/MayaLiveLinkPlugin2019.Build.cs | 11 + Source/MayaLiveLinkPlugin2019.Target.cs | 9 + Source/MayaLiveLinkUI.py | 201 +++ 16 files changed, 2088 insertions(+), 3 deletions(-) delete mode 100644 README.md create mode 100644 ReadMe.txt create mode 100644 Source/BuildMayaPlugin.Bat create mode 100644 Source/BuildMayaPlugin.xml create mode 100644 Source/LiveLink.mod create mode 100644 Source/MayaLiveLinkPlugin.cpp create mode 100644 Source/MayaLiveLinkPlugin2016.Build.cs create mode 100644 Source/MayaLiveLinkPlugin2016.Target.cs create mode 100644 Source/MayaLiveLinkPlugin2017.Build.cs create mode 100644 Source/MayaLiveLinkPlugin2017.Target.cs create mode 100644 Source/MayaLiveLinkPlugin2018.Build.cs create mode 100644 Source/MayaLiveLinkPlugin2018.Target.cs create mode 100644 Source/MayaLiveLinkPlugin2019.Build.cs create mode 100644 Source/MayaLiveLinkPlugin2019.Target.cs create mode 100644 Source/MayaLiveLinkUI.py diff --git a/LICENSE b/LICENSE index 19411b7..f44ea9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 UE4 Plug-ins +Copyright (c) 2019 UE4 Epic Games Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md deleted file mode 100644 index 01d185f..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# MayaLiveLinkPlugin -Plugin for Maya that send data to Unreal Engine diff --git a/ReadMe.txt b/ReadMe.txt new file mode 100644 index 0000000..75eb795 --- /dev/null +++ b/ReadMe.txt @@ -0,0 +1,2 @@ +Documentation for Maya Live Link can be found on our site: +https://docs.unrealengine.com/en-US/Engine/Animation/LiveLinkPlugin/ConnectingUnrealEngine4toMayawithLiveLink/index.html diff --git a/Source/BuildMayaPlugin.Bat b/Source/BuildMayaPlugin.Bat new file mode 100644 index 0000000..28aca16 --- /dev/null +++ b/Source/BuildMayaPlugin.Bat @@ -0,0 +1,4 @@ +@echo off + +RMDIR %~dp0Staging /S /Q +..\..\..\..\Build\BatchFiles\RunUAT.bat BuildGraph -Script=Engine/Source/Programs/NotForLicensees/MayaLiveLinkPlugin/BuildMayaPlugin.xml -Target="Stage Maya Plugin Module" \ No newline at end of file diff --git a/Source/BuildMayaPlugin.xml b/Source/BuildMayaPlugin.xml new file mode 100644 index 0000000..8200073 --- /dev/null +++ b/Source/BuildMayaPlugin.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/LiveLink.mod b/Source/LiveLink.mod new file mode 100644 index 0000000..9473d5f --- /dev/null +++ b/Source/LiveLink.mod @@ -0,0 +1 @@ ++ LiveLink 1.0 .\LiveLink \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin.cpp b/Source/MayaLiveLinkPlugin.cpp new file mode 100644 index 0000000..f9781b6 --- /dev/null +++ b/Source/MayaLiveLinkPlugin.cpp @@ -0,0 +1,1641 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "RequiredProgramMainCPPInclude.h" +#include "Misc/CommandLine.h" +#include "Async/TaskGraphInterfaces.h" +#include "Modules/ModuleManager.h" +#include "UObject/Object.h" +#include "Misc/ConfigCacheIni.h" + +#include "Roles/LiveLinkAnimationRole.h" +#include "Roles/LiveLinkAnimationTypes.h" +#include "Roles/LiveLinkCameraRole.h" +#include "Roles/LiveLinkCameraTypes.h" +#include "Roles/LiveLinkLightRole.h" +#include "Roles/LiveLinkLightTypes.h" +#include "Roles/LiveLinkTransformRole.h" +#include "Roles/LiveLinkTransformTypes.h" +#include "LiveLinkProvider.h" +#include "LiveLinkRefSkeleton.h" +#include "LiveLinkTypes.h" +#include "Misc/OutputDevice.h" + +DEFINE_LOG_CATEGORY_STATIC(LogBlankMayaPlugin, Log, All); + +IMPLEMENT_APPLICATION(MayaLiveLinkPlugin, "MayaLiveLinkPlugin"); + +// Maya includes +// For Maya 2016 the SDK has to be downloaded and installed manually for these includes to work. +#define DWORD BananaFritters +#include +#include +#include +#include //command +#include //command +#include //node +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef DWORD + +#define MCHECKERROR(STAT,MSG) \ + if (!STAT) { \ + perror(MSG); \ + return MS::kFailure; \ + } + +#define MREPORTERROR(STAT,MSG) \ + if (!STAT) { \ + perror(MSG); \ + } + +class FLiveLinkStreamedSubjectManager; + +TSharedPtr LiveLinkProvider; +TSharedPtr LiveLinkStreamManager; +FDelegateHandle ConnectionStatusChangedHandle; + +MCallbackIdArray myCallbackIds; + +MSpace::Space G_TransformSpace = MSpace::kTransform; + +bool bUEInitialized = false; + +// Execute the python command to refresh our UI +void RefreshUI() +{ + MGlobal::executeCommand("MayaLiveLinkRefreshUI"); +} + +void SetMatrixRow(double* Row, MVector Vec) +{ + Row[0] = Vec.x; + Row[1] = Vec.y; + Row[2] = Vec.z; +} + +double RadToDeg(double Rad) +{ + const double E_PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062; + return (Rad*180.0) / E_PI; +} + +double DegToRad(double Deg) +{ + const double E_PI = 3.1415926535897932384626433832795028841971693993751058209749445923078164062; + return Deg * (E_PI / 180.0f); +} + +MMatrix GetScale(const MFnIkJoint& Joint) +{ + double Scale[3]; + Joint.getScale(Scale); + MTransformationMatrix M; + M.setScale(Scale, G_TransformSpace); + return M.asMatrix(); +} + +MMatrix GetRotationOrientation(const MFnIkJoint& Joint, MTransformationMatrix::RotationOrder& RotOrder) +{ + double ScaleOrientation[3]; + Joint.getScaleOrientation(ScaleOrientation, RotOrder); + MTransformationMatrix M; + M.setRotation(ScaleOrientation, RotOrder); + return M.asMatrix(); +} + +MMatrix GetRotation(const MFnIkJoint& Joint, MTransformationMatrix::RotationOrder& RotOrder) +{ + double Rotation[3]; + Joint.getRotation(Rotation, RotOrder); + MTransformationMatrix M; + M.setRotation(Rotation, RotOrder); + return M.asMatrix(); +} + +MMatrix GetJointOrientation(const MFnIkJoint& Joint, MTransformationMatrix::RotationOrder& RotOrder) +{ + double JointOrientation[3]; + Joint.getOrientation(JointOrientation, RotOrder); + MTransformationMatrix M; + M.setRotation(JointOrientation, RotOrder); + return M.asMatrix(); +} + +MMatrix GetTranslation(const MFnIkJoint& Joint) +{ + MVector Translation = Joint.getTranslation(G_TransformSpace); + MTransformationMatrix M; + M.setTranslation(Translation, G_TransformSpace); + return M.asMatrix(); +} + +void RotateCoordinateSystemForUnreal(MMatrix& InOutMatrix) +{ + MQuaternion RotOffset; + RotOffset.setToXAxis(DegToRad(90.0)); + InOutMatrix *= RotOffset.asMatrix(); +} + +FTransform BuildUETransformFromMayaTransform(MMatrix& InMatrix) +{ + MMatrix UnrealSpaceJointMatrix; + + // from FFbxDataConverter::ConvertMatrix + for (int i = 0; i < 4; ++i) + { + double* Row = InMatrix[i]; + if (i == 1) + { + UnrealSpaceJointMatrix[i][0] = -Row[0]; + UnrealSpaceJointMatrix[i][1] = Row[1]; + UnrealSpaceJointMatrix[i][2] = -Row[2]; + UnrealSpaceJointMatrix[i][3] = -Row[3]; + } + else + { + UnrealSpaceJointMatrix[i][0] = Row[0]; + UnrealSpaceJointMatrix[i][1] = -Row[1]; + UnrealSpaceJointMatrix[i][2] = Row[2]; + UnrealSpaceJointMatrix[i][3] = Row[3]; + } + } + + //OutputRotation(FinalJointMatrix); + + MTransformationMatrix UnrealSpaceJointTransform(UnrealSpaceJointMatrix); + + // getRotation is MSpace::kTransform + double tx, ty, tz, tw; + UnrealSpaceJointTransform.getRotationQuaternion(tx, ty, tz, tw, MSpace::kWorld); + + FTransform UETrans; + UETrans.SetRotation(FQuat(tx, ty, tz, tw)); + + MVector Translation = UnrealSpaceJointTransform.getTranslation(MSpace::kWorld); + UETrans.SetTranslation(FVector(Translation.x, Translation.y, Translation.z)); + + double Scale[3]; + UnrealSpaceJointTransform.getScale(Scale, MSpace::kWorld); + UETrans.SetScale3D(FVector((float)Scale[0], (float)Scale[1], (float)Scale[2])); + return UETrans; +} + +FColor MayaColorToUnreal(MColor Color) +{ + FColor Result; + Result.R = FMath::Clamp(Color[0] * 255.0, 0.0, 255.0); + Result.G = FMath::Clamp(Color[1] * 255.0, 0.0, 255.0); + Result.B = FMath::Clamp(Color[2] * 255.0, 0.0, 255.0); + Result.A = 255; + return Result; +} + +void OutputRotation(const MMatrix& M) +{ + MTransformationMatrix TM(M); + + MEulerRotation Euler = TM.eulerRotation(); + + FVector V; + + V.X = RadToDeg(Euler[0]); + V.Y = RadToDeg(Euler[1]); + V.Z = RadToDeg(Euler[2]); + MGlobal::displayInfo(*V.ToString()); +} + +struct IStreamedEntity +{ +public: + virtual ~IStreamedEntity() {}; + + virtual bool ShouldDisplayInUI() const { return false; } + virtual MDagPath GetDagPath() const = 0; + virtual MString GetNameDisplayText() const = 0; + virtual MString GetRoleDisplayText() const = 0; + virtual MString GetSubjectTypeDisplayText() const = 0; + virtual bool ValidateSubject() const = 0; + virtual void RebuildSubjectData() = 0; + virtual void OnStream(double StreamTime, int32 FrameNumber) = 0; + virtual void SetStreamType(const FString& StreamType) = 0; +}; + +struct FStreamHierarchy +{ + FName JointName; + MFnIkJoint JointObject; + int32 ParentIndex; + + FStreamHierarchy() {} + + FStreamHierarchy(const FStreamHierarchy& Other) + : JointName(Other.JointName) + , JointObject(Other.JointObject.dagPath()) + , ParentIndex(Other.ParentIndex) + {} + + FStreamHierarchy(FName InJointName, const MDagPath& InJointPath, int32 InParentIndex) + : JointName(InJointName) + , JointObject(InJointPath) + , ParentIndex(InParentIndex) + {} +}; + +struct FLiveLinkStreamedJointHierarchySubject : IStreamedEntity +{ + FLiveLinkStreamedJointHierarchySubject(FName InSubjectName, MDagPath InRootPath) + : SubjectName(InSubjectName) + , RootDagPath(InRootPath) + , StreamMode(FCharacterStreamMode::FullHierarchy) + {} + + ~FLiveLinkStreamedJointHierarchySubject() + { + if (LiveLinkProvider.IsValid()) + { + LiveLinkProvider->RemoveSubject(SubjectName); + } + } + + virtual bool ShouldDisplayInUI() const override { return true; } + virtual MDagPath GetDagPath() const override { return RootDagPath; } + virtual MString GetNameDisplayText() const override { return MString(*SubjectName.ToString()); } + virtual MString GetRoleDisplayText() const override { return MString(*(CharacterStreamOptions[StreamMode])); } + virtual MString GetSubjectTypeDisplayText() const override { return MString("Character"); } + + virtual bool ValidateSubject() const override + { + MStatus Status; + bool bIsValid = RootDagPath.isValid(&Status); + + TCHAR* StatusMessage = TEXT("Unset"); + + if (Status == MS::kSuccess) + { + StatusMessage = TEXT("Success"); + } + else if (Status == MS::kFailure) + { + StatusMessage = TEXT("Failure"); + } + else + { + StatusMessage = TEXT("Other"); + } + + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Testing %s for removal Path:%s Valid:%s Status:%s\n"), *SubjectName.ToString(), RootDagPath.fullPathName().asWChar(), bIsValid ? TEXT("true") : TEXT("false"), StatusMessage); + if (Status != MS::kFailure && bIsValid) + { + //Path checks out as valid + MFnIkJoint Joint(RootDagPath, &Status); + + MVector returnvec = Joint.getTranslation(MSpace::kWorld, &Status); + if (Status == MS::kSuccess) + { + StatusMessage = TEXT("Success"); + } + else if (Status == MS::kFailure) + { + StatusMessage = TEXT("Failure"); + } + else + { + StatusMessage = TEXT("Other"); + } + + FPlatformMisc::LowLevelOutputDebugStringf(TEXT("\tTesting %s for removal Path:%s Valid:%s Status:%s\n"), *SubjectName.ToString(), RootDagPath.fullPathName().asWChar(), bIsValid ? TEXT("true") : TEXT("false"), StatusMessage); + } + return bIsValid; + } + + virtual void RebuildSubjectData() override + { + if (StreamMode == FCharacterStreamMode::RootOnly) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkTransformStaticData::StaticStruct()); + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(StaticData)); + } + else if (StreamMode == FCharacterStreamMode::FullHierarchy) + { + JointsToStream.Reset(); + + FLiveLinkStaticDataStruct StaticData(FLiveLinkSkeletonStaticData::StaticStruct()); + FLiveLinkSkeletonStaticData& AnimationData = *StaticData.Cast(); + + MItDag::TraversalType traversalType = MItDag::kBreadthFirst; + MFn::Type filter = MFn::kJoint; + + MStatus status; + MItDag JointIterator; + JointIterator.reset(RootDagPath, MItDag::kDepthFirst, MFn::kJoint); + + //Build Hierarchy + TArray ParentIndexStack; + ParentIndexStack.SetNum(100, false); + + int32 Index = 0; + + for (; !JointIterator.isDone(); JointIterator.next()) + { + uint32 Depth = JointIterator.depth(); + if (Depth >= (uint32)ParentIndexStack.Num()) + { + ParentIndexStack.SetNum(Depth + 1); + } + ParentIndexStack[Depth] = Index++; + + int32 ParentIndex = Depth == 0 ? -1 : ParentIndexStack[Depth - 1]; + + MDagPath JointPath; + status = JointIterator.getPath(JointPath); + MFnIkJoint JointObject(JointPath); + + FName JointName(JointObject.name().asChar()); + + JointsToStream.Add(FStreamHierarchy(JointName, JointPath, ParentIndex)); + AnimationData.BoneNames.Add(JointName); + AnimationData.BoneParents.Add(ParentIndex); + } + + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticData)); + } + } + + virtual void OnStream(double StreamTime, int32 FrameNumber) override + { + if (StreamMode == FCharacterStreamMode::RootOnly) + { + MFnTransform TransformNode(RootDagPath); + + MMatrix Transform = TransformNode.transformation().asMatrix(); + + FLiveLinkFrameDataStruct FrameData(FLiveLinkTransformFrameData::StaticStruct()); + FLiveLinkTransformFrameData& TransformData = *FrameData.Cast(); + + RotateCoordinateSystemForUnreal(Transform); + + // Convert Maya Camera orientation to Unreal + TransformData.Transform = BuildUETransformFromMayaTransform(Transform); + + TransformData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + else if (StreamMode == FCharacterStreamMode::FullHierarchy) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkAnimationFrameData::StaticStruct()); + FLiveLinkAnimationFrameData& AnimationData = *FrameData.Cast(); + + AnimationData.Transforms.Reserve(JointsToStream.Num()); + + TArray InverseScales; + InverseScales.Reserve(JointsToStream.Num()); + + for (int32 Idx = 0; Idx < JointsToStream.Num(); ++Idx) + { + const FStreamHierarchy& H = JointsToStream[Idx]; + + MTransformationMatrix::RotationOrder RotOrder = H.JointObject.rotationOrder(); + + MMatrix JointScale = GetScale(H.JointObject); + InverseScales.Add(JointScale.inverse()); + + MMatrix ParentInverseScale = (H.ParentIndex == -1) ? MMatrix::identity : InverseScales[H.ParentIndex]; + + MMatrix MayaSpaceJointMatrix = JointScale * + GetRotationOrientation(H.JointObject, RotOrder) * + GetRotation(H.JointObject, RotOrder) * + GetJointOrientation(H.JointObject, RotOrder) * + ParentInverseScale * + GetTranslation(H.JointObject); + + if (Idx == 0) // rotate the root joint to get the correct character rotation in Unreal + { + RotateCoordinateSystemForUnreal(MayaSpaceJointMatrix); + } + + AnimationData.Transforms.Add(BuildUETransformFromMayaTransform(MayaSpaceJointMatrix)); + } + + AnimationData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + } + + virtual void SetStreamType(const FString& StreamTypeIn) override + { + for (int32 StreamTypeIdx = 0; StreamTypeIdx < CharacterStreamOptions.Num(); ++StreamTypeIdx) + { + if (CharacterStreamOptions[StreamTypeIdx] == StreamTypeIn && StreamMode != (FCharacterStreamMode)StreamTypeIdx) + { + StreamMode = (FCharacterStreamMode)StreamTypeIdx; + RebuildSubjectData(); + return; + } + } + } + +private: + FName SubjectName; + MDagPath RootDagPath; + TArray JointsToStream; + + const TArray CharacterStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy") }; + enum FCharacterStreamMode + { + RootOnly, + FullHierarchy, + }; + FCharacterStreamMode StreamMode; +}; + +struct FLiveLinkBaseCameraStreamedSubject : public IStreamedEntity +{ +public: + FLiveLinkBaseCameraStreamedSubject(FName InSubjectName) : SubjectName(InSubjectName), StreamMode(FCameraStreamMode::Camera) {} + + ~FLiveLinkBaseCameraStreamedSubject() + { + if (LiveLinkProvider.IsValid()) + { + LiveLinkProvider->RemoveSubject(SubjectName); + } + } + + virtual MString GetNameDisplayText() const override { return *SubjectName.ToString(); } + virtual MString GetRoleDisplayText() const override { return MString(*(CameraStreamOptions[StreamMode])); } + virtual MString GetSubjectTypeDisplayText() const override { return MString("Camera"); } + + virtual bool ValidateSubject() const override { return true; } + + virtual void RebuildSubjectData() override + { + if (StreamMode == FCameraStreamMode::RootOnly) + { + FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); + } + else if (StreamMode == FCameraStreamMode::FullHierarchy) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkSkeletonStaticData::StaticStruct()); + FLiveLinkSkeletonStaticData& AnimationData = *StaticData.Cast(); + + AnimationData.BoneNames.Add(FName("root")); + AnimationData.BoneParents.Add(-1); + + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticData)); + } + else if (StreamMode == FCameraStreamMode::Camera) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkCameraStaticData::StaticStruct()); + FLiveLinkCameraStaticData& CameraData = *StaticData.Cast(); + CameraData.bIsFieldOfViewSupported = true; + CameraData.bIsAspectRatioSupported = true; + CameraData.bIsFocalLengthSupported = true; + CameraData.bIsProjectionModeSupported = true; + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkCameraRole::StaticClass(), MoveTemp(StaticData)); + } + } + + void StreamCamera(MDagPath CameraPath, double StreamTime, int32 FrameNumber) + { + MStatus Status; + bool bIsValid = CameraPath.isValid(&Status); + + if (bIsValid && Status == MStatus::kSuccess) + { + MFnCamera C(CameraPath); + + MPoint EyeLocation = C.eyePoint(MSpace::kWorld); + + MMatrix CameraTransformMatrix; + SetMatrixRow(CameraTransformMatrix[0], C.rightDirection(MSpace::kWorld)); + SetMatrixRow(CameraTransformMatrix[1], C.viewDirection(MSpace::kWorld)); + SetMatrixRow(CameraTransformMatrix[2], C.upDirection(MSpace::kWorld)); + SetMatrixRow(CameraTransformMatrix[3], EyeLocation); + + RotateCoordinateSystemForUnreal(CameraTransformMatrix); + FTransform CameraTransform = BuildUETransformFromMayaTransform(CameraTransformMatrix); + // Convert Maya Camera orientation to Unreal + CameraTransform.SetRotation(CameraTransform.GetRotation() * FRotator(0.0f, -90.0f, 0.0f).Quaternion()); + + if (StreamMode == FCameraStreamMode::RootOnly) + { + FLiveLinkFrameDataStruct TransformData = (FLiveLinkTransformFrameData::StaticStruct()); + FLiveLinkTransformFrameData& CameraTransformData = *TransformData.Cast(); + CameraTransformData.Transform = CameraTransform; + CameraTransformData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(TransformData)); + } + else if (StreamMode == FCameraStreamMode::FullHierarchy) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkAnimationFrameData::StaticStruct()); + FLiveLinkAnimationFrameData& AnimationData = *FrameData.Cast(); + + AnimationData.Transforms.Add(CameraTransform); + AnimationData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + else if (StreamMode == FCameraStreamMode::Camera) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkCameraFrameData::StaticStruct()); + FLiveLinkCameraFrameData& CameraData = *FrameData.Cast(); + + CameraData.FieldOfView = C.horizontalFieldOfView(); + CameraData.AspectRatio = C.aspectRatio(); + CameraData.FocalLength = C.focalLength(); + CameraData.ProjectionMode = C.isOrtho() ? ELiveLinkCameraProjectionMode::Orthographic : ELiveLinkCameraProjectionMode::Perspective; + + CameraData.Transform = CameraTransform; + CameraData.WorldTime = StreamTime; + + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + } + } + + virtual void SetStreamType(const FString& StreamTypeIn) override + { + for (int32 StreamTypeIdx = 0; StreamTypeIdx < CameraStreamOptions.Num(); ++StreamTypeIdx) + { + if (CameraStreamOptions[StreamTypeIdx] == StreamTypeIn && StreamMode != (FCameraStreamMode)StreamTypeIdx) + { + StreamMode = (FCameraStreamMode)StreamTypeIdx; + RebuildSubjectData(); + return; + } + } + } + +protected: + FName SubjectName; + + const TArray CameraStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy"), TEXT("Camera") }; + enum FCameraStreamMode + { + RootOnly, + FullHierarchy, + Camera, + }; + FCameraStreamMode StreamMode; +}; + +struct FLiveLinkStreamedActiveCamera : public FLiveLinkBaseCameraStreamedSubject +{ +public: + FLiveLinkStreamedActiveCamera() : FLiveLinkBaseCameraStreamedSubject(ActiveCameraName) {} + + MDagPath CurrentActiveCameraDag; + + virtual MDagPath GetDagPath() const override { return CurrentActiveCameraDag; } + + virtual void OnStream(double StreamTime, int32 FrameNumber) override + { + MStatus Status; + M3dView ActiveView = M3dView::active3dView(&Status); + if (Status == MStatus::kSuccess) + { + MDagPath CameraDag; + if (ActiveView.getCamera(CameraDag) == MStatus::kSuccess) + { + CurrentActiveCameraDag = CameraDag; + } + } + + StreamCamera(CurrentActiveCameraDag, StreamTime, FrameNumber); + } + +private: + static FName ActiveCameraName; +}; + +FName FLiveLinkStreamedActiveCamera::ActiveCameraName("EditorActiveCamera"); + +struct FLiveLinkStreamedCameraSubject : FLiveLinkBaseCameraStreamedSubject +{ +public: + virtual MDagPath GetDagPath() const override { return CameraPath; } + + FLiveLinkStreamedCameraSubject(FName InSubjectName, MDagPath InDagPath) : FLiveLinkBaseCameraStreamedSubject(InSubjectName), CameraPath(InDagPath) {} + + virtual bool ShouldDisplayInUI() const override { return true; } + + virtual void OnStream(double StreamTime, int32 FrameNumber) override + { + StreamCamera(CameraPath, StreamTime, FrameNumber); + } + +private: + MDagPath CameraPath; +}; + +struct FLiveLinkStreamedLightSubject : IStreamedEntity +{ +public: + FLiveLinkStreamedLightSubject(FName InSubjectName, MDagPath InRootPath) + : SubjectName(InSubjectName) + , RootDagPath(InRootPath) + , StreamMode(FLightStreamMode::Light) + {} + + ~FLiveLinkStreamedLightSubject() + { + if (LiveLinkProvider.IsValid()) + { + LiveLinkProvider->RemoveSubject(SubjectName); + } + } + + virtual bool ShouldDisplayInUI() const override { return true; } + virtual MDagPath GetDagPath() const override { return RootDagPath; } + virtual MString GetNameDisplayText() const override { return MString(*SubjectName.ToString()); } + virtual MString GetRoleDisplayText() const override { return MString(*(LightStreamOptions[StreamMode])); } + virtual MString GetSubjectTypeDisplayText() const override { return MString("Light"); } + + virtual bool ValidateSubject() const override { return true; } + + virtual void RebuildSubjectData() override + { + if (StreamMode == FLightStreamMode::RootOnly) + { + FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); + } + else if (StreamMode == FLightStreamMode::FullHierarchy) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkSkeletonStaticData::StaticStruct()); + FLiveLinkSkeletonStaticData& AnimationData = *StaticData.Cast(); + + AnimationData.BoneNames.Add(FName("root")); + AnimationData.BoneParents.Add(-1); + + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticData)); + } + else if (StreamMode == FLightStreamMode::Light) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkLightStaticData::StaticStruct()); + FLiveLinkLightStaticData& LightData = *StaticData.Cast(); + LightData.bIsIntensitySupported = true; + LightData.bIsLightColorSupported = true; + const bool bIsSpotLight = RootDagPath.hasFn(MFn::kSpotLight); + LightData.bIsInnerConeAngleSupported = bIsSpotLight; + LightData.bIsOuterConeAngleSupported = bIsSpotLight; + + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkLightRole::StaticClass(), MoveTemp(StaticData)); + } + } + + virtual void OnStream(double StreamTime, int32 FrameNumber) override + { + MFnTransform TransformNode(RootDagPath); + MMatrix MayaTransform = TransformNode.transformation().asMatrix(); + RotateCoordinateSystemForUnreal(MayaTransform); + const FTransform UnrealTransform = BuildUETransformFromMayaTransform(MayaTransform); + + if (StreamMode == FLightStreamMode::RootOnly) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkTransformFrameData::StaticStruct()); + FLiveLinkTransformFrameData& TransformData = *FrameData.Cast(); + + TransformData.Transform = UnrealTransform; + TransformData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + else if (StreamMode == FLightStreamMode::FullHierarchy) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkAnimationFrameData::StaticStruct()); + FLiveLinkAnimationFrameData& AnimationData = *FrameData.Cast(); + + AnimationData.Transforms.Add(UnrealTransform); + AnimationData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + else if (StreamMode == FLightStreamMode::Light) + { + + FLiveLinkFrameDataStruct FrameData(FLiveLinkLightFrameData::StaticStruct()); + FLiveLinkLightFrameData& TransformData = *FrameData.Cast(); + + TransformData.Transform = UnrealTransform; + TransformData.WorldTime = StreamTime; + + MFnLight Light(RootDagPath); + TransformData.Intensity = Light.intensity(); + TransformData.LightColor = MayaColorToUnreal(Light.color()); + + if (RootDagPath.hasFn(MFn::kSpotLight)) + { + MFnSpotLight SpotLight(RootDagPath); + TransformData.InnerConeAngle = static_cast(SpotLight.coneAngle()); + TransformData.OuterConeAngle = static_cast(SpotLight.penumbraAngle()); + } + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + } + + virtual void SetStreamType(const FString& StreamTypeIn) override + { + for (int32 StreamTypeIdx = 0; StreamTypeIdx < LightStreamOptions.Num(); ++StreamTypeIdx) + { + if (LightStreamOptions[StreamTypeIdx] == StreamTypeIn && StreamMode != (FLightStreamMode)StreamTypeIdx) + { + StreamMode = (FLightStreamMode)StreamTypeIdx; + RebuildSubjectData(); + return; + } + } + } + +private: + FName SubjectName; + MDagPath RootDagPath; + + const TArray LightStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy"), TEXT("Light") }; + enum FLightStreamMode + { + RootOnly, + FullHierarchy, + Light, + }; + FLightStreamMode StreamMode; +}; + +struct FLiveLinkStreamedPropSubject : IStreamedEntity +{ +public: + FLiveLinkStreamedPropSubject(FName InSubjectName, MDagPath InRootPath) + : SubjectName(InSubjectName) + , RootDagPath(InRootPath) + , StreamMode(FPropStreamMode::RootOnly) + {} + + ~FLiveLinkStreamedPropSubject() + { + if (LiveLinkProvider.IsValid()) + { + LiveLinkProvider->RemoveSubject(SubjectName); + } + } + + virtual bool ShouldDisplayInUI() const override { return true; } + virtual MDagPath GetDagPath() const override { return RootDagPath; } + virtual MString GetNameDisplayText() const override { return MString(*SubjectName.ToString()); } + virtual MString GetRoleDisplayText() const override { return MString(*(PropStreamOptions[StreamMode])); } + virtual MString GetSubjectTypeDisplayText() const override { return MString("Prop"); } + + virtual bool ValidateSubject() const override {return true;} + + virtual void RebuildSubjectData() override + { + if (StreamMode == FPropStreamMode::RootOnly) + { + FLiveLinkStaticDataStruct TransformData(FLiveLinkTransformStaticData::StaticStruct()); + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkTransformRole::StaticClass(), MoveTemp(TransformData)); + } + else if (StreamMode == FPropStreamMode::FullHierarchy) + { + FLiveLinkStaticDataStruct StaticData(FLiveLinkSkeletonStaticData::StaticStruct()); + FLiveLinkSkeletonStaticData& AnimationData = *StaticData.Cast(); + + AnimationData.BoneNames.Add(FName("root")); + AnimationData.BoneParents.Add(-1); + + LiveLinkProvider->UpdateSubjectStaticData(SubjectName, ULiveLinkAnimationRole::StaticClass(), MoveTemp(StaticData)); + } + } + + virtual void OnStream(double StreamTime, int32 FrameNumber) override + { + MFnTransform TransformNode(RootDagPath); + MMatrix MayaTransform = TransformNode.transformation().asMatrix(); + RotateCoordinateSystemForUnreal(MayaTransform); + const FTransform UnrealTransform = BuildUETransformFromMayaTransform(MayaTransform); + + if (StreamMode == FPropStreamMode::RootOnly) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkTransformFrameData::StaticStruct()); + FLiveLinkTransformFrameData& TransformData = *FrameData.Cast(); + + TransformData.Transform = UnrealTransform; + TransformData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + else if (StreamMode == FPropStreamMode::FullHierarchy) + { + FLiveLinkFrameDataStruct FrameData(FLiveLinkAnimationFrameData::StaticStruct()); + FLiveLinkAnimationFrameData& AnimationData = *FrameData.Cast(); + + AnimationData.Transforms.Add(UnrealTransform); + AnimationData.WorldTime = StreamTime; + LiveLinkProvider->UpdateSubjectFrameData(SubjectName, MoveTemp(FrameData)); + } + } + + virtual void SetStreamType(const FString& StreamTypeIn) override + { + for (int32 StreamTypeIdx = 0; StreamTypeIdx < PropStreamOptions.Num(); ++StreamTypeIdx) + { + if (PropStreamOptions[StreamTypeIdx] == StreamTypeIn && StreamMode != (FPropStreamMode)StreamTypeIdx) + { + StreamMode = (FPropStreamMode)StreamTypeIdx; + RebuildSubjectData(); + return; + } + } + } + +private: + FName SubjectName; + MDagPath RootDagPath; + + const TArray PropStreamOptions = { TEXT("Root Only"), TEXT("Full Hierarchy") }; + enum FPropStreamMode + { + RootOnly, + FullHierarchy, + }; + FPropStreamMode StreamMode; +}; + +class FLiveLinkStreamedSubjectManager +{ +private: + TArray> Subjects; + + void ValidateSubjects() + { + Subjects.RemoveAll([](const TSharedPtr& Item) + { + return !Item->ValidateSubject(); + }); + RefreshUI(); + } + +public: + + FLiveLinkStreamedSubjectManager() + { + Reset(); + } + + void GetSubjectNames(TArray& Entries) const + { + for (const TSharedPtr& Subject : Subjects) + { + if (Subject->ShouldDisplayInUI()) + { + Entries.Add(Subject->GetNameDisplayText()); + } + } + } + + void GetSubjectPaths(TArray& Entries) const + { + for (const TSharedPtr& Subject : Subjects) + { + if (Subject->ShouldDisplayInUI()) + { + Entries.Add(Subject->GetDagPath().fullPathName()); + } + } + } + + void GetSubjectRoles(TArray& Entries) const + { + for (const TSharedPtr& Subject : Subjects) + { + if (Subject->ShouldDisplayInUI()) + { + Entries.Add(Subject->GetRoleDisplayText()); + } + } + } + + void GetSubjectTypes(TArray& Entries) const + { + for (const TSharedPtr& Subject : Subjects) + { + if (Subject->ShouldDisplayInUI()) + { + Entries.Add(Subject->GetSubjectTypeDisplayText()); + } + } + } + + template + TSharedPtr AddSubjectOfType(ArgsType&&... Args) + { + TSharedPtr Subject = MakeShareable(new SubjectType(Args...)); + + Subject->RebuildSubjectData(); + + int32 FrameNumber = MAnimControl::currentTime().value(); + Subject->OnStream(FPlatformTime::Seconds(), FrameNumber); + + Subjects.Add(Subject); + return Subject; + } + + void AddJointHeirarchySubject(FName SubjectName, MDagPath RootPath) + { + AddSubjectOfType(SubjectName, RootPath); + } + + void AddCameraSubject(FName SubjectName, MDagPath RootPath) + { + AddSubjectOfType(SubjectName, RootPath); + } + + void AddLightSubject(FName SubjectName, MDagPath RootPath) + { + AddSubjectOfType(SubjectName, RootPath); + } + + void AddPropSubject(FName SubjectName, MDagPath RootPath) + { + AddSubjectOfType(SubjectName, RootPath); + } + + void RemoveSubject(MString PathOfSubjectToRemove) + { + for (int32 Index = Subjects.Num() - 1; Index >= 0; --Index) + { + if (Subjects[Index]->ShouldDisplayInUI()) + { + if (Subjects[Index]->GetDagPath().fullPathName() == PathOfSubjectToRemove) + { + Subjects.RemoveAt(Index); + break; + } + } + } + } + + void ChangeSubjectName(MString SubjectDagPath, MString NewName) + { + if (IStreamedEntity* Subject = GetSubjectByDagPath(SubjectDagPath)) + { + const MDagPath PathBackup = Subject->GetDagPath(); // store so we can re-create the subject + RemoveSubject(SubjectDagPath); + + if (PathBackup.hasFn(MFn::kJoint)) + { + AddJointHeirarchySubject(NewName.asChar(), PathBackup); + } + else if (PathBackup.hasFn(MFn::kCamera)) + { + AddCameraSubject(NewName.asChar(), PathBackup); + } + else if (PathBackup.hasFn(MFn::kLight)) + { + AddLightSubject(NewName.asChar(), PathBackup); + } + else + { + AddPropSubject(NewName.asChar(), PathBackup); + } + } + } + + void ChangeStreamType(MString SubjectPathIn, MString StreamTypeIn) + { + if (IStreamedEntity* Subject = GetSubjectByDagPath(SubjectPathIn)) + { + const FString StreamType(StreamTypeIn.asChar()); + Subject->SetStreamType(StreamType); + } + } + + IStreamedEntity* GetSubjectByDagPath(MString Path) + { + TSharedPtr* Found = Subjects.FindByPredicate([&Path](TSharedPtr& Subject) + { + if (Subject.IsValid()) + { + if (Subject->GetDagPath().fullPathName() == Path) + { + return Subject; + } + } + return TSharedPtr(); + }); + + return (*Found).Get(); + } + + void Reset() + { + Subjects.Reset(); + AddSubjectOfType(); + } + + void RebuildSubjects() + { + ValidateSubjects(); + for (const TSharedPtr& Subject : Subjects) + { + Subject->RebuildSubjectData(); + } + } + + void StreamSubjects() const + { + double StreamTime = FPlatformTime::Seconds(); + int32 FrameNumber = MAnimControl::currentTime().value(); + + for (const TSharedPtr& Subject : Subjects) + { + Subject->OnStream(StreamTime, FrameNumber); + } + } +}; + +const MString LiveLinkSubjectNamesCommandName("LiveLinkSubjectNames"); + +class LiveLinkSubjectNamesCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkSubjectNamesCommand(); } + + MStatus doIt(const MArgList& args) override + { + TArray SubjectNames; + LiveLinkStreamManager->GetSubjectNames(SubjectNames); + + for (const MString& Entry : SubjectNames) + { + appendToResult(Entry); + } + + return MS::kSuccess; + } +}; + +const MString LiveLinkSubjectPathsCommandName("LiveLinkSubjectPaths"); + +class LiveLinkSubjectPathsCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkSubjectPathsCommand(); } + + MStatus doIt(const MArgList& args) override + { + TArray SubjectPaths; + LiveLinkStreamManager->GetSubjectPaths(SubjectPaths); + + for (const MString& Entry : SubjectPaths) + { + appendToResult(Entry); + } + + return MS::kSuccess; + } +}; + +const MString LiveLinkSubjectRolesCommandName("LiveLinkSubjectRoles"); + +class LiveLinkSubjectRolesCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkSubjectRolesCommand(); } + + MStatus doIt(const MArgList& args) override + { + TArray SubjectRoles; + LiveLinkStreamManager->GetSubjectRoles(SubjectRoles); + + for (const MString& Entry : SubjectRoles) + { + appendToResult(Entry); + } + + return MS::kSuccess; + } +}; + +const MString LiveLinkSubjectTypesCommandName("LiveLinkSubjectTypes"); + +class LiveLinkSubjectTypesCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkSubjectTypesCommand(); } + + MStatus doIt(const MArgList& args) override + { + TArray SubjectTypes; + LiveLinkStreamManager->GetSubjectTypes(SubjectTypes); + + for (const MString& Entry : SubjectTypes) + { + appendToResult(Entry); + } + + return MS::kSuccess; + } +}; + +const MString LiveLinkAddSelectionCommandName("LiveLinkAddSelection"); + +class LiveLinkAddSelectionCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkAddSelectionCommand(); } + + MStatus doIt(const MArgList& args) override + { + MSelectionList SelectedItems; + MGlobal::getActiveSelectionList(SelectedItems); + + for (unsigned int i = 0; i < SelectedItems.length(); ++i) + { + MObject SelectedRoot; + SelectedItems.getDependNode(i, SelectedRoot); + + bool ItemAdded = false; + + MItDag DagIterator; + DagIterator.reset(SelectedRoot); + // first try to find a specific subject item under the selected root item. + // we iterate through the DAG to find items in groups/sets and to be able to find the Shape compoments which + // hold the interesting properties. + while (!DagIterator.isDone() && !ItemAdded) + { + MDagPath CurrentItemPath; + if (!DagIterator.getPath(CurrentItemPath)) + continue; + + MFnDagNode CurrentNode(CurrentItemPath); + const char* SubjectName = CurrentNode.name().asChar(); + + if (CurrentItemPath.hasFn(MFn::kJoint)) + { + LiveLinkStreamManager->AddJointHeirarchySubject(SubjectName, CurrentItemPath); + ItemAdded = true; + } + else if (CurrentItemPath.hasFn(MFn::kCamera)) + { + LiveLinkStreamManager->AddCameraSubject(SubjectName, CurrentItemPath); + ItemAdded = true; + } + else if (CurrentItemPath.hasFn(MFn::kLight)) + { + LiveLinkStreamManager->AddLightSubject(SubjectName, CurrentItemPath); + ItemAdded = true; + } + + if (ItemAdded) + { + MGlobal::displayInfo(MString("LiveLinkAddSubjectCommand ") + SubjectName); + } + + DagIterator.next(); + } + + // if there was no specific item, we assume that the selected item is a prop. + // the props are handled differently because almost everything has a kTransform function set, so if a subject + // is under a group node or in a set, the group would be added as a prop otherwise. + if (!ItemAdded) + { + if (SelectedRoot.hasFn(MFn::kTransform)) + { + MFnDagNode DagNode(SelectedRoot); + + MDagPath SubjectPath; + DagNode.getPath(SubjectPath); + + LiveLinkStreamManager->AddPropSubject(DagNode.name().asChar(), SubjectPath); + } + } + } + return MS::kSuccess; + } +}; + +const MString LiveLinkRemoveSubjectCommandName("LiveLinkRemoveSubject"); + +class LiveLinkRemoveSubjectCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkRemoveSubjectCommand(); } + + MStatus doIt(const MArgList& args) override + { + MSyntax Syntax; + Syntax.addArg(MSyntax::kString); + + MArgDatabase argData(Syntax, args); + + MString SubjectToRemove; + argData.getCommandArgument(0, SubjectToRemove); + + LiveLinkStreamManager->RemoveSubject(SubjectToRemove); + + return MS::kSuccess; + } +}; + +const MString LiveLinkChangeSubjectNameCommandName("LiveLinkChangeSubjectName"); + +class LiveLinkChangeSubjectNameCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkChangeSubjectNameCommand(); } + + MStatus doIt(const MArgList& args) override + { + MSyntax Syntax; + Syntax.addArg(MSyntax::kString); + Syntax.addArg(MSyntax::kString); + + MArgDatabase argData(Syntax, args); + + MString SubjectDagPath; + MString NewName; + argData.getCommandArgument(0, SubjectDagPath); + argData.getCommandArgument(1, NewName); + + LiveLinkStreamManager->ChangeSubjectName(SubjectDagPath, NewName); + + return MS::kSuccess; + } +}; + +const MString LiveLinkConnectionStatusCommandName("LiveLinkConnectionStatus"); + +class LiveLinkConnectionStatusCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkConnectionStatusCommand(); } + + MStatus doIt(const MArgList& args) override + { + MString ConnectionStatus("No Provider (internal error)"); + bool bConnection = false; + + if(LiveLinkProvider.IsValid()) + { + if (LiveLinkProvider->HasConnection()) + { + ConnectionStatus = "Connected"; + bConnection = true; + } + else + { + ConnectionStatus = "No Connection"; + } + } + + appendToResult(ConnectionStatus); + appendToResult(bConnection); + + return MS::kSuccess; + } +}; + +const MString LiveLinkChangeSubjectStreamTypeCommandName("LiveLinkChangeSubjectStreamType"); + +class LiveLinkChangeSubjectStreamTypeCommand : public MPxCommand +{ +public: + static void cleanup() {} + static void* creator() { return new LiveLinkChangeSubjectStreamTypeCommand(); } + + MStatus doIt(const MArgList& args) override + { + MSyntax Syntax; + Syntax.addArg(MSyntax::kString); + Syntax.addArg(MSyntax::kString); + + MArgDatabase argData(Syntax, args); + + MString SubjectPath; + argData.getCommandArgument(0, SubjectPath); + MString StreamType; + argData.getCommandArgument(1, StreamType); + + LiveLinkStreamManager->ChangeStreamType(SubjectPath, StreamType); + + return MS::kSuccess; + } +}; + +void OnForceChange(MTime& time, void* clientData) +{ + LiveLinkStreamManager->StreamSubjects(); +} + +class FMayaOutputDevice : public FOutputDevice +{ +public: + FMayaOutputDevice() : bAllowLogVerbosity(false) {} + + virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override + { + if ((bAllowLogVerbosity && Verbosity <= ELogVerbosity::Log) || (Verbosity <= ELogVerbosity::Display)) + { + MGlobal::displayInfo(V); + } + } + +private: + + bool bAllowLogVerbosity; + +}; + +void OnScenePreOpen(void* client) +{ + LiveLinkStreamManager->Reset(); + RefreshUI(); +} + +void OnSceneOpen(void* client) +{ + //BuildStreamHierarchyData(); +} + +void AllDagChangesCallback( + MDagMessage::DagMessage msgType, + MDagPath &child, + MDagPath &parent, + void *clientData) +{ + LiveLinkStreamManager->RebuildSubjects(); +} + +void OnConnectionStatusChanged() +{ + MGlobal::executeCommand("MayaLiveLinkRefreshConnectionUI"); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +TMap PostRenderCallbackIds; +TMap ViewportDeletedCallbackIds; + +void OnPostRenderViewport(const MString &str, void* ClientData) +{ + LiveLinkStreamManager->StreamSubjects(); +} + +void OnViewportClosed(void* ClientData) +{ + uintptr_t ViewIndex = reinterpret_cast(ClientData); + + MMessage::removeCallback(PostRenderCallbackIds[ViewIndex]); + PostRenderCallbackIds.Remove(ViewIndex); + + MMessage::removeCallback(ViewportDeletedCallbackIds[ViewIndex]); + ViewportDeletedCallbackIds.Remove(ViewIndex); +} + +void ClearViewportCallbacks() +{ + for (TPair& Pair : PostRenderCallbackIds) + { + MMessage::removeCallback(Pair.Value); + } + PostRenderCallbackIds.Reset(); + + for (TPair& Pair : ViewportDeletedCallbackIds) + { + MMessage::removeCallback(Pair.Value); + } + ViewportDeletedCallbackIds.Reset(); +} + +MStatus RefreshViewportCallbacks() +{ + MStatus ExitStatus; + + if (int(M3dView::numberOf3dViews()) != PostRenderCallbackIds.Num()) + { + ClearViewportCallbacks(); + + static MString ListEditorPanelsCmd = "gpuCacheListModelEditorPanels"; + + MStringArray EditorPanels; + ExitStatus = MGlobal::executeCommand(ListEditorPanelsCmd, EditorPanels); + MCHECKERROR(ExitStatus, "gpuCacheListModelEditorPanels"); + + if (ExitStatus == MStatus::kSuccess) + { + for (uintptr_t i = 0; i < EditorPanels.length(); ++i) + { + MStatus Status; + MCallbackId CallbackId = MUiMessage::add3dViewPostRenderMsgCallback(EditorPanels[i], OnPostRenderViewport, NULL, &Status); + + MREPORTERROR(Status, "MUiMessage::add3dViewPostRenderMsgCallback()"); + + if (Status != MStatus::kSuccess) + { + ExitStatus = MStatus::kFailure; + continue; + } + + PostRenderCallbackIds.Add(i, CallbackId); + + CallbackId = MUiMessage::addUiDeletedCallback(EditorPanels[i], OnViewportClosed, reinterpret_cast(i), &Status); + + MREPORTERROR(Status, "MUiMessage::addUiDeletedCallback()"); + + if (Status != MStatus::kSuccess) + { + ExitStatus = MStatus::kFailure; + continue; + } + ViewportDeletedCallbackIds.Add(i, CallbackId); + } + } + } + + return ExitStatus; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void OnInterval(float elapsedTime, float lastTime, void* clientData) +{ + //No good way to check for new views being created, so just periodically refresh our list + RefreshViewportCallbacks(); + + OnConnectionStatusChanged(); + + FTicker::GetCoreTicker().Tick(elapsedTime); +} + +/** +* This function is called by Maya when the plugin becomes loaded +* +* @param MayaPluginObject The Maya object that represents our plugin +* +* @return MS::kSuccess if everything went OK and the plugin is ready to use +*/ +DLLEXPORT MStatus initializePlugin(MObject MayaPluginObject) +{ + if(!bUEInitialized) + { + GEngineLoop.PreInit(TEXT("MayaLiveLinkPlugin -Messaging")); + ProcessNewlyLoadedUObjects(); + + // ensure target platform manager is referenced early as it must be created on the main thread + GetTargetPlatformManager(); + + // Tell the module manager is may now process newly-loaded UObjects when new C++ modules are loaded + FModuleManager::Get().StartProcessingNewlyLoadedObjects(); + + FModuleManager::Get().LoadModule(TEXT("UdpMessaging")); + + GLog->TearDown(); //clean up existing output devices + GLog->AddOutputDevice(new FMayaOutputDevice()); //Add Maya output device + + LiveLinkProvider = ILiveLinkProvider::CreateLiveLinkProvider(TEXT("Maya Live Link")); + ConnectionStatusChangedHandle = LiveLinkProvider->RegisterConnStatusChangedHandle(FLiveLinkProviderConnectionStatusChanged::FDelegate::CreateStatic(&OnConnectionStatusChanged)); + + bUEInitialized = true; // Dont redo this part if someone unloads and reloads our plugin + } + + // Tell Maya about our plugin + MFnPlugin MayaPlugin( + MayaPluginObject, + "MayaLiveLinkPlugin", + "v1.0"); + + // We do not tick the core engine but we need to tick the ticker to make sure the message bus endpoint in LiveLinkProvider is + // up to date + FTicker::GetCoreTicker().Tick(1.f); + + LiveLinkStreamManager = MakeShareable(new FLiveLinkStreamedSubjectManager()); + + + MCallbackId forceUpdateCallbackId = MDGMessage::addForceUpdateCallback((MMessage::MTimeFunction)OnForceChange); + myCallbackIds.append(forceUpdateCallbackId); + + MCallbackId ScenePreOpenedCallbackID = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, (MMessage::MBasicFunction)OnScenePreOpen); + myCallbackIds.append(ScenePreOpenedCallbackID); + + MCallbackId SceneOpenedCallbackId = MSceneMessage::addCallback(MSceneMessage::kAfterOpen, (MMessage::MBasicFunction)OnSceneOpen); + myCallbackIds.append(SceneOpenedCallbackId); + + MCallbackId dagChangedCallbackId = MDagMessage::addAllDagChangesCallback(AllDagChangesCallback); + myCallbackIds.append(dagChangedCallbackId); + + // Update function every 5 seconds + MCallbackId timerCallback = MTimerMessage::addTimerCallback(5.f, (MMessage::MElapsedTimeFunction)OnInterval); + myCallbackIds.append(timerCallback); + + MayaPlugin.registerCommand(LiveLinkSubjectNamesCommandName, LiveLinkSubjectNamesCommand::creator); + MayaPlugin.registerCommand(LiveLinkSubjectPathsCommandName, LiveLinkSubjectPathsCommand::creator); + MayaPlugin.registerCommand(LiveLinkSubjectRolesCommandName, LiveLinkSubjectRolesCommand::creator); + MayaPlugin.registerCommand(LiveLinkSubjectTypesCommandName, LiveLinkSubjectTypesCommand::creator); + MayaPlugin.registerCommand(LiveLinkAddSelectionCommandName, LiveLinkAddSelectionCommand::creator); + MayaPlugin.registerCommand(LiveLinkRemoveSubjectCommandName, LiveLinkRemoveSubjectCommand::creator); + MayaPlugin.registerCommand(LiveLinkChangeSubjectNameCommandName, LiveLinkChangeSubjectNameCommand::creator); + MayaPlugin.registerCommand(LiveLinkConnectionStatusCommandName, LiveLinkConnectionStatusCommand::creator); + MayaPlugin.registerCommand(LiveLinkChangeSubjectStreamTypeCommandName, LiveLinkChangeSubjectStreamTypeCommand::creator); + + // Print to Maya's output window, too! + UE_LOG(LogBlankMayaPlugin, Display, TEXT("MayaLiveLinkPlugin initialized")); + + RefreshViewportCallbacks(); + + const MStatus MayaStatusResult = MS::kSuccess; + return MayaStatusResult; +} + + +/** +* Called by Maya either at shutdown, or when the user opts to unload the plugin through the Plugin Manager +* +* @param MayaPluginObject The Maya object that represents our plugin +* +* @return MS::kSuccess if everything went OK and the plugin was fully shut down +*/ +DLLEXPORT MStatus uninitializePlugin(MObject MayaPluginObject) +{ + // Make sure the Garbage Collector does not try to remove Delete Listeners on shutdown as those will be invalid causing a crash + GIsRequestingExit = true; + + // Get the plugin API for the plugin object + MFnPlugin MayaPlugin(MayaPluginObject); + + // ... do stuff here ... + + MayaPlugin.deregisterCommand(LiveLinkSubjectNamesCommandName); + MayaPlugin.deregisterCommand(LiveLinkSubjectPathsCommandName); + MayaPlugin.deregisterCommand(LiveLinkSubjectRolesCommandName); + MayaPlugin.deregisterCommand(LiveLinkSubjectTypesCommandName); + MayaPlugin.deregisterCommand(LiveLinkAddSelectionCommandName); + MayaPlugin.deregisterCommand(LiveLinkRemoveSubjectCommandName); + MayaPlugin.deregisterCommand(LiveLinkChangeSubjectNameCommandName); + MayaPlugin.deregisterCommand(LiveLinkConnectionStatusCommandName); + MayaPlugin.deregisterCommand(LiveLinkChangeSubjectStreamTypeCommandName); + + ClearViewportCallbacks(); + if (myCallbackIds.length() != 0) + { + // Make sure we remove all the callbacks we added + MMessage::removeCallbacks(myCallbackIds); + } + + if (ConnectionStatusChangedHandle.IsValid()) + { + LiveLinkProvider->UnregisterConnStatusChangedHandle(ConnectionStatusChangedHandle); + ConnectionStatusChangedHandle.Reset(); + } + + // Maya 2016 does not clean up the address space when unloading a plugin. + // So if we cleaned up here it would crash in the init above when trying to load the plugin a second. + const MString MayaApiVersion = MayaPlugin.apiVersion(); + if (FString(MayaApiVersion.asChar()).Compare("201700") >= 0) + { + LiveLinkProvider.Reset(); + LiveLinkStreamManager.Reset(); + + GEngineLoop.AppPreExit(); + FModuleManager::Get().UnloadModulesAtShutdown(); + GEngineLoop.AppExit(); + } + + MGlobal::executeCommand("MayaLiveLinkClearUI"); + + FTicker::GetCoreTicker().Tick(1.f); + + const MStatus MayaStatusResult = MS::kSuccess; + return MayaStatusResult; +} diff --git a/Source/MayaLiveLinkPlugin2016.Build.cs b/Source/MayaLiveLinkPlugin2016.Build.cs new file mode 100644 index 0000000..33b1f64 --- /dev/null +++ b/Source/MayaLiveLinkPlugin2016.Build.cs @@ -0,0 +1,78 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public abstract class MayaLiveLinkPluginBase : ModuleRules +{ + public MayaLiveLinkPluginBase(ReadOnlyTargetRules Target) : base(Target) + { + // For LaunchEngineLoop.cpp include. You shouldn't need to add anything else to this line. + PrivateIncludePaths.AddRange(new string[] { "Runtime/Launch/Public", "Runtime/Launch/Private" }); + + // Unreal dependency modules + PrivateDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "ApplicationCore", + "Projects", + "UdpMessaging", + "LiveLinkInterface", + "LiveLinkMessageBusFramework", + }); + + + // + // Maya SDK setup + // + + { + //string MayaVersionString = GetMayaVersion(); + string MayaInstallFolder = GetMayaInstallFolderPath(); + + // Make sure this version of Maya is actually installed + if (Directory.Exists(MayaInstallFolder)) + { + // These are required for Maya headers to compile properly as a DLL + PublicDefinitions.Add("NT_PLUGIN=1"); + PublicDefinitions.Add("REQUIRE_IOSTREAM=1"); + + string IncludePath = GetMayaIncludePath(); + PrivateIncludePaths.Add(IncludePath); + + if (Target.Platform == UnrealTargetPlatform.Win64) // @todo: Support other platforms? + { + PublicLibraryPaths.Add(GetMayaLibraryPath()); + + // Maya libraries we're depending on + PublicAdditionalLibraries.AddRange(new string[] + { + "Foundation.lib", + "OpenMaya.lib", + "OpenMayaAnim.lib", + "OpenMayaUI.lib"} + ); + } + } + //else + //{ + // throw new BuildException("Couldn't find Autodesk Maya " + MayaVersionString + " in folder '" + MayaInstallFolder + "'. This version of Maya must be installed for us to find the Maya SDK files."); + //} + } + } + + public abstract string GetMayaVersion(); + public virtual string GetMayaInstallFolderPath() { return @"C:\Program Files\Autodesk\Maya" + GetMayaVersion(); } + public virtual string GetMayaIncludePath() { return Path.Combine(GetMayaInstallFolderPath(), "include"); } + public virtual string GetMayaLibraryPath() { return Path.Combine(GetMayaInstallFolderPath(), "lib"); } +} + +public class MayaLiveLinkPlugin2016 : MayaLiveLinkPluginBase +{ + public MayaLiveLinkPlugin2016(ReadOnlyTargetRules Target) : base(Target) + { + } + + public override string GetMayaVersion() { return "2016"; } +} diff --git a/Source/MayaLiveLinkPlugin2016.Target.cs b/Source/MayaLiveLinkPlugin2016.Target.cs new file mode 100644 index 0000000..6995ba6 --- /dev/null +++ b/Source/MayaLiveLinkPlugin2016.Target.cs @@ -0,0 +1,47 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.Collections.Generic; + +public abstract class MayaLiveLinkPluginTargetBase : TargetRules +{ + public MayaLiveLinkPluginTargetBase(TargetInfo Target, string InMayaVersionString) : base(Target) + { + Type = TargetType.Program; + + bShouldCompileAsDLL = true; + LinkType = TargetLinkType.Monolithic; + + // We only need minimal use of the engine for this plugin + bBuildDeveloperTools = false; + bUseMallocProfiler = false; + bBuildWithEditorOnlyData = true; + bCompileAgainstEngine = false; + bCompileAgainstCoreUObject = true; + bCompileICU = false; + bHasExports = true; + + bBuildInSolutionByDefault = false; + + ExeBinariesSubFolder = "NotForLicensees/Maya/" + InMayaVersionString; + this.LaunchModuleName = "MayaLiveLinkPlugin" + InMayaVersionString; + + // Add a post-build step that copies the output to a file with the .mll extension + string OutputName = LaunchModuleName; + if (Target.Configuration != UnrealTargetConfiguration.Development) + { + OutputName = string.Format("{0}-{1}-{2}", OutputName, Target.Platform, Target.Configuration); + } + + string BinaryFilePath = "$(EngineDir)\\Binaries\\Win64\\NotForLicensees\\Maya\\" + InMayaVersionString + "\\" + OutputName; + string Command = "copy /Y " + BinaryFilePath + ".dll " + BinaryFilePath + ".mll"; + PostBuildSteps.Add(Command); + } +} + +public class MayaLiveLinkPlugin2016Target : MayaLiveLinkPluginTargetBase +{ + public MayaLiveLinkPlugin2016Target(TargetInfo Target) : base(Target, "2016") + { + } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2017.Build.cs b/Source/MayaLiveLinkPlugin2017.Build.cs new file mode 100644 index 0000000..5092b9a --- /dev/null +++ b/Source/MayaLiveLinkPlugin2017.Build.cs @@ -0,0 +1,11 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2017 : MayaLiveLinkPluginBase +{ + public MayaLiveLinkPlugin2017(ReadOnlyTargetRules Target) : base(Target) + { + } + + public override string GetMayaVersion() { return "2017"; } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2017.Target.cs b/Source/MayaLiveLinkPlugin2017.Target.cs new file mode 100644 index 0000000..9c09ebc --- /dev/null +++ b/Source/MayaLiveLinkPlugin2017.Target.cs @@ -0,0 +1,9 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2017Target : MayaLiveLinkPluginTargetBase +{ + public MayaLiveLinkPlugin2017Target(TargetInfo Target) : base(Target, "2017") + { + } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2018.Build.cs b/Source/MayaLiveLinkPlugin2018.Build.cs new file mode 100644 index 0000000..3864ed6 --- /dev/null +++ b/Source/MayaLiveLinkPlugin2018.Build.cs @@ -0,0 +1,11 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2018 : MayaLiveLinkPluginBase +{ + public MayaLiveLinkPlugin2018(ReadOnlyTargetRules Target) : base(Target) + { + } + + public override string GetMayaVersion() { return "2018"; } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2018.Target.cs b/Source/MayaLiveLinkPlugin2018.Target.cs new file mode 100644 index 0000000..ac135c3 --- /dev/null +++ b/Source/MayaLiveLinkPlugin2018.Target.cs @@ -0,0 +1,9 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2018Target : MayaLiveLinkPluginTargetBase +{ + public MayaLiveLinkPlugin2018Target(TargetInfo Target) : base(Target, "2018") + { + } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2019.Build.cs b/Source/MayaLiveLinkPlugin2019.Build.cs new file mode 100644 index 0000000..311437e --- /dev/null +++ b/Source/MayaLiveLinkPlugin2019.Build.cs @@ -0,0 +1,11 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2019 : MayaLiveLinkPluginBase +{ + public MayaLiveLinkPlugin2019(ReadOnlyTargetRules Target) : base(Target) + { + } + + public override string GetMayaVersion() { return "2019"; } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkPlugin2019.Target.cs b/Source/MayaLiveLinkPlugin2019.Target.cs new file mode 100644 index 0000000..acfcfc3 --- /dev/null +++ b/Source/MayaLiveLinkPlugin2019.Target.cs @@ -0,0 +1,9 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; + +public class MayaLiveLinkPlugin2019Target : MayaLiveLinkPluginTargetBase +{ + public MayaLiveLinkPlugin2019Target(TargetInfo Target) : base(Target, "2019") + { + } +} \ No newline at end of file diff --git a/Source/MayaLiveLinkUI.py b/Source/MayaLiveLinkUI.py new file mode 100644 index 0000000..70ac53c --- /dev/null +++ b/Source/MayaLiveLinkUI.py @@ -0,0 +1,201 @@ +import sys +import inspect + +import maya +import maya.OpenMaya as OpenMaya +import maya.OpenMayaMPx as OpenMayaMPx +import maya.cmds as cmds +from pymel.core.windows import Callback, CallbackWithArgs + +StreamTypesPerSubjectType = { + "Prop": ["Root Only", "Full Hierarchy"], + "Character": ["Root Only", "Full Hierarchy"], + "Camera": ["Root Only", "Full Hierarchy", "Camera"], + "Light": ["Root Only", "Full Hierarchy", "Light"], + } + +def OnRemoveSubject(SubjectPath): + cmds.LiveLinkRemoveSubject(SubjectPath) + RefreshSubjects() + +def CreateSubjectTable(): + cmds.rowColumnLayout("SubjectLayout", numberOfColumns=5, adjustableColumn=2, columnWidth=[(1, 20), (2,80), (3, 100), (4, 180), (5, 120)], columnOffset=[(1, 'right', 5), (2, 'right', 10), (4, 'left', 10)], parent="SubjectWrapperLayout") + cmds.text(label="") + cmds.text(label="Subject Type", font="boldLabelFont", align="left") + cmds.text(label="Subject Name", font="boldLabelFont", align="left") + cmds.text(label="DAG Path", font="boldLabelFont", align="left") + cmds.text(label="Stream Type", font="boldLabelFont", align="left") + cmds.rowColumnLayout("SubjectLayout", edit=True, rowOffset=(1, "bottom", 10)) + +#Populate subjects list from c++ +def PopulateSubjects(): + SubjectNames = cmds.LiveLinkSubjectNames() + SubjectPaths = cmds.LiveLinkSubjectPaths() + SubjectTypes = cmds.LiveLinkSubjectTypes() + SubjectRoles = cmds.LiveLinkSubjectRoles() + if SubjectPaths is not None: + RowCounter = 0 + for (SubjectName, SubjectPath, SubjectType, SubjectRole) in zip(SubjectNames, SubjectPaths, SubjectTypes, SubjectRoles): + cmds.button(label="-", height=21, command=Callback(OnRemoveSubject, SubjectPath), parent="SubjectLayout") + cmds.text(label=SubjectType, height=21, align="left", parent="SubjectLayout") + cmds.textField(text=SubjectName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectName, SubjectPath), parent="SubjectLayout") + cmds.text(label=SubjectPath, align="left", height=21, parent="SubjectLayout") + + LayoutName = "ColumnLayoutRow_" + str(RowCounter) # adding a trailing index makes the name unique which is required by the api + + cmds.columnLayout(LayoutName, parent="SubjectLayout") + cmds.optionMenu("SubjectTypeMenu", parent=LayoutName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectStreamType, SubjectPath)) + + for StreamType in StreamTypesPerSubjectType[SubjectType]: + cmds.menuItem(label=StreamType) + + StreamTypeIndex = StreamTypesPerSubjectType[SubjectType].index(SubjectRole) + 1 # menu items are 1-indexed + cmds.optionMenu("SubjectTypeMenu", edit=True, select=StreamTypeIndex) + + RowCounter += 1 + +def ClearSubjects(): + if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)): + cmds.deleteUI("SubjectLayout") + +#Refresh subjects list +def RefreshSubjects(): + if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)): + cmds.deleteUI("SubjectLayout") + CreateSubjectTable() + PopulateSubjects() + +#Connection UI Colours +ConnectionActiveColour = [0.71, 0.9, 0.1] +ConnectionInactiveColour = [1.0, 0.4, 0.4] +ConnectionColourMap = { + True : ConnectionActiveColour, + False: ConnectionInactiveColour +} + +#Base class for command (common creator method + allows for automatic register/unregister) +class LiveLinkCommand(OpenMayaMPx.MPxCommand): + def __init__(self): + OpenMayaMPx.MPxCommand.__init__(self) + + @classmethod + def Creator(Cls): + return OpenMayaMPx.asMPxPtr( Cls() ) + +# Is supplied object a live link command +def IsLiveLinkCommand(InCls): + return inspect.isclass(InCls) and issubclass(InCls, LiveLinkCommand) and InCls != LiveLinkCommand + +# Given a list of strings of names return all the live link commands listed +def GetLiveLinkCommandsFromModule(ModuleItems): + EvalItems = (eval(Item) for Item in ModuleItems) + return [Command for Command in EvalItems if IsLiveLinkCommand(Command) ] + +# Command to create the Live Link UI +class MayaLiveLinkUI(LiveLinkCommand): + WindowName = "MayaLiveLinkUI" + Title = "Maya Live Link UI" + WindowSize = (500, 300) + + def __init__(self): + LiveLinkCommand.__init__(self) + + # Invoked when the command is run. + def doIt(self,argList): + if (cmds.window(self.WindowName , exists=True)): + cmds.deleteUI(self.WindowName) + window = cmds.window( self.WindowName, title=self.Title, widthHeight=(self.WindowSize[0], self.WindowSize[1]) ) + + #Get current connection status + ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus() + + cmds.columnLayout( "mainColumn", adjustableColumn=True ) + cmds.rowLayout("HeaderRow", numberOfColumns=3, adjustableColumn=1, parent = "mainColumn") + cmds.text(label="Unreal Engine Live Link", align="left") + cmds.text(label="Connection:") + cmds.text("ConnectionStatusUI", label=ConnectionText, align="center", backgroundColor=ConnectionColourMap[ConnectedState], width=150) + + cmds.separator(h=20, style="none", parent="mainColumn") + cmds.columnLayout("SubjectWrapperLayout", parent="mainColumn") # just used as a container that will survive refreshing, so the following controls stay in their correct place + + CreateSubjectTable() + PopulateSubjects() + + cmds.separator(h=20, style="none", parent="mainColumn") + cmds.button( label='Add Selection', parent = "mainColumn", command=self.AddSelection) + + cmds.showWindow( self.WindowName ) + + def AddSelection(self, *args): + cmds.LiveLinkAddSelection() + RefreshSubjects() + +# Command to Refresh the subject UI +class MayaLiveLinkRefreshUI(LiveLinkCommand): + def __init__(self): + LiveLinkCommand.__init__(self) + + # Invoked when the command is run. + def doIt(self,argList): + RefreshSubjects() + +class MayaLiveLinkClearUI(LiveLinkCommand): + def __init__(self): + LiveLinkCommand.__init__(self) + + def doIt(self, argList): + ClearSubjects() + CreateSubjectTable() + +# Command to Refresh the connection UI +class MayaLiveLinkRefreshConnectionUI(LiveLinkCommand): + def __init__(self): + LiveLinkCommand.__init__(self) + + # Invoked when the command is run. + def doIt(self,argList): + if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)): + #Get current connection status + ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus() + cmds.text("ConnectionStatusUI", edit=True, label=ConnectionText, backgroundColor=ConnectionColourMap[ConnectedState]) + +class MayaLiveLinkGetActiveCamera(LiveLinkCommand): + def __init__(self): + LiveLinkCommand.__init__(self) + + # Invoked when the command is run. + def doIt(self,argList): + self.clearResult() + try: + c = cmds.getPanel(wf=1) + cam = cmds.modelPanel(c, q=True, camera=True) + except: + pass + else: + self.setResult(cam) + +#Grab commands declared +Commands = GetLiveLinkCommandsFromModule(dir()) + +#Initialize the script plug-in +def initializePlugin(mobject): + mplugin = OpenMayaMPx.MFnPlugin(mobject) + + print "LiveLink:" + for Command in Commands: + try: + print "\tRegistering Command '%s'"%Command.__name__ + mplugin.registerCommand( Command.__name__, Command.Creator ) + except: + sys.stderr.write( "Failed to register command: %s\n" % Command.__name__ ) + raise + +# Uninitialize the script plug-in +def uninitializePlugin(mobject): + mplugin = OpenMayaMPx.MFnPlugin(mobject) + + for Command in Commands: + try: + mplugin.deregisterCommand( Command.__name__ ) + except: + sys.stderr.write( "Failed to unregister command: %s\n" % Command.__name__ ) \ No newline at end of file