diff --git a/Changes b/Changes index 2e4235a09c..8fa71330be 100644 --- a/Changes +++ b/Changes @@ -1,11 +1,24 @@ 10.4.x.x (relative to 10.4.2.1) ======== +Features +-------- + +- IECoreNuke : Add LiveScene support for Nuke (#1310). + - LiveSceneKnob : Knob to interface with LiveScene from Python. + - LiveSceneHolder : Node to hold LiveSceneKnob to provide a Python interface with LiveScene. +- IECoreMaya : Add non-drawable `ieSceneShapeProxy` subclassed from `ieSceneShape` (#1311). + Improvements ------------ - Added string constructor and static `fromString` function to `IECore.MurmurHash`. +Fixes +----- + +- IECoreUSD : Fixed error in `pluginfo.json` preventing USD on Windows from loading `IECoreUSD.dll`. + Build ----- @@ -121,6 +134,22 @@ Build - Updated IE options file to support Nuke 13.x custom dependencies (#1263). +10.3.8.0 (relative to 10.3.7.2) +======== + +Features +-------- + +- IECoreNuke : Add LiveScene support for Nuke (#1310). + - LiveSceneKnob : Knob to interface with LiveScene from Python. + - LiveSceneHolder : Node to hold LiveSceneKnob to provide a Python interface with LiveScene. +- IECoreMaya : Add non-drawable `ieSceneShapeProxy` subclassed from `ieSceneShape` (#1311). + +Fixes +----- + +- IECoreUSD : Fixed error in `pluginfo.json` preventing USD on Windows from loading `IECoreUSD.dll`. + 10.3.7.2 (relative to 10.3.7.1) ======== diff --git a/SConstruct b/SConstruct index c920b4e8bc..4c163acd26 100644 --- a/SConstruct +++ b/SConstruct @@ -2691,7 +2691,7 @@ if doConfigure : nukePythonSources = sorted( glob.glob( "src/IECoreNuke/bindings/*.cpp" ) ) nukePythonScripts = glob.glob( "python/IECoreNuke/*.py" ) nukePluginSources = sorted( glob.glob( "src/IECoreNuke/plugin/*.cpp" ) ) - nukeNodeNames = [ "ieObject", "ieOp", "ieDrawable", "ieDisplay" ] + nukeNodeNames = [ "ieObject", "ieOp", "ieDrawable", "ieDisplay", "ieLiveScene", "sccWriter" ] # nuke library nukeLibrary = nukeEnv.SharedLibrary( "lib/" + os.path.basename( nukeEnv.subst( "$INSTALL_NUKELIB_NAME" ) ), nukeSources ) @@ -2751,7 +2751,12 @@ if doConfigure : for nodeName in nukeNodeNames : nukeStubEnv = nukePluginEnv.Clone( IECORE_NAME=nodeName ) - nukeStubName = "plugins/nuke/" + os.path.basename( nukeStubEnv.subst( "$INSTALL_NUKEPLUGIN_NAME" ) ) + ".tcl" + # In order to have our custom file format (scc) displayed in the file_type knob of the WriteGeo node, we need to install + # a dummy library with "[fileExtension]Writer" + if nodeName == "sccWriter": + nukeStubName = "plugins/nuke/" + os.path.basename( nukeStubEnv.subst( "$INSTALL_NUKEPLUGIN_NAME$SHLIBSUFFIX" ) ) + else: + nukeStubName = "plugins/nuke/" + os.path.basename( nukeStubEnv.subst( "$INSTALL_NUKEPLUGIN_NAME" ) ) + ".tcl" nukeStub = nukePluginEnv.Command( nukeStubName, nukePlugin, "echo 'load ieCore' > $TARGET" ) nukeStubInstall = nukeStubEnv.Install( os.path.dirname( nukeStubEnv.subst( "$INSTALL_NUKEPLUGIN_NAME" ) ), nukeStub ) nukeStubEnv.Alias( "install", nukeStubInstall ) @@ -3130,7 +3135,7 @@ if doConfigure : "!IECOREUSD_RELATIVE_LIB_FOLDER!" : os.path.relpath( usdLibraryInstall[0].get_path(), os.path.dirname( usdEnv.subst( "$INSTALL_USD_RESOURCE_DIR/IECoreUSD/plugInfo.json" ) ) - ).format( "\\", "\\\\" ), + ).replace( "\\", "\\\\" ), } ) usdEnv.AddPostAction( "$INSTALL_USD_RESOURCE_DIR/IECoreUSD", lambda target, source, env : makeSymLinks( usdEnv, usdEnv["INSTALL_USD_RESOURCE_DIR"] ) ) diff --git a/include/IECoreMaya/MayaTypeIds.h b/include/IECoreMaya/MayaTypeIds.h index 644b777ef6..82223ab78e 100644 --- a/include/IECoreMaya/MayaTypeIds.h +++ b/include/IECoreMaya/MayaTypeIds.h @@ -67,6 +67,7 @@ enum MayaTypeId GeometryCombinerId = 0x00110DD2, SceneShapeId = 0x00110DD3, SceneShapeInterfaceId = 0x00110DD4, + SceneShapeProxyId = 0x00110DD5, /// Don't forget to update MayaTypeIdsBinding.cpp LastId = 0x00110E3F, diff --git a/include/IECoreMaya/SceneShapeProxy.h b/include/IECoreMaya/SceneShapeProxy.h new file mode 100644 index 0000000000..e56ba90812 --- /dev/null +++ b/include/IECoreMaya/SceneShapeProxy.h @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IE_COREMAYA_SCENESHAPEPROXY_H +#define IE_COREMAYA_SCENESHAPEPROXY_H + +#include "IECoreMaya/SceneShape.h" + +namespace IECoreMaya +{ + +/// A proxy derived from the SceneShape which exposes the same functionality as the base clase +/// with the exception, that we never register it as a maya SubSceneOverride. The reasoning +/// behind this is that the SubSceneOverride does not take into account the visibility state of the shape. +/// During an update loop of the SubSceneOverride, all SceneShapes will be queried for their update state, +/// regardless their visibility in the scene. This query is slow and we get a huge drop in performance +/// when having a huge amount of SceneShapes in the scene. +/// This is considered to be a bug in the ViewPort 2 API. Our attempts to rewrite the code to use +/// "MPxGeometryOverride" or "MPxDrawOverride" prove themselves as unstable or not suitable for our +/// use case, why we decided to use this "hackery" and not register a proxy of the SceneShape for +/// drawing at all +class IECOREMAYA_API SceneShapeProxy : public SceneShape +{ + public : + + SceneShapeProxy(); + virtual ~SceneShapeProxy(); + + static void *creator(); + static MStatus initialize(); + static MTypeId id; +}; + +} + +#endif // IE_COREMAYA_SCENESHAPEPROXY_H diff --git a/include/IECoreMaya/SceneShapeProxyUI.h b/include/IECoreMaya/SceneShapeProxyUI.h new file mode 100644 index 0000000000..8ed06afc05 --- /dev/null +++ b/include/IECoreMaya/SceneShapeProxyUI.h @@ -0,0 +1,59 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECOREMAYA_SCENESHAPEPROXYUI_H +#define IECOREMAYA_SCENESHAPEPROXYUI_H + +#include "maya/MPxSurfaceShapeUI.h" +#include "maya/MTypes.h" +#include "IECoreMaya/Export.h" + +namespace IECoreMaya +{ + +/// The SceneShapeProxyUI is required for the registration of the SceneShapeProxy and we just make it a NoOp +/// TODO: It might be worth to see if the SceneShapeUI has any dependencies on the drawing capabilities of the +/// shape and if that's not the case, register SceneShapeProxy with the original implementation of SceneShapeUI +class IECOREMAYA_API SceneShapeProxyUI : public MPxSurfaceShapeUI +{ + + public : + + SceneShapeProxyUI(); + static void *creator(); +}; + +} // namespace IECoreMaya + +#endif // IECOREMAYA_SCENESHAPEPROXYUI_H diff --git a/include/IECoreNuke/Convert.h b/include/IECoreNuke/Convert.h index 93ffb3b8ce..e1ce198c33 100644 --- a/include/IECoreNuke/Convert.h +++ b/include/IECoreNuke/Convert.h @@ -114,12 +114,21 @@ IECORENUKE_API Imath::M44f convert( const DD::Image::Matrix4 &from ); template<> IECORENUKE_API Imath::M44d convert( const DD::Image::Matrix4 &from ); +template<> +IECORENUKE_API DD::Image::Matrix4 convert( const Imath::M44d &from ); + template<> IECORENUKE_API Imath::Box2i convert( const DD::Image::Box &from ); template<> IECORENUKE_API DD::Image::Box3 convert( const Imath::Box3f &from ); +template<> +IECORENUKE_API Imath::Box3f convert( const DD::Image::Box3 &from ); + +template<> +IECORENUKE_API Imath::Box3d convert( const DD::Image::Box3 &from ); + } // namespace IECore #endif // IECORENUKE_CONVERT_H diff --git a/include/IECoreNuke/LiveScene.h b/include/IECoreNuke/LiveScene.h new file mode 100644 index 0000000000..1c2edab373 --- /dev/null +++ b/include/IECoreNuke/LiveScene.h @@ -0,0 +1,132 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_LIVESCENE_H +#define IECORENUKE_LIVESCENE_H + +#include "DDImage/GeoOp.h" +#include "DDImage/GeometryList.h" + +#include "IECore/PathMatcher.h" + +#include "IECoreScene/SceneInterface.h" + +#include "IECoreNuke/Export.h" +#include "IECoreNuke/TypeIds.h" + +namespace IECoreNuke +{ + +IE_CORE_FORWARDDECLARE( LiveScene ); + +/// A read-only class for representing a live Nuke scene as an IECore::SceneInterface +class IECORENUKE_API LiveScene : public IECoreScene::SceneInterface +{ + public : + + static const std::string& nameAttribute; + + IE_CORE_DECLARERUNTIMETYPEDEXTENSION( LiveScene, LiveSceneTypeId, IECoreScene::SceneInterface ); + + LiveScene(); + LiveScene( DD::Image::GeoOp *op, const std::string rootPath="/" ); + + ~LiveScene() override; + + std::string fileName() const override; + + Name name() const override; + void path( Path &p ) const override; + + Imath::Box3d readBound( double time ) const override; + void writeBound( const Imath::Box3d &bound, double time ); + + IECore::ConstDataPtr readTransform( double time ) const override; + Imath::M44d readTransformAsMatrix( double time ) const override; + IECore::ConstDataPtr readWorldTransform( double time ) const; + Imath::M44d readWorldTransformAsMatrix( double time ) const; + void writeTransform( const IECore::Data *transform, double time ) override; + + bool hasAttribute( const Name &name ) const override; + void attributeNames( NameList &attrs ) const override; + IECore::ConstObjectPtr readAttribute( const Name &name, double time ) const override; + void writeAttribute( const Name &name, const IECore::Object *attribute, double time ) override; + + bool hasTag( const Name &name, int filter = SceneInterface::LocalTag ) const override; + void readTags( NameList &tags, int filter = SceneInterface::LocalTag ) const override; + void writeTags( const NameList &tags ) override; + + NameList setNames( bool includeDescendantSets = true ) const override; + IECore::PathMatcher readSet( const Name &name, bool includeDescendantSets = true, const IECore::Canceller *canceller = nullptr ) const override; + void writeSet( const Name &name, const IECore::PathMatcher &set ) override; + void hashSet( const Name& setName, IECore::MurmurHash &h ) const override; + + bool hasObject() const override; + IECore::ConstObjectPtr readObject( double time, const IECore::Canceller *canceller = nullptr ) const override; + IECoreScene::PrimitiveVariableMap readObjectPrimitiveVariables( const std::vector &primVarNames, double time ) const override; + void writeObject( const IECore::Object *object, double time ) override; + + void childNames( NameList &childNames ) const override; + bool hasChild( const Name &name ) const override; + IECoreScene::SceneInterfacePtr child( const Name &name, MissingBehaviour missingBehaviour = SceneInterface::ThrowIfMissing ) override; + IECoreScene::ConstSceneInterfacePtr child( const Name &name, MissingBehaviour missingBehaviour = SceneInterface::ThrowIfMissing ) const override; + IECoreScene::SceneInterfacePtr createChild( const Name &name ) override; + + IECoreScene::SceneInterfacePtr scene( const Path &path, MissingBehaviour missingBehaviour = SceneInterface::ThrowIfMissing ) override; + IECoreScene::ConstSceneInterfacePtr scene( const Path &path, MissingBehaviour missingBehaviour = SceneInterface::ThrowIfMissing ) const override; + + void hash( HashType hashType, double time, IECore::MurmurHash &h ) const override; + + static double timeToFrame( const double& time ); + static double frameToTime( const int& frame ); + + private: + + DD::Image::GeoOp *op() const; + DD::Image::GeometryList geometryList( const double* time=nullptr ) const; + + std::string geoInfoPath( const int& index ) const; + + DD::Image::GeoOp *m_op; + std::string m_rootPath; + IECore::PathMatcher m_pathMatcher; + typedef std::map objectPathMap; + mutable objectPathMap m_objectPathMap; +}; + +IE_CORE_DECLAREPTR( LiveScene ); + +} // namespace IECoreNuke + +#endif // IECORENUKE_LIVESCENE_H diff --git a/include/IECoreNuke/LiveSceneHolder.h b/include/IECoreNuke/LiveSceneHolder.h new file mode 100644 index 0000000000..c998fa9e00 --- /dev/null +++ b/include/IECoreNuke/LiveSceneHolder.h @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_LIVESCENEHOLDER_H +#define IECORENUKE_LIVESCENEHOLDER_H + +#include "DDImage/GeoOp.h" + +#include "IECoreNuke/Export.h" +#include "IECoreNuke/LiveScene.h" + + +namespace IECoreNuke +{ + +/// This Op does no processing, but simply provides a single LiveSceneKnob. +/// This is mainly used for the LiveSceneKnob test cases. +class IECORENUKE_API LiveSceneHolder : public DD::Image::GeoOp +{ + + public : + + LiveSceneHolder( Node *node ); + virtual ~LiveSceneHolder(); + + virtual void knobs( DD::Image::Knob_Callback f ); + virtual const char *Class() const; + virtual const char *node_help() const; + + private : + + static const Description g_description; + static DD::Image::Op *build( Node *node ); + +}; + +} // namespace IECoreNuke + +#endif // IECORENUKE_LIVESCENEHOLDER_H diff --git a/include/IECoreNuke/LiveSceneKnob.h b/include/IECoreNuke/LiveSceneKnob.h new file mode 100644 index 0000000000..e5ed2716d8 --- /dev/null +++ b/include/IECoreNuke/LiveSceneKnob.h @@ -0,0 +1,94 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_LIVESCENEKNOB_H +#define IECORENUKE_LIVESCENEKNOB_H + +#include "IECoreNuke/Export.h" + +#include "IECoreNuke/LiveSceneHolder.h" +#include "IECoreNuke/LiveScene.h" + +IECORE_PUSH_DEFAULT_VISIBILITY +#include "DDImage/Knobs.h" +IECORE_POP_DEFAULT_VISIBILITY + +namespace IECoreNuke +{ + +/// A nuke knob capable of holding arbitrary IECore::LiveScenes. +class IECORENUKE_API LiveSceneKnob : public DD::Image::Knob +{ + + public : + + IECoreNuke::LiveScenePtr getValue(); + + /// Call this from an Op::knobs() implementation to create an LiveSceneKnob. + static LiveSceneKnob *sceneKnob( DD::Image::Knob_Callback f, IECoreNuke::LiveSceneHolder* op, const char *name, const char *label ); + + protected : + + LiveSceneKnob( DD::Image::Knob_Closure *f, IECoreNuke::LiveSceneHolder* op, const char *name, const char *label = 0 ); + virtual ~LiveSceneKnob(); + + virtual const char *Class() const; + + private : + + IECoreNuke::LiveScenePtr m_value; + IECoreNuke::LiveSceneHolder* m_op; + +}; + +namespace Detail +{ + +// Used to implement the python binding +struct PythonLiveSceneKnob : public IECore::RefCounted +{ + + IE_CORE_DECLAREMEMBERPTR( PythonLiveSceneKnob ); + + LiveSceneKnob *sceneKnob; + +}; + +IE_CORE_DECLAREPTR( PythonLiveSceneKnob ); + +} // namespace Detail + +} // namespace IECoreNuke + +#endif // IECORENUKE_LIVESCENEKNOB_H diff --git a/include/IECoreNuke/SceneCacheReader.h b/include/IECoreNuke/SceneCacheReader.h index d609549c5b..e04c1a9cd8 100644 --- a/include/IECoreNuke/SceneCacheReader.h +++ b/include/IECoreNuke/SceneCacheReader.h @@ -172,6 +172,8 @@ class IECORENUKE_API SceneCacheReader : public DD::Image::SourceGeo // only the first reader allocates the shared data SharedData *m_data; + + std::map m_indexToWorldTransform; }; } // namespace IECoreNuke diff --git a/include/IECoreNuke/SceneCacheWriter.h b/include/IECoreNuke/SceneCacheWriter.h new file mode 100644 index 0000000000..ebc7aaa550 --- /dev/null +++ b/include/IECoreNuke/SceneCacheWriter.h @@ -0,0 +1,73 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_SCENECACHE_WRITER_H +#define IECORENUKE_SCENECACHE_WRITER_H + +#include "IECoreNuke/Export.h" +#include "IECoreNuke/LiveScene.h" + +#include "DDImage/GeoWriter.h" +#include "DDImage/Scene.h" + +namespace IECoreNuke +{ + +/// A class to support writing SceneCache supported files out of Nuke using the WriteGeo node. +class IECORENUKE_API SceneCacheWriter : public DD::Image::GeoWriter +{ + public : + + SceneCacheWriter( DD::Image::WriteGeo* writeNode ); + + void execute( DD::Image::Scene& scene ) override; + bool open(); + + static DD::Image::GeoWriter* Build( DD::Image::WriteGeo* readNode ); + static DD::Image::GeoWriter::Description description; + + bool animation() const override; + + private : + + void writeLocation( IECoreScene::ConstSceneInterfacePtr inScene, IECoreScene::SceneInterfacePtr outScene, const IECore::InternedString& childName ); + + IECoreNuke::LiveScenePtr m_liveScene; + IECoreScene::SceneInterfacePtr m_writer; + +}; + +} // namespace IECoreNuke + +#endif // IECORENUKE_SCENECACHE_WRITER_H diff --git a/include/IECoreNuke/ToNukeGeometryConverter.h b/include/IECoreNuke/ToNukeGeometryConverter.h index 8bde60eb44..576b825e54 100644 --- a/include/IECoreNuke/ToNukeGeometryConverter.h +++ b/include/IECoreNuke/ToNukeGeometryConverter.h @@ -39,6 +39,7 @@ #include "IECore/NumericParameter.h" #include "IECore/Object.h" +#include "IECore/SimpleTypedParameter.h" #include "DDImage/GeometryList.h" @@ -97,6 +98,8 @@ class IECORENUKE_API ToNukeGeometryConverter : public ToNukeConverter static TypesToFnsMap *typesToFns(); IECore::IntParameterPtr m_objIndexParameter; + IECore::StringParameterPtr m_pathParameter; + }; } // namespace IECoreNuke diff --git a/include/IECoreNuke/TypeIds.h b/include/IECoreNuke/TypeIds.h index 273957acf7..84ecedb260 100644 --- a/include/IECoreNuke/TypeIds.h +++ b/include/IECoreNuke/TypeIds.h @@ -48,6 +48,7 @@ enum TypeId FromNukeCameraConverterTypeId = 107005, FromNukeTileConverterTypeId = 107006, NukeDisplayDriverTypeId = 107007, + LiveSceneTypeId = 107008, LastCoreNukeTypeId = 107999 }; diff --git a/include/IECoreNuke/bindings/LiveSceneBinding.h b/include/IECoreNuke/bindings/LiveSceneBinding.h new file mode 100644 index 0000000000..0df2ed96a9 --- /dev/null +++ b/include/IECoreNuke/bindings/LiveSceneBinding.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_LIVESCENEBINDING_H +#define IECORENUKE_LIVESCENEBINDING_H + +namespace IECoreNuke +{ + +void bindLiveScene(); + +} + +#endif // IECORENUKE_LIVESCENEBINDING_H diff --git a/include/IECoreNuke/bindings/LiveSceneKnobBinding.h b/include/IECoreNuke/bindings/LiveSceneKnobBinding.h new file mode 100644 index 0000000000..245b132689 --- /dev/null +++ b/include/IECoreNuke/bindings/LiveSceneKnobBinding.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECORENUKE_LIVESCENEKNOBBINDING_H +#define IECORENUKE_LIVESCENEKNOBBINDING_H + +namespace IECoreNuke +{ + +void bindLiveSceneKnob(); + +} + +#endif // IECORENUKE_LIVESCENEKNOBBINDING_H diff --git a/mel/IECoreMaya/IECoreMaya.mel b/mel/IECoreMaya/IECoreMaya.mel index 0f77a7c67b..87fcf1e320 100644 --- a/mel/IECoreMaya/IECoreMaya.mel +++ b/mel/IECoreMaya/IECoreMaya.mel @@ -53,3 +53,4 @@ source "IECoreMaya/ieDrawableHolderUI.mel"; source "IECoreMaya/ieGeometryCombinerUI.mel"; source "IECoreMaya/ieCurveCombinerUI.mel"; source "IECoreMaya/ieSceneShapeUI.mel"; +source "IECoreMaya/ieSceneShapeProxyUI.mel"; diff --git a/mel/IECoreMaya/ieSceneShapeProxyUI.mel b/mel/IECoreMaya/ieSceneShapeProxyUI.mel new file mode 100644 index 0000000000..4164df0eea --- /dev/null +++ b/mel/IECoreMaya/ieSceneShapeProxyUI.mel @@ -0,0 +1,63 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + + +global proc AEieSceneShapeProxyTemplate( string $nodeName ) +{ + editorTemplate -beginScrollLayout; + + editorTemplate -beginLayout "Inputs"; + editorTemplate -annotation "Path to the scene interface file." -addControl "file" ; + editorTemplate -annotation "Path in the scene interface where you start reading." -addControl "root"; + editorTemplate -annotation "If on, only read the object at the root path." -addControl "objectOnly"; + editorTemplate -addControl "time"; + + editorTemplate -endLayout; + + editorTemplate -beginLayout "Queries"; + editorTemplate -addControl "querySpace"; + editorTemplate -addControl "queryPaths"; + editorTemplate -addControl "queryAttributes"; + editorTemplate -addControl "queryConvertParameters"; + + editorTemplate -endLayout; + + editorTemplate -beginLayout "All Dynamic Attributes"; + editorTemplate -beginLayout "Open With Caution - Maya May Hang"; + editorTemplate -extraControlsLabel "Too Late Now!" -addExtraControls; + editorTemplate -endLayout; + editorTemplate -endLayout; + + editorTemplate -endScrollLayout; +} diff --git a/python/IECoreMaya/FnSceneShape.py b/python/IECoreMaya/FnSceneShape.py index 9fa84b6e5c..6057f4922f 100644 --- a/python/IECoreMaya/FnSceneShape.py +++ b/python/IECoreMaya/FnSceneShape.py @@ -47,7 +47,7 @@ from six.moves import range -## A function set for operating on the IECoreMaya::SceneShape type. +## A function set for operating on the IECoreMaya::SceneShape and IECoreMaya::SceneShapeProxy types. class FnSceneShape( maya.OpenMaya.MFnDagNode ) : __MayaAttributeDataType = namedtuple('__MayaAttributeDataType', 'namespace type') @@ -74,28 +74,48 @@ class FnSceneShape( maya.OpenMaya.MFnDagNode ) : ## Initialise the function set for the given procedural object, which may # either be an MObject or a node name in string or unicode form. # Note: Most of the member functions assume that this function set is initialized with the full dag path. - def __init__( self, object ) : - if isinstance( object, six.string_types ) : - object = StringUtil.dagPathFromString( object ) + def __init__( self, mayaObject ) : + + if isinstance( mayaObject, six.string_types ) : + mayaObject = StringUtil.dagPathFromString( mayaObject ) + maya.OpenMaya.MFnDagNode.__init__( self, mayaObject ) + + # We use pythons metaprogramming capabilities and dynamically change the object that is created based + # on the node type type that is passed in. We do this so that we can use FnSceneShape for two different + # node types, i.e. SceneShape and its derived proxy class SceneShapeProxy, without having to change huge + # parts of the codebase. SceneShape and SceneShapeProxy both provide the same core functionality of reading + # a SceneInterface, with the difference that SceneShapeProxy can't be drawn in the ViewPort and therefore + # drawing/selecting related methods in those class have no effect. See SceneShapeProxy.h for more information. + def __new__( cls, mayaObject ): + + if isinstance( mayaObject, six.string_types ) : + mayaObject = StringUtil.dagPathFromString( mayaObject ) + else: + dagPath = maya.OpenMaya.MDagPath() + maya.OpenMaya.MDagPath.getAPathTo(mayaObject, dagPath) + mayaObject = dagPath - maya.OpenMaya.MFnDagNode.__init__( self, object ) + if maya.cmds.nodeType(mayaObject.fullPathName()) == "ieSceneShape": + return object.__new__(FnSceneShape) + if maya.cmds.nodeType(mayaObject.fullPathName()) == "ieSceneShapeProxy": + return object.__new__(_FnSceneShapeProxy) ## Creates a new node under a transform of the specified name. Returns a function set instance operating on this new node. - @staticmethod + @classmethod @IECoreMaya.UndoFlush() - def create( parentName, transformParent = None, shadingEngine = None ) : + def create( cls, parentName, transformParent = None, shadingEngine = None, shapeType=None ) : try: parentNode = maya.cmds.createNode( "transform", name=parentName, skipSelect=True, parent = transformParent ) except: # The parent name is supposed to be the children names in a sceneInterface, they could be numbers, maya doesn't like that. Use a prefix. parentNode = maya.cmds.createNode( "transform", name="sceneShape_"+parentName, skipSelect=True, parent = transformParent ) - return FnSceneShape.createShape( parentNode, shadingEngine=shadingEngine ) + return cls.createShape( parentNode, shadingEngine=shadingEngine, shapeType=shapeType ) ## Create a scene shape under the given node. Returns a function set instance operating on this shape. - @staticmethod + @classmethod @IECoreMaya.UndoFlush() - def createShape( parentNode, shadingEngine = None ) : + def createShape( cls, parentNode, shadingEngine = None, shapeType=None ) : parentShort = parentNode.rpartition( "|" )[-1] numbersMatch = re.search( r"[0-9]+$", parentShort ) if numbersMatch is not None : @@ -105,11 +125,11 @@ def createShape( parentNode, shadingEngine = None ) : shapeName = parentShort + "SceneShape" dagMod = maya.OpenMaya.MDagModifier() - shapeNode = dagMod.createNode( FnSceneShape._mayaNodeType(), IECoreMaya.StringUtil.dependencyNodeFromString( parentNode ) ) + shapeNode = dagMod.createNode( shapeType if shapeType else cls._mayaNodeType(), IECoreMaya.StringUtil.dependencyNodeFromString( parentNode ) ) dagMod.renameNode( shapeNode, shapeName ) dagMod.doIt() - fnScS = FnSceneShape( shapeNode ) + fnScS = cls( shapeNode ) maya.cmds.sets( fnScS.fullPathName(), edit=True, forceElement=shadingEngine or "initialShadingGroup" ) fnScS.findPlug( "objectOnly" ).setLocked( True ) @@ -123,13 +143,13 @@ def createShape( parentNode, shadingEngine = None ) : ## Registers a new callback triggered when a new child shape is created during geometry expansion. # Signature: `callback( dagPath )` - @staticmethod - def addChildCreatedCallback( func ): - FnSceneShape.__childCreatedCallbacks.add( func ) + @classmethod + def addChildCreatedCallback( cls, func ): + cls.__childCreatedCallbacks.add( func ) - @staticmethod - def __executeChildCreatedCallbacks( dagPath ): - for callback in FnSceneShape.__childCreatedCallbacks: + @classmethod + def __executeChildCreatedCallbacks( cls, dagPath ): + for callback in cls.__childCreatedCallbacks: try: callback( dagPath ) except Exception as exc: @@ -266,11 +286,11 @@ def __createChild( self, childName, sceneFile, sceneRoot, drawGeo = False, drawC if maya.cmds.objExists(childPath): shape = maya.cmds.listRelatives( childPath, fullPath=True, type="ieSceneShape" ) if shape: - fnChild = IECoreMaya.FnSceneShape( shape[0] ) + fnChild = self.__class__( shape[0] ) else: - fnChild = IECoreMaya.FnSceneShape.createShape( childPath, shadingEngine=shadingGroup ) + fnChild = self.createShape( childPath, shadingEngine=shadingGroup ) else: - fnChild = IECoreMaya.FnSceneShape.create( childName, transformParent=parentPath, shadingEngine=shadingGroup ) + fnChild = self.create( childName, transformParent=parentPath, shadingEngine=shadingGroup ) fnChildTransform = maya.OpenMaya.MFnDagNode( fnChild.parent( 0 ) ) @@ -298,24 +318,24 @@ def __createChild( self, childName, sceneFile, sceneRoot, drawGeo = False, drawC outTransform = self.findPlug( "outTransform" ).elementByLogicalIndex( index ) childTranslate = fnChildTransform.findPlug( "translate" ) - FnSceneShape.__disconnectPlug( dgMod, childTranslate ) + self.__disconnectPlug( dgMod, childTranslate ) dgMod.connect( outTransform.child( self.attribute( "outTranslate" ) ), childTranslate ) childRotate = fnChildTransform.findPlug( "rotate" ) - FnSceneShape.__disconnectPlug( dgMod, childRotate) + self.__disconnectPlug( dgMod, childRotate) dgMod.connect( outTransform.child( self.attribute( "outRotate" ) ), childRotate ) childScale = fnChildTransform.findPlug( "scale" ) - FnSceneShape.__disconnectPlug( dgMod, childScale ) + self.__disconnectPlug( dgMod, childScale ) dgMod.connect( outTransform.child( self.attribute( "outScale" ) ), childScale ) childTime = fnChild.findPlug( "time" ) - FnSceneShape.__disconnectPlug( dgMod, childTime ) + self.__disconnectPlug( dgMod, childTime ) dgMod.connect( self.findPlug( "outTime" ), childTime ) dgMod.doIt() - FnSceneShape.__executeChildCreatedCallbacks( fnChild.fullPathName() ) + self.__executeChildCreatedCallbacks( fnChild.fullPathName() ) return fnChild @@ -691,7 +711,7 @@ def __cortexTypeToMayaType( self, querySceneInterface, attributeName ): timePlug = self.findPlug( 'time', False ) time = timePlug.asMTime().asUnits( maya.OpenMaya.MTime.kSeconds ) cortexData = querySceneInterface.readAttribute( attributeName, time ) - return FnSceneShape.__cortexToMayaDataTypeMap.get( cortexData.typeId() ) + return self.__cortexToMayaDataTypeMap.get( cortexData.typeId() ) ## Returns a list of attribute names which can be promoted to maya plugs. # \param queryPath Path to the scene from which we want return attribute names. Defaults to root '/' @@ -793,3 +813,15 @@ def promoteAttribute( self, attributeName, queryPath='/', nodePath='', mayaAttri @classmethod def _mayaNodeType( cls ): return "ieSceneShape" + + +# A derived function set for operating on the IECoreMaya::SceneShapeProxy type. +# It inherits all functionality from the base clase and we only override the `_mayaNodeType` method. +# This object is used in the __new__ method of FnSceneShape to create the correct object depending +# on the passed in node type +class _FnSceneShapeProxy( FnSceneShape ) : + + # Returns the maya node type that this function set operates on + @classmethod + def _mayaNodeType( cls ): + return "ieSceneShapeProxy" diff --git a/python/IECoreMaya/__init__.py b/python/IECoreMaya/__init__.py index a82438552d..c5f39ab6fc 100644 --- a/python/IECoreMaya/__init__.py +++ b/python/IECoreMaya/__init__.py @@ -101,5 +101,6 @@ from . import Menus from . import SceneShapeUI from .FnSceneShape import FnSceneShape +from .FnSceneShape import _FnSceneShapeProxy __import__( "IECore" ).loadConfig( "CORTEX_STARTUP_PATHS", subdirectory = "IECoreMaya" ) diff --git a/python/IECoreNuke/__init__.py b/python/IECoreNuke/__init__.py index ea630c0c3b..7fd11c4e81 100644 --- a/python/IECoreNuke/__init__.py +++ b/python/IECoreNuke/__init__.py @@ -34,6 +34,9 @@ __import__( "IECore" ) +# importing IECoreScene before _IECoreNuke required for LiveScene binding to work as we need the SceneInterface binding loading first. +import IECoreScene + from ._IECoreNuke import * from .KnobAccessors import setKnobValue, getKnobValue diff --git a/src/IECoreMaya/IECoreMaya.cpp b/src/IECoreMaya/IECoreMaya.cpp index 313d6be15c..2ba03f40ad 100644 --- a/src/IECoreMaya/IECoreMaya.cpp +++ b/src/IECoreMaya/IECoreMaya.cpp @@ -71,7 +71,9 @@ #include "IECoreMaya/DrawableHolder.h" #include "IECoreMaya/DrawableHolderUI.h" #include "IECoreMaya/SceneShape.h" +#include "IECoreMaya/SceneShapeProxy.h" #include "IECoreMaya/SceneShapeUI.h" +#include "IECoreMaya/SceneShapeProxyUI.h" #include "IECoreMaya/SceneShapeInterface.h" #include "IECoreMaya/SceneShapeSubSceneOverride.h" @@ -148,6 +150,12 @@ MStatus initialize(MFnPlugin &plugin) s = MHWRender::MDrawRegistry::registerSubSceneOverrideCreator( SceneShapeSubSceneOverride::drawDbClassification(), SceneShapeSubSceneOverride::drawDbId(), SceneShapeSubSceneOverride::Creator ); assert( s ); + /// Note the missing classification for the draw DB and the "missing" call to "MHWRender::MDrawRegistry::registerSubSceneOverrideCreator" + /// after registering the Shape itself. See the documentation of the SceneShapeProxy class in SceneShapeProxy.h for the reason behind this. + s = plugin.registerShape( "ieSceneShapeProxy", SceneShapeProxy::id, + SceneShapeProxy::creator, SceneShapeProxy::initialize, SceneShapeProxyUI::creator ); + assert( s ); + s = plugin.registerNode( "ieOpHolderNode", OpHolderNode::id, OpHolderNode::creator, OpHolderNode::initialize ); assert( s ); @@ -245,6 +253,7 @@ MStatus uninitialize(MFnPlugin &plugin) s = plugin.deregisterNode( ParameterisedHolderComponentShape::id ); s = plugin.deregisterNode( SceneShapeInterface::id ); s = plugin.deregisterNode( SceneShape::id ); + s = plugin.deregisterNode( SceneShapeProxy::id ); s = plugin.deregisterNode( OpHolderNode::id ); s = plugin.deregisterNode( ConverterHolder::id ); s = plugin.deregisterNode( TransientParameterisedHolderNode::id ); diff --git a/src/IECoreMaya/SceneShape.cpp b/src/IECoreMaya/SceneShape.cpp index aa69e1d4fa..207552b6a7 100644 --- a/src/IECoreMaya/SceneShape.cpp +++ b/src/IECoreMaya/SceneShape.cpp @@ -190,7 +190,7 @@ SceneShape *SceneShape::findScene( const MDagPath &p, bool noIntermediate, MDagP MFnDagNode fnChildDag(childObject); MPxNode* userNode = fnChildDag.userNode(); - if( userNode && userNode->typeId() == SceneShapeId ) + if( userNode && ( userNode->typeId() == SceneShapeId || userNode->typeId() == SceneShapeProxyId ) ) { if ( noIntermediate && fnChildDag.isIntermediateObject() ) { diff --git a/src/IECoreMaya/SceneShapeProxy.cpp b/src/IECoreMaya/SceneShapeProxy.cpp new file mode 100644 index 0000000000..965d0f95ba --- /dev/null +++ b/src/IECoreMaya/SceneShapeProxy.cpp @@ -0,0 +1,55 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECoreMaya/SceneShapeProxy.h" +#include "IECoreMaya/MayaTypeIds.h" + +using namespace IECoreMaya; + +MTypeId SceneShapeProxy::id = SceneShapeProxyId; + +SceneShapeProxy::SceneShapeProxy() {} + +SceneShapeProxy::~SceneShapeProxy() {} + +void *SceneShapeProxy::creator() +{ + return new SceneShapeProxy; +} + +MStatus SceneShapeProxy::initialize() +{ + MStatus s = inheritAttributesFrom( "ieSceneShape" ); + return s; +} diff --git a/src/IECoreMaya/SceneShapeProxyUI.cpp b/src/IECoreMaya/SceneShapeProxyUI.cpp new file mode 100644 index 0000000000..935348cf81 --- /dev/null +++ b/src/IECoreMaya/SceneShapeProxyUI.cpp @@ -0,0 +1,46 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECoreMaya/SceneShapeProxyUI.h" + +using namespace IECoreMaya; + +SceneShapeProxyUI::SceneShapeProxyUI() +{ +} + +void *SceneShapeProxyUI::creator() +{ + return new SceneShapeProxyUI; +} diff --git a/src/IECoreMaya/bindings/MayaTypeIdBinding.cpp b/src/IECoreMaya/bindings/MayaTypeIdBinding.cpp index 806a2082f8..cbe1e9f8c4 100644 --- a/src/IECoreMaya/bindings/MayaTypeIdBinding.cpp +++ b/src/IECoreMaya/bindings/MayaTypeIdBinding.cpp @@ -69,6 +69,7 @@ void bindMayaTypeId() .value( "GeometryCombiner", GeometryCombinerId ) .value( "SceneShapeInterface", SceneShapeInterfaceId ) .value( "SceneShape", SceneShapeId ) + .value( "SceneShapeProxy", SceneShapeProxyId ) ; } diff --git a/src/IECoreNuke/Convert.cpp b/src/IECoreNuke/Convert.cpp index e3c1076a52..e9e8baf8f3 100644 --- a/src/IECoreNuke/Convert.cpp +++ b/src/IECoreNuke/Convert.cpp @@ -149,6 +149,20 @@ Imath::M44d convert( const DD::Image::Matrix4 &from ) return result; } +template<> +DD::Image::Matrix4 convert( const Imath::M44d& transform ) +{ + DD::Image::Matrix4 nukeMatrix; + for ( unsigned x=0; x < 4; x++ ) + { + for ( unsigned y=0; y < 4; y++ ) + { + nukeMatrix[x][y] = transform[x][y]; + } + } + return nukeMatrix; +} + template<> Imath::Box2i convert( const DD::Image::Box &from ) { @@ -161,4 +175,16 @@ DD::Image::Box3 convert( const Imath::Box3f &from ) return DD::Image::Box3( convert( from.min ), convert( from.max ) ); } +template<> +Imath::Box3f convert( const DD::Image::Box3 &from ) +{ + return Imath::Box3f( convert( from.min() ), convert( from.max() ) ); +} + +template<> +Imath::Box3d convert( const DD::Image::Box3 &from ) +{ + return Imath::Box3d( convert( from.min() ), convert( from.max() ) ); +} + } // namespace IECore diff --git a/src/IECoreNuke/LiveScene.cpp b/src/IECoreNuke/LiveScene.cpp new file mode 100644 index 0000000000..30bd508a1a --- /dev/null +++ b/src/IECoreNuke/LiveScene.cpp @@ -0,0 +1,486 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECoreNuke/LiveScene.h" + +#include "DDImage/Scene.h" + +#include "IECoreNuke/Convert.h" +#include "IECoreNuke/MeshFromNuke.h" + +#include "IECore/Exception.h" +#include "IECore/NullObject.h" +#include "IECore/TransformationMatrixData.h" + +#include "OpenEXR/ImathBoxAlgo.h" + +#include "boost/algorithm/string.hpp" +#include "boost/format.hpp" +#include "boost/tokenizer.hpp" + +using namespace IECore; +using namespace IECoreScene; +using namespace IECoreNuke; +using namespace DD::Image; + +namespace +{ + +IECore::TransformationMatrixd convertTransformMatrix( DD::Image::Matrix4& from ) +{ + auto to = IECore::TransformationMatrixd(); + DD::Image::Vector3 rotation, translation, scale, shear; + from.decompose( rotation, translation, scale, shear, DD::Image::Matrix4::RotationOrder::eXYZ ); + to.scale = IECore::convert( scale ); + to.shear = IECore::convert( shear ); + to.rotate = IECore::convert( rotation ); + to.translate = IECore::convert( translation ); + return to; +} + +} + +const std::string& LiveScene::nameAttribute( "ieName" ); + +IE_CORE_DEFINERUNTIMETYPED( LiveScene ); + +LiveScene::LiveScene() : m_op( nullptr ) +{ +} + +LiveScene::LiveScene( GeoOp *op, const std::string rootPath ) : m_op( op ), m_rootPath( rootPath ) +{ + m_pathMatcher = IECore::PathMatcher(); + m_pathMatcher.addPath( m_rootPath ); +} + +LiveScene::~LiveScene() +{ +} + +GeoOp *LiveScene::op() const +{ + return m_op; +} + +double LiveScene::timeToFrame( const double& time ) +{ + return time * DD::Image::root_real_fps(); +} + +double LiveScene::frameToTime( const int& frame ) +{ + return frame / double( DD::Image::root_real_fps() ); +} + +std::string LiveScene::geoInfoPath( const int& index ) const +{ + auto it = m_objectPathMap.find( index ); + if ( it != m_objectPathMap.end() ) + { + return it->second; + } + else + { + auto info = geometryList().object( index ); + std::string nameValue; + if( auto nameAttrib = info.get_group_attribute( GroupType::Group_Object, nameAttribute.data() ) ) + { + nameValue = nameAttrib->stdstring(); + } + else + { + nameValue = "/object" + std::to_string( index ); + } + + m_objectPathMap[index] = nameValue; + + return nameValue; + } +} + +GeometryList LiveScene::geometryList( const double* time ) const +{ + auto oc = OutputContext(); + if ( time ) + { + oc.setFrame( timeToFrame( *time ) ); + } + else + { + oc.setFrame( m_op->outputContext().frame() ); + } + + auto nodeInputOp = m_op->node_input( 0, Op::EXECUTABLE_INPUT, &oc ); + auto geoOp = dynamic_cast( nodeInputOp ); + + geoOp->validate(true); + boost::shared_ptr scene( new DD::Image::Scene() ); + geoOp->build_scene( *scene ); + + return *scene->object_list(); +} + +std::string LiveScene::fileName() const +{ + throw Exception( "IECoreNuke::LiveScene does not support fileName()." ); +} + +SceneInterface::Name LiveScene::name() const +{ + IECoreScene::SceneInterface::Path path; + IECoreScene::SceneInterface::stringToPath( m_rootPath, path ); + if ( path.empty() ) + { + return IECoreScene::SceneInterface::rootName; + } + + return *path.rbegin(); +} + +void LiveScene::path( Path &p ) const +{ + p.clear(); + IECoreScene::SceneInterface::stringToPath( m_rootPath, p ); +} + +Imath::Box3d LiveScene::readBound( double time ) const +{ + Imath::Box3d bound; + IECoreScene::SceneInterface::Path rootPath, currentPath; + for( unsigned i=0; i < geometryList( &time ).objects(); ++i ) + { + auto nameValue = geoInfoPath( i ); + auto result = m_pathMatcher.match( nameValue ); + if ( ( result != IECore::PathMatcher::AncestorMatch ) && ( result != IECore::PathMatcher::ExactMatch ) ) + { + continue; + } + IECoreScene::SceneInterface::stringToPath( m_rootPath, rootPath ); + IECoreScene::SceneInterface::stringToPath( nameValue, currentPath ); + + GeoInfo info = geometryList( &time ).object( i ); + Box3 objectBound; + if ( ( currentPath.size() > 1 ) && ( ( currentPath.size() == rootPath.size() + 1 ) || ( nameValue == m_rootPath ) ) ) + { + // object space bound + objectBound = info.bbox(); + } + else + { + objectBound = info.getTransformedBBox(); + } + Imath::Box3d b = IECore::convert( objectBound ); + + if( b.hasVolume() ) + { + bound.extendBy( b ); + } + } + + return bound; +} + +void LiveScene::writeBound( const Imath::Box3d &bound, double time ) +{ + throw Exception( "IECoreNuke::LiveScene::writeBound: write operations not supported!" ); +} + +ConstDataPtr LiveScene::readTransform( double time ) const +{ + + for( unsigned i=0; i < geometryList().objects(); ++i ) + { + auto nameValue = geoInfoPath( i ); + auto result = m_pathMatcher.match( nameValue ); + if ( result == IECore::PathMatcher::ExactMatch ) + { + auto geoInfo = geometryList( &time ).object( i ); + auto from = geoInfo.matrix; + return new TransformationMatrixdData( convertTransformMatrix( from ) ); + } + } + + return new TransformationMatrixdData( IECore::TransformationMatrixd() ); +} + +Imath::M44d LiveScene::readTransformAsMatrix( double time ) const +{ + return runTimeCast< const TransformationMatrixdData >( readTransform( time ) )->readable().transform(); +} + +void LiveScene::writeTransform( const Data *transform, double time ) +{ + throw Exception( "IECoreNuke::LiveScene::writeTransform: write operations not supported!" ); +} + +bool LiveScene::hasAttribute( const Name &name ) const +{ + throw Exception( "IECoreNuke::LiveScene does not support hasAttribute()." ); +} + +void LiveScene::attributeNames( NameList &attrs ) const +{ + throw Exception( "IECoreNuke::LiveScene does not support attributeNames()." ); +} + +ConstObjectPtr LiveScene::readAttribute( const Name &name, double time ) const +{ + throw Exception( "IECoreNuke::LiveScene does not support readAttribute()." ); +} + +void LiveScene::writeAttribute( const Name &name, const Object *attribute, double time ) +{ + throw Exception( "IECoreNuke::LiveScene::writeAttribute: write operations not supported!" ); +} + +bool LiveScene::hasTag( const Name &name, int filter ) const +{ + throw Exception( "IECoreNuke::LiveScene does not support hasTag()." ); +} + +void LiveScene::readTags( NameList &tags, int filter ) const +{ + throw Exception( "IECoreNuke::LiveScene does not support readTags()." ); +} + +void LiveScene::writeTags( const NameList &tags ) +{ + throw Exception( "IECoreNuke::LiveScene::writeTags not supported" ); +} + +SceneInterface::NameList LiveScene::setNames( bool includeDescendantSets ) const +{ + throw Exception( "IECoreNuke::LiveScene::setNames not supported" ); +} + +IECore::PathMatcher LiveScene::readSet( const Name &name, bool includeDescendantSets, const IECore::Canceller *canceller ) const +{ + throw Exception( "IECoreNuke::LiveScene::readSet not supported" ); +} + +void LiveScene::writeSet( const Name &name, const IECore::PathMatcher &set ) +{ + throw Exception( "IECoreNuke::LiveScene::writeSet not supported" ); +} + +void LiveScene::hashSet( const Name& setName, IECore::MurmurHash &h ) const +{ + throw Exception( "IECoreNuke::LiveScene::hashSet not supported" ); +} + +bool LiveScene::hasObject() const +{ + for( unsigned i=0; i < geometryList().objects(); ++i ) + { + auto nameValue = geoInfoPath( i ); + auto result = m_pathMatcher.match( nameValue ); + if ( result == IECore::PathMatcher::ExactMatch ) + { + return true; + } + } + + return false; +} + +ConstObjectPtr LiveScene::readObject( double time, const IECore::Canceller *canceller) const +{ + for( unsigned i=0; i < geometryList().objects(); ++i ) + { + auto nameValue = geoInfoPath( i ); + auto result = m_pathMatcher.match( nameValue ); + if ( result == IECore::PathMatcher::ExactMatch ) + { + auto geoInfo = geometryList( &time ).object( i ); + MeshFromNukePtr converter = new IECoreNuke::MeshFromNuke( &geoInfo ); + return converter->convert(); + } + } + + return IECore::NullObject::defaultNullObject(); +} + +PrimitiveVariableMap LiveScene::readObjectPrimitiveVariables( const std::vector &primVarNames, double time ) const +{ + throw Exception( "IECoreNuke::readObjectPrimitiveVariables() not implemented!" ); +} + +void LiveScene::writeObject( const Object *object, double time ) +{ + throw Exception( "IECoreNuke::LiveScene::writeObject: write operations not supported!" ); +} + +void LiveScene::childNames( NameList &childNames ) const +{ + childNames.clear(); + std::vector allPaths; + + for( unsigned i=0; i < geometryList().objects(); ++i ) + { + auto nameValue = geoInfoPath( i ); + auto result = m_pathMatcher.match( nameValue ); + if ( ( result == IECore::PathMatcher::AncestorMatch ) || ( result == IECore::PathMatcher::ExactMatch ) ) + { + allPaths.push_back( nameValue ); + } + } + + // filter only children + IECoreScene::SceneInterface::Path allPath, rootPath; + IECoreScene::SceneInterface::stringToPath( m_rootPath, rootPath ); + for ( auto& path : allPaths ) + { + allPath.clear(); + IECoreScene::SceneInterface::stringToPath( path, allPath ); + if ( rootPath.size() < allPath.size() ) + { + childNames.push_back( allPath[rootPath.size()] ); + } + } +} + +bool LiveScene::hasChild( const Name &name ) const +{ + IECoreScene::SceneInterface::NameList names; + childNames( names ); + + return find( names.cbegin(), names.cend(), name ) != names.cend(); +} + +SceneInterfacePtr LiveScene::child( const Name &name, MissingBehaviour missingBehaviour ) +{ + IECoreScene::SceneInterface::NameList names; + childNames( names ); + + if( find( names.cbegin(), names.cend(), name ) == names.cend() ) + { + switch ( missingBehaviour ) + { + case MissingBehaviour::ThrowIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name \"" + name.string() + "\" is not a valid childName." ); + case MissingBehaviour::NullIfMissing: + return nullptr; + case MissingBehaviour::CreateIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name\"" + name.string() + "\" is missing and LiveScene is read-only" ); + } + } + return new LiveScene( m_op, m_rootPath + "/" + name.string() ); +} + +ConstSceneInterfacePtr LiveScene::child( const Name &name, MissingBehaviour missingBehaviour ) const +{ + IECoreScene::SceneInterface::NameList names; + childNames( names ); + + if( find( names.cbegin(), names.cend(), name ) == names.cend() ) + { + switch ( missingBehaviour ) + { + case MissingBehaviour::ThrowIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name \"" + name.string() + "\" is not a valid childName." ); + case MissingBehaviour::NullIfMissing: + return nullptr; + case MissingBehaviour::CreateIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name\"" + name.string() + "\" is missing and LiveScene is read-only" ); + } + } + return new LiveScene( m_op, m_rootPath + "/" + name.string() ); +} + +SceneInterfacePtr LiveScene::createChild( const Name &name ) +{ + throw Exception( "IECoreNuke::LiveScene is read-only" ); +} + +ConstSceneInterfacePtr LiveScene::scene( const Path &path, MissingBehaviour missingBehaviour ) const +{ + IECoreNuke::ConstLiveScenePtr currentScene( this ); + for ( const auto& child : path ) + { + if ( auto childScene = currentScene->child( child, missingBehaviour ) ) + { + currentScene = dynamic_cast( childScene.get() ); + } + else + { + switch ( missingBehaviour ) + { + case MissingBehaviour::ThrowIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name \"" + child.string() + "\" is not a valid childName." ); + case MissingBehaviour::NullIfMissing: + return nullptr; + case MissingBehaviour::CreateIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name\"" + child.string() + "\" is missing and LiveScene is read-only" ); + } + } + } + + std::string pathStr; + IECoreScene::SceneInterface::pathToString( path, pathStr ); + return new LiveScene( m_op, pathStr ); +} + +SceneInterfacePtr LiveScene::scene( const Path &path, MissingBehaviour missingBehaviour ) +{ + IECoreNuke::LiveScenePtr currentScene( this ); + for ( const auto& child : path ) + { + if ( auto childScene = currentScene->child( child, missingBehaviour ) ) + { + currentScene = dynamic_cast( childScene.get() ); + } + else + { + switch ( missingBehaviour ) + { + case MissingBehaviour::ThrowIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name \"" + child.string() + "\" is not a valid childName." ); + case MissingBehaviour::NullIfMissing: + return nullptr; + case MissingBehaviour::CreateIfMissing: + throw Exception( "IECoreNuke::LiveScene: Name\"" + child.string() + "\" is missing and LiveScene is read-only" ); + } + } + } + + std::string pathStr; + IECoreScene::SceneInterface::pathToString( path, pathStr ); + return new LiveScene( m_op, pathStr ); +} + +void LiveScene::hash( HashType hashType, double time, MurmurHash &h ) const +{ + throw Exception( "Hashes currently not supported in IECoreNuke::LiveScene objects." ); +} diff --git a/src/IECoreNuke/LiveSceneHolder.cpp b/src/IECoreNuke/LiveSceneHolder.cpp new file mode 100644 index 0000000000..f5630d614f --- /dev/null +++ b/src/IECoreNuke/LiveSceneHolder.cpp @@ -0,0 +1,72 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECoreNuke/LiveSceneHolder.h" + +#include "IECoreNuke/LiveSceneKnob.h" + +using namespace IECoreNuke; + +const DD::Image::Op::Description LiveSceneHolder::g_description( "ieLiveScene", build ); + +LiveSceneHolder::LiveSceneHolder( Node *node ) + : DD::Image::GeoOp( node ) +{ +} + +LiveSceneHolder::~LiveSceneHolder() +{ +} + +void LiveSceneHolder::knobs( DD::Image::Knob_Callback f ) +{ + Op::knobs( f ); + + LiveSceneKnob::sceneKnob( f, this, "scene", "Scene" ); +} + +DD::Image::Op *LiveSceneHolder::build( Node *node ) +{ + return new LiveSceneHolder( node ); +} + +const char *LiveSceneHolder::Class() const +{ + return g_description.name; +} + +const char *LiveSceneHolder::node_help() const +{ + return "Holds cortex live scene on the \"scene\" knob."; +} diff --git a/src/IECoreNuke/LiveSceneKnob.cpp b/src/IECoreNuke/LiveSceneKnob.cpp new file mode 100644 index 0000000000..69c207431a --- /dev/null +++ b/src/IECoreNuke/LiveSceneKnob.cpp @@ -0,0 +1,89 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "IECoreNuke/LiveSceneKnob.h" + +#include "IECorePython/ScopedGILLock.h" + +using namespace IECoreNuke; +using namespace DD::Image; +using namespace boost::python; + +LiveSceneKnob::LiveSceneKnob( DD::Image::Knob_Closure* f, IECoreNuke::LiveSceneHolder* op, const char *name, const char *label ) + : DD::Image::Knob( f, name, label ), m_value( nullptr ), m_op(op) +{ + + set_flag( NO_ANIMATION ); + + // set up the object that will provide the python binding + IECorePython::ScopedGILLock gilLock; + Detail::PythonLiveSceneKnobPtr pythonKnob = new Detail::PythonLiveSceneKnob; + pythonKnob->sceneKnob = this; + object pythonKnobLiveScene( pythonKnob ); + Py_INCREF( pythonKnobLiveScene.ptr() ); + setPyObject( pythonKnobLiveScene.ptr() ); +} + +LiveSceneKnob::~LiveSceneKnob() +{ + // tidy up the object for the python binding + IECorePython::ScopedGILLock gilLock; + object pythonKnobLiveScene( handle<>( borrowed( (PyObject *)pyObject() ) ) ); + Detail::PythonLiveSceneKnobPtr pythonKnob = extract( pythonKnobLiveScene ); + pythonKnob->sceneKnob = nullptr; + Py_DECREF( pythonKnobLiveScene.ptr() ); +} + +IECoreNuke::LiveScenePtr LiveSceneKnob::getValue() +{ + if( auto geoOp = dynamic_cast( m_op ) ) + { + geoOp->validate(true); + m_value.reset(); + m_value = new IECoreNuke::LiveScene( m_op ); + } + return m_value; +} + +LiveSceneKnob *LiveSceneKnob::sceneKnob( DD::Image::Knob_Callback f, IECoreNuke::LiveSceneHolder* op, const char *name, const char *label ) +{ + return CustomKnob2( LiveSceneKnob, f, op, name, label ); +} + +const char *LiveSceneKnob::Class() const +{ + return "LiveSceneKnob"; +} diff --git a/src/IECoreNuke/SceneCacheReader.cpp b/src/IECoreNuke/SceneCacheReader.cpp index e647810a97..ee52da86f6 100644 --- a/src/IECoreNuke/SceneCacheReader.cpp +++ b/src/IECoreNuke/SceneCacheReader.cpp @@ -982,7 +982,7 @@ void SceneCacheReader::geometry_engine( DD::Image::Scene& scene, GeometryList& o for( unsigned i = 0; i < out.size(); i++ ) { - out[i].matrix = m_baseParentMatrix; + out[i].matrix = m_baseParentMatrix * m_indexToWorldTransform[i]; } } @@ -995,7 +995,7 @@ void SceneCacheReader::create_geometry( DD::Image::Scene &scene, DD::Image::Geom // something in nuke assumes we won't change anything if // rebuilding isn't needed - we get crashes if we rebuild // when not necessary. - if( !rebuild( Mask_Attributes ) && !rebuild( Mask_Matrix ) ) + if( !rebuild( Mask_Attributes )) { return; } @@ -1009,7 +1009,7 @@ void SceneCacheReader::create_geometry( DD::Image::Scene &scene, DD::Image::Geom try { - if( rebuild( Mask_Primitives ) ) + if( rebuild( Mask_Primitives ) || rebuild( Mask_Matrix ) ) { out.delete_objects(); @@ -1079,16 +1079,15 @@ void SceneCacheReader::loadPrimitive( DD::Image::GeometryList &out, const std::s Imath::M44d transformd; transformd = worldTransform( sceneInterface, rootPath, time ); - IECoreScene::TransformOpPtr transformer = new IECoreScene::TransformOp(); - transformer->inputParameter()->setValue( const_cast< IECore::Object * >(object.get()) ); // safe const_cast because the Op will copy the input object. - transformer->copyParameter()->setTypedValue( true ); - transformer->matrixParameter()->setValue( new IECore::M44dData( transformd ) ); - object = transformer->operate(); - + IECoreNuke::ToNukeGeometryConverterPtr converter = IECoreNuke::ToNukeGeometryConverter::create( object ); if (converter) { + converter->parameters()->parameter( "path" )->setValue( new IECore::StringData( itemPath ) ); converter->convert( out ); + // store the world matrix to apply in geometry_engine because + // somewhere after the create_geometry nuke reset the matrix in the SourceGeo base class. + m_indexToWorldTransform[out.objects()-1] = IECore::convert( transformd ); } } } diff --git a/src/IECoreNuke/SceneCacheWriter.cpp b/src/IECoreNuke/SceneCacheWriter.cpp new file mode 100644 index 0000000000..c78616cc1f --- /dev/null +++ b/src/IECoreNuke/SceneCacheWriter.cpp @@ -0,0 +1,114 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "IECoreNuke/SceneCacheWriter.h" + +using namespace IECore; +using namespace IECoreNuke; +using namespace IECoreScene; + +DD::Image::GeoWriter::Description SceneCacheWriter::description( "scc\0", SceneCacheWriter::Build ); + +DD::Image::GeoWriter* SceneCacheWriter::Build( DD::Image::WriteGeo* readNode ) +{ + return new SceneCacheWriter( readNode ); +} + +SceneCacheWriter::SceneCacheWriter( DD::Image::WriteGeo* writeNode ) : + DD::Image::GeoWriter( writeNode ) +{ +} + +void SceneCacheWriter::execute( DD::Image::Scene& scene ) +{ + open(); + if ( auto geoOp = dynamic_cast( geo ) ) + { + m_liveScene = new IECoreNuke::LiveScene( geoOp ); + } + + IECoreScene::SceneInterface::NameList names; + m_liveScene->childNames( names ); + for ( const auto& name : names ) + { + writeLocation( m_liveScene, m_writer, name ); + } +} + +bool SceneCacheWriter::animation() const +{ + return true; +} + +bool SceneCacheWriter::open() +{ + if ( !m_writer ) + { + m_writer = IECoreScene::SceneInterface::create( std::string( filename() ), IECore::IndexedIO::Write ); + } + + return true; +} + +void SceneCacheWriter::writeLocation( ConstSceneInterfacePtr inScene, SceneInterfacePtr outScene, const IECore::InternedString& childName ) +{ + auto time = LiveScene::frameToTime( frame() ); + ConstSceneInterfacePtr inChild = inScene->child( childName, SceneInterface::MissingBehaviour::ThrowIfMissing ); + auto outChild = outScene->child( childName, IECoreScene::SceneInterface::MissingBehaviour::CreateIfMissing ); + try + { + outChild->writeTransform( inChild->readTransform( time ).get(), time ); + } + catch ( ... ) + { + } + if ( inChild->hasObject() ) + { + try + { + outChild->writeObject( inChild->readObject( time ).get(), time ); + } + catch ( ... ) + { + } + } + + // recursion + SceneInterface::NameList grandChildNames; + inChild->childNames( grandChildNames ); + for ( auto& grandChildName : grandChildNames ) + { + writeLocation( inChild, outChild, grandChildName ); + } +} diff --git a/src/IECoreNuke/ToNukeGeometryConverter.cpp b/src/IECoreNuke/ToNukeGeometryConverter.cpp index 704e13605b..fc5d3ac415 100644 --- a/src/IECoreNuke/ToNukeGeometryConverter.cpp +++ b/src/IECoreNuke/ToNukeGeometryConverter.cpp @@ -33,6 +33,7 @@ ////////////////////////////////////////////////////////////////////////// #include "IECoreNuke/ToNukeGeometryConverter.h" +#include "IECoreNuke/LiveScene.h" #include "IECore/CompoundData.h" #include "IECore/CompoundParameter.h" @@ -53,6 +54,9 @@ ToNukeGeometryConverter::ToNukeGeometryConverter( const std::string &description m_objIndexParameter = new IntParameter( "objIndex", "Index for the first object inserted on the GeometryList. Use -1 to simply add on the next index available", -1 ); parameters()->addParameter( m_objIndexParameter ); + m_pathParameter = new StringParameter( "path", "The object path in the hierarchy.", new StringData() ); + parameters()->addParameter( m_pathParameter ); + } void ToNukeGeometryConverter::convert( GeometryList &geoList ) const @@ -63,6 +67,10 @@ void ToNukeGeometryConverter::convert( GeometryList &geoList ) const objIndex = (int)geoList.objects(); } geoList.add_object(objIndex); + + // add path attribute + auto nameAttribute = geoList.writable_attribute( objIndex, GroupType::Group_Object, IECoreNuke::LiveScene::nameAttribute.data(), AttribType::STD_STRING_ATTRIB); + nameAttribute->stdstring() = m_pathParameter->getTypedValue(); ConstCompoundObjectPtr operands = parameters()->getTypedValidatedValue(); doConversion( srcParameter()->getValidatedValue(), geoList, objIndex, operands.get() ); diff --git a/src/IECoreNuke/bindings/IECoreNuke.cpp b/src/IECoreNuke/bindings/IECoreNuke.cpp index 6f5f9ab29c..cd5ffeabcc 100644 --- a/src/IECoreNuke/bindings/IECoreNuke.cpp +++ b/src/IECoreNuke/bindings/IECoreNuke.cpp @@ -37,12 +37,16 @@ #include "IECoreNuke/bindings/FnOpHolderBinding.h" #include "IECoreNuke/bindings/FnParameterisedHolderBinding.h" #include "IECoreNuke/bindings/ObjectKnobBinding.h" +#include "IECoreNuke/bindings/LiveSceneBinding.h" +#include "IECoreNuke/bindings/LiveSceneKnobBinding.h" using namespace boost::python; using namespace IECoreNuke; BOOST_PYTHON_MODULE( _IECoreNuke ) { + bindLiveScene(); + bindLiveSceneKnob(); bindObjectKnob(); bindFnParameterisedHolder(); bindFnOpHolder(); diff --git a/src/IECoreNuke/bindings/LiveSceneBinding.cpp b/src/IECoreNuke/bindings/LiveSceneBinding.cpp new file mode 100644 index 0000000000..8376013cb4 --- /dev/null +++ b/src/IECoreNuke/bindings/LiveSceneBinding.cpp @@ -0,0 +1,51 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "IECoreNuke/LiveScene.h" +#include "IECoreNuke/bindings/LiveSceneBinding.h" + +#include "IECorePython/IECoreBinding.h" +#include "IECorePython/RunTimeTypedBinding.h" + +using namespace IECoreNuke; +using namespace boost::python; + +void IECoreNuke::bindLiveScene() +{ + IECorePython::RunTimeTypedClass() + .def( init<>() ) + ; +} diff --git a/src/IECoreNuke/bindings/LiveSceneKnobBinding.cpp b/src/IECoreNuke/bindings/LiveSceneKnobBinding.cpp new file mode 100644 index 0000000000..80ed648a65 --- /dev/null +++ b/src/IECoreNuke/bindings/LiveSceneKnobBinding.cpp @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include "boost/python.hpp" + +#include "IECoreNuke/LiveSceneKnob.h" + +#include "IECorePython/RefCountedBinding.h" + +#include "IECore/Exception.h" + +using namespace boost::python; + +namespace IECoreNuke +{ + +// always check your knob before using it +static void check( Detail::PythonLiveSceneKnob &knob ) +{ + if( !knob.sceneKnob ) + { + throw( IECore::InvalidArgumentException( "Knob not alive." ) ); + } +} + +static const char *name( Detail::PythonLiveSceneKnob &knob ) +{ + check( knob ); + return knob.sceneKnob->name().c_str(); +} + +static const char *label( Detail::PythonLiveSceneKnob &knob ) +{ + check( knob ); + return knob.sceneKnob->label().c_str(); +} + +static IECoreNuke::LiveScenePtr getValue( Detail::PythonLiveSceneKnob &knob ) +{ + check( knob ); + IECoreNuke::LiveScenePtr v = knob.sceneKnob->getValue(); + return v; +} + +void bindLiveSceneKnob() +{ + + IECorePython::RefCountedClass( "LiveSceneKnob" ) + .def( "name", &name ) + .def( "label", &label ) + .def( "getValue", &getValue ) + ; + +} + +} // namespace IECoreNuke diff --git a/src/IECoreNuke/plugin/menu.py b/src/IECoreNuke/plugin/menu.py index 9ab7609e0a..2233b8cbb3 100644 --- a/src/IECoreNuke/plugin/menu.py +++ b/src/IECoreNuke/plugin/menu.py @@ -52,5 +52,6 @@ cortexMenu = nodesMenu.addMenu( "Cortex", icon="CortexMenu.png" ) cortexMenu.addCommand( "Display", "nuke.createNode( 'ieDisplay' )" ) + cortexMenu.addCommand( "LiveScene", "nuke.createNode( 'ieLiveScene' )" ) cortexMenu.addCommand( "LensDistort", "nuke.createNode( 'ieLensDistort' )" ) cortexMenu.addCommand( "SceneCacheReader", "nuke.createNode( 'ieSceneCacheReader' )" ) diff --git a/test/IECoreMaya/All.py b/test/IECoreMaya/All.py index 7602d93e6c..ce9cf6e090 100644 --- a/test/IECoreMaya/All.py +++ b/test/IECoreMaya/All.py @@ -78,6 +78,7 @@ from ToMayaCameraConverterTest import ToMayaCameraConverterTest from LiveSceneTest import * from SceneShapeTest import SceneShapeTest +from SceneShapeProxyTest import SceneShapeProxyTest from FnSceneShapeTest import FnSceneShapeTest from FromMayaLocatorConverterTest import FromMayaLocatorConverterTest from ToMayaLocatorConverterTest import ToMayaLocatorConverterTest diff --git a/test/IECoreMaya/FnSceneShapeTest.py b/test/IECoreMaya/FnSceneShapeTest.py index e2021a6812..5b5cd2e8c7 100644 --- a/test/IECoreMaya/FnSceneShapeTest.py +++ b/test/IECoreMaya/FnSceneShapeTest.py @@ -113,6 +113,32 @@ def __setupTableProp( self ): mat = IECore.TransformationMatrixd( s, r, t ) tableTop_GEO.writeTransform( IECore.TransformationMatrixdData(mat), 0 ) + def testClassTypeInstantiation( self ): + + sceneShapeNode = maya.cmds.createNode( "ieSceneShape" ) + sceneShapeFn = IECoreMaya.FnSceneShape( sceneShapeNode ) + self.assertEqual( sceneShapeFn.__class__, IECoreMaya.FnSceneShape ) + + sceneShapeProxyNode = maya.cmds.createNode( "ieSceneShapeProxy" ) + sceneShapeProxyFn = IECoreMaya.FnSceneShape( sceneShapeProxyNode ) + self.assertEqual( sceneShapeProxyFn.__class__, IECoreMaya._FnSceneShapeProxy ) + + def testCreateShapeType( self ): + + sceneShapeTransform = maya.cmds.createNode( "transform" ) + sceneShapeFn = IECoreMaya.FnSceneShape.createShape( sceneShapeTransform, shapeType="ieSceneShape") + self.assertEqual( sceneShapeFn.__class__, IECoreMaya.FnSceneShape ) + + sceneShapeNode = maya.cmds.listRelatives( sceneShapeTransform, shapes=True) + self.assertTrue( maya.cmds.objectType( sceneShapeNode, isType="ieSceneShape" )) + + sceneShapeProxyTransform = maya.cmds.createNode( "transform" ) + sceneShapeProxyFn = IECoreMaya.FnSceneShape.createShape( sceneShapeProxyTransform, shapeType="ieSceneShapeProxy" ) + self.assertEqual( sceneShapeProxyFn.__class__, IECoreMaya._FnSceneShapeProxy ) + + sceneShapeProxyNode = maya.cmds.listRelatives( sceneShapeProxyTransform, shapes=True) + self.assertTrue( maya.cmds.objectType( sceneShapeProxyNode, isType="ieSceneShapeProxy" )) + def testSceneInterface( self ) : maya.cmds.file( new=True, f=True ) diff --git a/test/IECoreMaya/SceneShapeProxyTest.py b/test/IECoreMaya/SceneShapeProxyTest.py new file mode 100644 index 0000000000..67f90a1b9c --- /dev/null +++ b/test/IECoreMaya/SceneShapeProxyTest.py @@ -0,0 +1,48 @@ +########################################################################## +# +# Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Image Engine Design nor the names of any +# other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + + +import IECoreMaya +import maya.cmds + +import SceneShapeTest + +class SceneShapeProxyTest( SceneShapeTest.SceneShapeTest ): + + def setUp( self ): + IECoreMaya.TestCase.setUp(self) + self._node = maya.cmds.createNode( "ieSceneShapeProxy" ) + +if __name__ == "__main__": + IECoreMaya.TestProgram( plugins = [ "ieCore" ] ) diff --git a/test/IECoreMaya/SceneShapeTest.py b/test/IECoreMaya/SceneShapeTest.py index aa66d6841e..79864ef68a 100644 --- a/test/IECoreMaya/SceneShapeTest.py +++ b/test/IECoreMaya/SceneShapeTest.py @@ -48,9 +48,9 @@ class SceneShapeTest( IECoreMaya.TestCase ) : __testPlugAnimFile = "test/testPlugAnim.scc" __testPlugAttrFile = "test/testPlugAttr.scc" - def setUp( self ) : - - maya.cmds.file( new=True, f=True ) + def setUp( self ): + super( SceneShapeTest, self ).setUp() + self._node = maya.cmds.createNode( "ieSceneShape" ) def writeSCC( self, file, rotation=imath.V3d( 0, 0, 0 ), time=0 ) : @@ -136,8 +136,7 @@ def testComputePlugs( self ) : self.writeSCC( file = SceneShapeTest.__testFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node maya.cmds.setAttr( node+'.file', SceneShapeTest.__testFile,type='string' ) maya.cmds.setAttr( node+'.root',"/",type='string' ) @@ -179,13 +178,11 @@ def testComputePlugs( self ) : self.assertEqual( maya.cmds.getAttr( node+".outBound", mi=True ), [1]) self.assertEqual( maya.cmds.getAttr( node+".outObjects", mi=True ), [2]) - def testPlugValues( self ) : self.writeSCC( file=SceneShapeTest.__testPlugFile, rotation = imath.V3d( 0, 0, IECore.degreesToRadians( -30 ) ) ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node maya.cmds.setAttr( node+'.file', SceneShapeTest.__testPlugFile,type='string' ) maya.cmds.setAttr( node+'.root',"/",type='string' ) @@ -291,13 +288,11 @@ def testPlugValues( self ) : maya.cmds.setAttr( node+'.time', 5 ) self.assertEqual( maya.cmds.getAttr( node+".outTime" ), 5 ) - def testAnimPlugValues( self ) : self.writeAnimSCC( file=SceneShapeTest.__testPlugAnimFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node maya.cmds.connectAttr( "time1.outTime", node+".time" ) maya.cmds.setAttr( node+'.file', SceneShapeTest.__testPlugAnimFile,type='string' ) @@ -413,13 +408,11 @@ def testAnimPlugValues( self ) : self.assertAlmostEqual( maya.cmds.getAttr( node+".outTransform[2].outRotateZ"), 0.0 ) self.assertEqual( maya.cmds.getAttr( node+".outTransform[2].outScale"), [(1.0, 1.0, 1.0)] ) - - def testqueryAttributes( self ) : + def testQueryAttributes( self ) : self.writeAttributeSCC( file=SceneShapeTest.__testPlugAttrFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node maya.cmds.setAttr( node+'.file', SceneShapeTest.__testPlugAttrFile,type='string' ) maya.cmds.setAttr( node+".queryPaths[0]", "/1", type="string") @@ -458,8 +451,7 @@ def testTags( self ) : self.writeTagSCC( file=SceneShapeTest.__testFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node fn = IECoreMaya.FnSceneShape( node ) transform = str(maya.cmds.listRelatives( node, parent=True )[0]) maya.cmds.setAttr( node+'.file', SceneShapeTest.__testFile, type='string' ) @@ -492,8 +484,7 @@ def testLiveSceneTags( self ) : self.writeTagSCC( file=SceneShapeTest.__testFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node fn = IECoreMaya.FnSceneShape( node ) transform = str(maya.cmds.listRelatives( node, parent=True )[0]) maya.cmds.setAttr( node+'.file', SceneShapeTest.__testFile, type='string' ) @@ -528,8 +519,7 @@ def testLinkedLiveSceneTags( self ) : self.writeTagSCC( file=SceneShapeTest.__testFile ) - maya.cmds.file( new=True, f=True ) - node = maya.cmds.createNode( 'ieSceneShape' ) + node = self._node fn = IECoreMaya.FnSceneShape( node ) transform = str(maya.cmds.listRelatives( node, parent=True )[0]) maya.cmds.setAttr( node+'.file', SceneShapeTest.__testFile, type='string' ) diff --git a/test/IECoreNuke/All.py b/test/IECoreNuke/All.py index a1ed4da447..e5a564c734 100644 --- a/test/IECoreNuke/All.py +++ b/test/IECoreNuke/All.py @@ -48,6 +48,8 @@ from OpHolderTest import OpHolderTest from SceneCacheReaderTest import SceneCacheReaderTest from PNGReaderTest import PNGReaderTest +from LiveSceneKnobTest import LiveSceneKnobTest +from SceneCacheWriterTest import SceneCacheWriterTest unittest.TestProgram( testRunner = unittest.TextTestRunner( diff --git a/test/IECoreNuke/LiveSceneKnobTest.py b/test/IECoreNuke/LiveSceneKnobTest.py new file mode 100644 index 0000000000..65c0bd3e3c --- /dev/null +++ b/test/IECoreNuke/LiveSceneKnobTest.py @@ -0,0 +1,331 @@ +########################################################################## +# +# Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Image Engine Design nor the names of any +# other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import unittest + +import nuke + +import IECore +import IECoreNuke + +class LiveSceneKnobTest( IECoreNuke.TestCase ) : + + def testNameAndLabel( self ) : + + n = nuke.createNode( "ieLiveScene" ) + + k = n.knob( "scene" ) + + self.assertEqual( k.name(), "scene" ) + self.assertEqual( k.label(), "Scene" ) + + def testAccessors( self ) : + + n = nuke.createNode( "ieLiveScene" ) + + k = n.knob( "scene" ) + + self.assertTrue( isinstance( k, IECoreNuke.LiveSceneKnob ) ) + + self.assertTrue( isinstance( k.getValue(), IECoreNuke.LiveScene ) ) + + def testQueryScene( self ) : + + sph = nuke.createNode( "Sphere" ) + cub = nuke.createNode( "Cube" ) + sn = nuke.createNode( "Scene") + sn.setInput( 0, sph ) + sn.setInput( 1, cub ) + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sn ) + + k = n.knob( "scene" ) + + self.assertTrue( isinstance( k, IECoreNuke.LiveSceneKnob ) ) + + self.assertTrue( isinstance( k.getValue(), IECoreNuke.LiveScene ) ) + + def testNameAndPath( self ): + import IECoreScene + sph = nuke.createNode( "Sphere" ) + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sph ) + + liveScene = n.knob( "scene" ).getValue() + + self.assertEqual( liveScene.name(), "/" ) + self.assertEqual( liveScene.path(), [] ) + + sceneFile = "test/IECoreNuke/scripts/animatedSpheres.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + n.setInput( 0, sceneReader ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + self.assertEqual( liveScene.name(), expectedScene.name() ) + self.assertEqual( liveScene.path(), expectedScene.path() ) + + sceneA = liveScene.scene( ["A"] ) + expectedSceneA = expectedScene.scene( ["A"] ) + + self.assertEqual( sceneA.name(), expectedSceneA.name() ) + self.assertEqual( sceneA.path(), expectedSceneA.path() ) + + sceneBb = liveScene.scene( ["B", "b"] ) + expectedSceneBb = expectedScene.scene( ["B", "b"] ) + + self.assertEqual( sceneBb.name(), expectedSceneBb.name() ) + self.assertEqual( sceneBb.path(), expectedSceneBb.path() ) + + def testChildNames( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + + self.assertEqual( sorted( liveScene.childNames() ) , sorted( expectedScene.childNames() ) ) + + self.assertTrue( liveScene.hasChild( "B" ) ) + self.assertFalse( liveScene.hasChild( "wrongChild" ) ) + + self.assertRaises( RuntimeError, liveScene.child, "wrongChild" ) + self.assertFalse( liveScene.child( "wrongChild", IECoreScene.SceneInterface.MissingBehaviour.NullIfMissing ) ) + self.assertRaises( RuntimeError, liveScene.child, "wrongChild", IECoreScene.SceneInterface.MissingBehaviour.CreateIfMissing ) + + for subPath in ( ["B"], ["A"] ): + subScene = liveScene.scene( subPath ) + subExpectedScene = expectedScene.scene( subPath ) + + self.assertEqual( subScene.childNames(), subExpectedScene.childNames() ) + + self.assertRaises( RuntimeError, liveScene.scene, ["B", "wrongChild"] ) + self.assertFalse( liveScene.scene( ["B", "wrongChild"], IECoreScene.SceneInterface.MissingBehaviour.NullIfMissing ) ) + self.assertRaises( RuntimeError, liveScene.scene, ["B", "wrongChild"], IECoreScene.SceneInterface.MissingBehaviour.CreateIfMissing ) + + def assertAlmostEqualBound( self, box1, box2 ): + for dim in range( 3 ): + self.assertAlmostEqual( box1.size()[dim], box2.size()[dim], 4 ) + + def testBounds( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + for time in (0, 1, 2, 3): + liveSceneBound = liveScene.readBound( time ) + expectedSceneBound = expectedScene.readBound( time ) + self.assertAlmostEqualBound( liveSceneBound, expectedSceneBound ) + + for subPath in ( ["B"], ["A"] ): + subScene = liveScene.scene( subPath ) + subExpectedScene = expectedScene.scene( subPath ) + + self.assertEqual( subScene.readBound(0), subExpectedScene.readBound(0) ) + + def testAnimatedBounds( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/animatedTransform.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/group/cube'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + for time in (0, 1, 2): + liveSceneBound = liveScene.readBound( time ) + expectedSceneBound = expectedScene.readBound( time ) + self.assertAlmostEqualBound( liveSceneBound, expectedSceneBound ) + + for subPath in ( ["group"], ["group", "cube"] ): + subScene = liveScene.scene( subPath ) + subExpectedScene = expectedScene.scene( subPath ) + + self.assertEqual( subScene.readBound(0), subExpectedScene.readBound(0) ) + + def assertAlmostEqualTransform( self, tran1, tran2 ): + for x in range( 4 ): + for y in range( 4 ): + self.assertAlmostEqual( tran1[x][y], tran2[x][y], 4 ) + + def _checkNukeSceneTransform( self, liveScene, expectedScene, matrix, time ): + import imath + # combine the matrix of each parent as the SceneCacheReader does + matrix = expectedScene.readTransform( time ).value * matrix + if liveScene.childNames(): + # each parent location have an identity matrix + self.assertEqual( liveScene.readTransform( time ).value.transform, imath.M44d() ) + for childName in liveScene.childNames(): + self._checkNukeSceneTransform( liveScene.child( childName ), expectedScene.child( childName ), matrix, time ) + else: + # check the leaf matrix is the same as the combined parents' matrix + self.assertAlmostEqualTransform( liveScene.readTransform( time ).value.transform, matrix ) + + def testTransform( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + for time in (0, 1, 2, 3): + liveSceneTransform = liveScene.readTransform( time ) + expectedSceneTransform = expectedScene.readTransform( time ) + self.assertEqual( liveSceneTransform.value.transform, expectedSceneTransform.value ) + + # check the leaf has the world matrix + for childName in liveScene.childNames(): + # root transform + matrix = expectedScene.readTransform( 0 ).value + self._checkNukeSceneTransform( liveScene.child( childName ), expectedScene.child( childName ), matrix, 0 ) + + def testAnimatedTransform( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/animatedTransform.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/group/cube'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + for time in (0, 1, 2): + liveSceneTransform = liveScene.readTransform( time ) + expectedSceneTransform = expectedScene.readTransform( time ) + self.assertEqual( liveSceneTransform.value.transform, expectedSceneTransform.value ) + + # check the leaf has the world matrix + for childName in liveScene.childNames(): + # root transform + matrix = expectedScene.readTransform( 0 ).value + self._checkNukeSceneTransform( liveScene.child( childName ), expectedScene.child( childName ), matrix, 0 ) + + def testHasObject( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + self.assertFalse( liveScene.hasObject() ) + + for subPath in ( ["A"], ["B"] ): + self.assertFalse( liveScene.scene( subPath ).hasObject() ) + + for subPath in ( ["A", "a"], ["B", "b"] ): + self.assertTrue( liveScene.scene( subPath ).hasObject() ) + + def testReadObjet( self ): + import IECoreScene + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + n = nuke.createNode( "ieLiveScene" ) + n.setInput( 0, sceneReader ) + + liveScene = n.knob( "scene" ).getValue() + objectA = liveScene.scene( [ "A", "a" ] ).readObject( 0 ) + expectedObjectA = expectedScene.scene( [ "A", "a" ] ).readObject( 0 ) + + self.assertEqual( objectA.topologyHash(), expectedObjectA.topologyHash() ) + self.assertEqual( objectA.keys(), [ "P", "uv" ] ) + + +if __name__ == "__main__": + unittest.main() + diff --git a/test/IECoreNuke/SceneCacheWriterTest.py b/test/IECoreNuke/SceneCacheWriterTest.py new file mode 100644 index 0000000000..95dc4af641 --- /dev/null +++ b/test/IECoreNuke/SceneCacheWriterTest.py @@ -0,0 +1,128 @@ +########################################################################## +# +# Copyright (c) 2022, Image Engine Design Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Image Engine Design nor the names of any +# other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import unittest +import os +import shutil +import tempfile + +import nuke + +import imath + +import IECore +import IECoreScene +import IECoreNuke + +class SceneCacheWriterTest( IECoreNuke.TestCase ) : + + def setUp( self ) : + self.__temporaryDirectory = None + + def tearDown( self ) : + if self.__temporaryDirectory is not None : + shutil.rmtree( self.__temporaryDirectory ) + + def temporaryDirectory( self ) : + + if self.__temporaryDirectory is None : + self.__temporaryDirectory = tempfile.mkdtemp( prefix = "ieCoreNukeTest" ) + + return self.__temporaryDirectory + + def testWriteSimpleSphere( self ): + + outputFile = os.path.join( self.temporaryDirectory(), "sphere.scc" ) + + sphere = nuke.createNode( "Sphere" ) + writer = nuke.createNode( "WriteGeo" ) + writer["file"].fromScript( outputFile ) + + nuke.execute( writer, 1001, 1001 ) + + self.assertTrue( os.path.exists( outputFile ) ) + + scene = IECoreScene.SharedSceneInterfaces.get( outputFile ) + + self.assertEqual( scene.childNames(), ["object0"] ) + self.assertEqual( scene.readTransform( 0 ).value, imath.M44d() ) + + liveSceneHolder = nuke.createNode( "ieLiveScene" ) + liveSceneHolder.setInput( 0, sphere ) + + liveScene = liveSceneHolder["scene"].getValue() + + liveSceneMesh = liveScene.scene( ["object0"] ).readObject( 0 ) + mesh = scene.scene( ["object0"] ).readObject( 0 ) + + self.assertEqual( mesh.topologyHash(), liveSceneMesh.topologyHash() ) + + + def testWriteSceneCacheReader( self ): + import random + import IECoreScene + + outputFile = os.path.join( self.temporaryDirectory(), "scene.scc" ) + + sceneFile = "test/IECoreNuke/scripts/data/liveSceneData.scc" + sceneReader = nuke.createNode( "ieSceneCacheReader" ) + sceneReader.knob( "file" ).setValue( sceneFile ) + expectedScene = IECoreScene.SharedSceneInterfaces.get( sceneFile ) + + sceneReader.forceValidate() + widget = sceneReader.knob( "sceneView" ) + widget.setSelectedItems( ['/root/A/a', '/root/B/b'] ) + + writer = nuke.createNode( "WriteGeo" ) + writer["file"].fromScript( outputFile ) + + nuke.execute( writer, 1, 48 ) + + scene = IECoreScene.SharedSceneInterfaces.get( outputFile ) + + self.assertEqual( scene.childNames(), expectedScene.childNames() ) + for time in range( 0, 3 ): + self.assertAlmostEqual( scene.readBound( time ).min(), expectedScene.readBound( time ).min() ) + mesh = scene.scene( ["B", "b"] ).readObject( time ) + expectedMesh = expectedScene.scene( ["B", "b"] ).readObject( time ) + random.seed( 12 ) + for i in range( 12 ): + pointIndex = random.choice( range( len( mesh["P"].data ) ) ) + self.assertAlmostEqual( mesh["P"].data[pointIndex], expectedMesh["P"].data[pointIndex], 4 ) + + +if __name__ == "__main__": + unittest.main() + + diff --git a/test/IECoreNuke/scripts/data/animatedTransform.scc b/test/IECoreNuke/scripts/data/animatedTransform.scc new file mode 100644 index 0000000000..5012579de1 Binary files /dev/null and b/test/IECoreNuke/scripts/data/animatedTransform.scc differ diff --git a/test/IECoreNuke/scripts/data/liveSceneData.scc b/test/IECoreNuke/scripts/data/liveSceneData.scc new file mode 100644 index 0000000000..a84d8141c6 Binary files /dev/null and b/test/IECoreNuke/scripts/data/liveSceneData.scc differ