diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3fbd1456fc3..39b84e87fff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -77,7 +77,7 @@ jobs: testRunner: sudo -E -u testUser sconsCacheMegabytes: 400 jobs: 4 - dependenciesURL: https://github.com/ImageEngine/cortex/releases/download/10.7.0.0a7/cortex-10.7.0.0a7-linux-platform24.tar.gz + dependenciesURL: https://github.com/ImageEngine/cortex/releases/download/10.7.0.0a8/cortex-10.7.0.0a8-linux-platform24.tar.gz extraBuildArguments: CYCLES_ROOT="" - name: windows diff --git a/.github/workflows/main/installDependencies.py b/.github/workflows/main/installDependencies.py index 30cd0e2b22f..8f724c712bb 100755 --- a/.github/workflows/main/installDependencies.py +++ b/.github/workflows/main/installDependencies.py @@ -49,7 +49,7 @@ # Determine default archive URL. -defaultURL = "https://github.com/GafferHQ/dependencies/releases/download/11.0.0a4/gafferDependencies-11.0.0a4-{platform}-{vfxPlatform}.{extension}" +defaultURL = "https://github.com/ImageEngine/cortex/releases/download/10.7.0.0a8/cortex-10.7.0.0a8-{platform}-{vfxPlatform}.{extension}" # Parse command line arguments. diff --git a/Changes.md b/Changes.md index fd1d9680cdd..1fa77d084fb 100644 --- a/Changes.md +++ b/Changes.md @@ -5,6 +5,7 @@ Features -------- - Cycles : Updated to version 5.0.0. +- CurvesInterpolation : Added node for modifying CurvesPrimitive `basis` and `wrap`. This includes the ability to convert curves with `Pinned` wrap to `NonPeriodic`, adding the appropriate "phantom" points to maintain curve shape. Improvements ------------ @@ -20,6 +21,9 @@ Improvements - CyclesOptions : Added `cycles:integrator:volume_ray_marching` option. - LightEditor : Added column for `cycles:visibility:camera` attribute. - OpenColorIO : Added ACES Studio 2.0 config. The default config is still ACES 1.3, due to RenderMan not supporting ACES 2.0. +- CurvesPrimitive : Added `Pinned` wrap mode in addition to the existing `Periodic` and `NonPeriodic` modes. This conveniently interpolates CatmullRom +and BSpline curves to their endpoints automatically, without manual management of duplicate endpoints or "phantom vertices". +- SceneReader, SceneWriter : Added support for pinned UsdGeomBasisCurves. Fixes ----- @@ -28,6 +32,9 @@ Fixes - UI : Fixed failure to cancel background computations when more than one UI element was waiting for the same result. This could result in the UI becoming unresponsive until the computation was complete. - BackgroundMethod : Fixed bug that allowed unwanted background computations to continue when a widget was hidden. - MeshTessellate : Fixed crashes caused by non-manifold geometry. +- 3Delight : Fixed rendering of linear curves. +- DeleteCurves : Fixed deletion of periodic curves. +- ResamplePrimitiveVariables : Fixed resampling between Vertex and Varying for linear curves. API --- @@ -71,7 +78,7 @@ Build ----- - Boost : Updated to version 1.85.0. -- Cortex : Updated to version 10.7.0.0a7. +- Cortex : Updated to version 10.7.0.0a8. - Cycles : Updated to version 5.0.0. - Embree : Updated to version 4.4.0. - Imath : Updated to version 3.1.12. diff --git a/include/GafferScene/CurvesInterpolation.h b/include/GafferScene/CurvesInterpolation.h new file mode 100644 index 00000000000..b038e492535 --- /dev/null +++ b/include/GafferScene/CurvesInterpolation.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2026, Cinesite VFX Ltd. 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 John Haddon 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. +// +////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "GafferScene/ObjectProcessor.h" + +#include "Gaffer/OptionalValuePlug.h" + +namespace GafferScene +{ + +class GAFFERSCENE_API CurvesInterpolation : public ObjectProcessor +{ + + public : + + explicit CurvesInterpolation( const std::string &name=defaultName() ); + ~CurvesInterpolation() override; + + GAFFER_NODE_DECLARE_TYPE( GafferScene::CurvesInterpolation, CurvesInterpolationTypeId, ObjectProcessor ); + + /// Values are from `IECore::StandardCubicBasis`. + Gaffer::OptionalValuePlug *basisPlug(); + const Gaffer::OptionalValuePlug *basisPlug() const; + + Gaffer::OptionalValuePlug *wrapPlug(); + const Gaffer::OptionalValuePlug *wrapPlug() const; + + Gaffer::BoolPlug *expandPinnedPlug(); + const Gaffer::BoolPlug *expandPinnedPlug() const; + + protected : + + bool affectsProcessedObject( const Gaffer::Plug *input ) const override; + void hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const override; + + private : + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( CurvesInterpolation ) + +} // namespace GafferScene diff --git a/include/GafferScene/TypeIds.h b/include/GafferScene/TypeIds.h index 458f9818bb2..b0d661a5087 100644 --- a/include/GafferScene/TypeIds.h +++ b/include/GafferScene/TypeIds.h @@ -196,6 +196,7 @@ enum TypeId CameraQueryTypeId = 120151, ClosurePlugTypeId = 120152, ReflectionConstraintTypeId = 120153, + CurvesInterpolationTypeId = 120154, LastTypeId = 120999 }; diff --git a/python/GafferSceneTest/CurvesInterpolationTest.py b/python/GafferSceneTest/CurvesInterpolationTest.py new file mode 100644 index 00000000000..686dac8581e --- /dev/null +++ b/python/GafferSceneTest/CurvesInterpolationTest.py @@ -0,0 +1,152 @@ +########################################################################## +# +# Copyright (c) 2026, Cinesite VFX Ltd. 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 John Haddon 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 imath + +import IECore +import IECoreScene + +import Gaffer +import GafferScene +import GafferSceneTest + +class CurvesInterpolationTest( GafferSceneTest.SceneTestCase ) : + + def testPassThrough( self ) : + + objectToScene = GafferScene.ObjectToScene() + objectToScene["object"].setValue( + IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), IECore.CubicBasisf.catmullRom() + ) + ) + + interpolation = GafferScene.CurvesInterpolation() + interpolation["in"].setInput( objectToScene["out"] ) + + # No filter + + self.assertTrue( interpolation["out"].exists( "/object" ) ) + self.assertScenesEqual( interpolation["out"], interpolation["in"] ) + self.assertSceneHashesEqual( interpolation["out"], interpolation["in"] ) + + # Filter, but not matching anything yet + + pathFilter = GafferScene.PathFilter() + interpolation["filter"].setInput( pathFilter["out"] ) + + self.assertTrue( interpolation["out"].exists( "/object" ) ) + self.assertScenesEqual( interpolation["out"], interpolation["in"] ) + self.assertSceneHashesEqual( interpolation["out"], interpolation["in"] ) + + # Filter matching something, but node disabled + + pathFilter["paths"].setValue( IECore.StringVectorData( [ "/object" ] ) ) + interpolation["enabled"].setValue( False ) + + self.assertTrue( interpolation["out"].exists( "/object" ) ) + self.assertScenesEqual( interpolation["out"], interpolation["in"] ) + self.assertSceneHashesEqual( interpolation["out"], interpolation["in"] ) + + # Node enabled, but not doing anything + + interpolation["enabled"].setValue( True ) + + self.assertTrue( interpolation["out"].exists( "/object" ) ) + self.assertScenesEqual( interpolation["out"], interpolation["in"] ) + self.assertSceneHashesEqual( interpolation["out"], interpolation["in"] ) + + def testChangeWrap( self ) : + + objectToScene = GafferScene.ObjectToScene() + objectToScene["object"].setValue( + IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), IECore.CubicBasisf.catmullRom(), + wrap = IECoreScene.CurvesPrimitive.Wrap.Pinned, + p = IECore.V3fVectorData( [ imath.V3f( x ) for x in range( 0, 4 ) ] ) + ) + ) + + pathFilter = GafferScene.PathFilter() + pathFilter["paths"].setValue( IECore.StringVectorData( [ "/object" ] ) ) + + interpolation = GafferScene.CurvesInterpolation() + interpolation["in"].setInput( objectToScene["out"] ) + interpolation["filter"].setInput( pathFilter["out"] ) + + self.assertEqual( interpolation["out"].object( "/object" ).wrap(), IECoreScene.CurvesPrimitive.Wrap.Pinned ) + + interpolation["wrap"]["enabled"].setValue( True ) + interpolation["wrap"]["value"].setValue( IECoreScene.CurvesPrimitive.Wrap.NonPeriodic ) + + self.assertEqual( interpolation["out"].object( "/object" ).wrap(), IECoreScene.CurvesPrimitive.Wrap.NonPeriodic ) + self.assertEqual( interpolation["out"].object( "/object" )["P"], interpolation["in"].object( "/object" )["P"] ) + + interpolation["expandPinned"].setValue( True ) + self.assertEqual( interpolation["out"].object( "/object" ).wrap(), IECoreScene.CurvesPrimitive.Wrap.NonPeriodic ) + self.assertEqual( + interpolation["out"].object( "/object" )["P"].data, + IECore.V3fVectorData( [ imath.V3f( x ) for x in range( -1, 5 ) ], IECore.GeometricData.Interpretation.Point ) + ) + + def testChangeBasis( self ) : + + objectToScene = GafferScene.ObjectToScene() + objectToScene["object"].setValue( + IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), IECore.CubicBasisf.catmullRom(), + p = IECore.V3fVectorData( [ imath.V3f( x ) for x in range( 0, 4 ) ] ) + ) + ) + + pathFilter = GafferScene.PathFilter() + pathFilter["paths"].setValue( IECore.StringVectorData( [ "/object" ] ) ) + + interpolation = GafferScene.CurvesInterpolation() + interpolation["in"].setInput( objectToScene["out"] ) + interpolation["filter"].setInput( pathFilter["out"] ) + + self.assertEqual( interpolation["out"].object( "/object" ).basis(), IECore.CubicBasisf.catmullRom() ) + + interpolation["basis"]["enabled"].setValue( True ) + interpolation["basis"]["value"].setValue( IECore.StandardCubicBasis.BSpline ) + + self.assertEqual( interpolation["out"].object( "/object" ).basis(), IECore.CubicBasisf.bSpline() ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneTest/IECoreScenePreviewTest/PrimitiveAlgoTest.py b/python/GafferSceneTest/IECoreScenePreviewTest/PrimitiveAlgoTest.py index a04543be925..8824cadb720 100644 --- a/python/GafferSceneTest/IECoreScenePreviewTest/PrimitiveAlgoTest.py +++ b/python/GafferSceneTest/IECoreScenePreviewTest/PrimitiveAlgoTest.py @@ -274,7 +274,7 @@ def testMergePrimitivesSimpleCurves( self ) : self.assertEqual( merged["P"], IECoreScene.PrimitiveVariable( Interpolation.Vertex, IECore.V3fVectorData( curveVerts1 + curveVerts2, IECore.GeometricData.Interpretation.Point ) ) ) curves1.setTopology( curves1.verticesPerCurve(), curves1.basis(), True ) - with self.assertRaisesRegex( RuntimeError, "Cannot merge periodic and non-periodic curves" ) : + with self.assertRaisesRegex( RuntimeError, "Cannot merge curves with mismatched wrap" ) : PrimitiveAlgo.mergePrimitives( [( curves1, imath.M44f() ), ( curves2, imath.M44f() ) ] ) curves2.setTopology( curves2.verticesPerCurve(), curves2.basis(), True ) diff --git a/python/GafferSceneTest/__init__.py b/python/GafferSceneTest/__init__.py index 883e6776202..ce567822b6c 100644 --- a/python/GafferSceneTest/__init__.py +++ b/python/GafferSceneTest/__init__.py @@ -196,6 +196,7 @@ from .CameraQueryTest import CameraQueryTest from .GlobalsSanitiserTest import GlobalsSanitiserTest from .ReflectionConstraintTest import ReflectionConstraintTest +from .CurvesInterpolationTest import CurvesInterpolationTest from .IECoreScenePreviewTest import * from .IECoreGLPreviewTest import * diff --git a/python/GafferSceneUI/CurvesInterpolationUI.py b/python/GafferSceneUI/CurvesInterpolationUI.py new file mode 100644 index 00000000000..c263246f27d --- /dev/null +++ b/python/GafferSceneUI/CurvesInterpolationUI.py @@ -0,0 +1,127 @@ +########################################################################## +# +# Copyright (c) 2026, Cinesite VFX Ltd. 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 John Haddon 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 Gaffer +import GafferScene + +import IECore +import IECoreScene + +########################################################################## +# Metadata +########################################################################## + +Gaffer.Metadata.registerNode( + + GafferScene.CurvesInterpolation, + + "description", + """ + Defines how CurvesPrimitive geometry is interpolated, by + modifying `basis` and `wrap`. + """, + + plugs = { + + "basis" : { + + "description" : + """ + The method used to interpolate the vertices of the curves. + + - Linear : Straight lines between vertices. + - BSpline : Smooth interpolation approximating - but not passing + directly through - each vertex. Requires `Pinned` wrap mode to + interpolate all the way to the end vertices. + - CatmullRom : Smooth interpolation passing directly through each + vertex. Requires `Pinned` wrap mode to interpolate all the way + to the end vertices. + """, + + }, + + "basis.value" : { + + "plugValueWidget:type" : "GafferUI.PresetsPlugValueWidget", + # We only expose the basis types which have a step of 1. + # We can safely convert between any of these without them + # invalidating the curve topology. + "preset:Linear" : IECore.StandardCubicBasis.Linear, + "preset:BSpline" : IECore.StandardCubicBasis.BSpline, + "preset:CatmullRom" : IECore.StandardCubicBasis.CatmullRom, + + }, + + "wrap" : { + + "description" : + """ + The treatment of the first and last curve segments. + + - Pinned : Automatically uses "phantom points" to ensure that + CatmullRom and BSpline curves interpolate all the way to their + endpoints. Equivalent to `NonPeriodic` for all other curve types. + - Periodic : Wraps the curve around to form a closed loop. + - NonPeriodic : Neither wraps the curve to produce a loop or introduces + phantom endpoints. Generally inferior to the other options. + """, + + }, + + "wrap.value" : { + + "plugValueWidget:type" : "GafferUI.PresetsPlugValueWidget", + "preset:Pinned" : IECoreScene.CurvesPrimitive.Wrap.Pinned, + "preset:Periodic" : IECoreScene.CurvesPrimitive.Wrap.Periodic, + "preset:NonPeriodic" : IECoreScene.CurvesPrimitive.Wrap.NonPeriodic, + + }, + + "expandPinned" : { + + "description" : + """ + When converting Pinned curves to NonPeriodic, adds the "phantom" vertices + so that the curves continue to interpolate to their original endpoints. + """, + + "layout:activator" : lambda plug : plug.parent()["wrap"]["enabled"].getValue() and plug.parent()["wrap"]["value"].getValue() == IECoreScene.CurvesPrimitive.Wrap.NonPeriodic, + + }, + + } + +) diff --git a/python/GafferSceneUI/CurvesTweaksUI.py b/python/GafferSceneUI/CurvesTweaksUI.py new file mode 100644 index 00000000000..4c55531771f --- /dev/null +++ b/python/GafferSceneUI/CurvesTweaksUI.py @@ -0,0 +1,99 @@ +########################################################################## +# +# Copyright (c) 2026, Cinesite VFX Ltd. 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 John Haddon 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 Gaffer +import GafferScene + +import IECore +import IECoreScene + +########################################################################## +# Metadata +########################################################################## + +Gaffer.Metadata.registerNode( + + GafferScene.CurvesTweaks, + + # "description", + # """ + # Changes between polygon and subdivision representations + # for mesh objects, and optionally recalculates vertex + # normals for polygon meshes. + + # Note that currently the Gaffer viewport does not display + # subdivision meshes with smoothing, so the results of using + # this node will not be seen until a render is performed. + # """, + + plugs = { + + "wrap.value" : { + + # "description" : + # """ + # The interpolation type to apply to the mesh. + # """, + + # "preset:Unchanged" : "", + # "preset:Polygon" : "linear", + # "preset:Subdivision Surface" : "catmullClark", + + "plugValueWidget:type" : "GafferUI.PresetsPlugValueWidget", + "preset:NonPeriodic" : IECoreScene.CurvesPrimitive.Wrap.NonPeriodic, + "preset:Periodic" : IECoreScene.CurvesPrimitive.Wrap.Periodic, + "preset:Pinned" : IECoreScene.CurvesPrimitive.Wrap.Pinned, + + }, + + "expandPinned" : { + + "layout:activator" : lambda plug : plug.parent()["wrap"]["enabled"].getValue() and plug.parent()["wrap"]["value"].getValue() == IECoreScene.CurvesPrimitive.Wrap.NonPeriodic, + + }, + + "basis.value" : { + + "plugValueWidget:type" : "GafferUI.PresetsPlugValueWidget", + "preset:Linear" : IECore.StandardCubicBasis.Linear, + "preset:BSpline" : IECore.StandardCubicBasis.BSpline, + "preset:CatmullRom" : IECore.StandardCubicBasis.CatmullRom, + + }, + + } + +) diff --git a/python/GafferSceneUI/__init__.py b/python/GafferSceneUI/__init__.py index 03a616370e3..d01d56f6a74 100644 --- a/python/GafferSceneUI/__init__.py +++ b/python/GafferSceneUI/__init__.py @@ -213,6 +213,7 @@ from . import ImageSelectionToolUI from . import CameraQueryUI from . import ReflectionConstraintUI +from . import CurvesInterpolationUI # then all the PathPreviewWidgets. note that the order # of import controls the order of display. diff --git a/python/GafferTest/TestCase.py b/python/GafferTest/TestCase.py index c4b29e83598..519919e065d 100644 --- a/python/GafferTest/TestCase.py +++ b/python/GafferTest/TestCase.py @@ -308,6 +308,7 @@ def __undocumentedPlugs( self, node, additionalTerminalPlugTypes = () ) : Gaffer.Box2fPlug, Gaffer.Box3fPlug, Gaffer.TransformPlug, Gaffer.Transform2DPlug, Gaffer.CompoundDataPlug.MemberPlug, + Gaffer.OptionalValuePlug, additionalTerminalPlugTypes ) diff --git a/python/IECoreArnoldTest/RendererTest.py b/python/IECoreArnoldTest/RendererTest.py index 466ed36c215..3e9f3890f50 100644 --- a/python/IECoreArnoldTest/RendererTest.py +++ b/python/IECoreArnoldTest/RendererTest.py @@ -2602,6 +2602,49 @@ def testTraceSets( self ) : for i in range( 0, arnold.AiArrayGetNumElements( a.contents ) ) : self.assertEqual( arnold.AiArrayGetStr( a, i ), sets[i] ) + def testCurvesWrap( self ) : + + Wrap = IECoreScene.CurvesPrimitive.Wrap + for basis, wrap, expectedWrap, expectedWarning in [ + ( IECore.CubicBasisf.catmullRom(), Wrap.Pinned, "pinned", None ), + ( IECore.CubicBasisf.bSpline(), Wrap.Pinned, "pinned", None ), + ( IECore.CubicBasisf.linear(), Wrap.Pinned, "nonperiodic", None ), + ( IECore.CubicBasisf.linear(), Wrap.Periodic, "nonperiodic", "Arnold does not implement periodic wrap. Using nonperiodic instead." ), + ( IECore.CubicBasisf.linear(), Wrap.NonPeriodic, "nonperiodic", None ), + ] : + + with self.subTest( basis = basis, wrap = wrap ) : + + renderer = GafferScene.Private.IECoreScenePreview.Renderer.create( + "Arnold", + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.SceneDescription, + str( self.temporaryDirectory() / "test.ass" ) + ) + + curves = IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), basis, wrap, IECore.V3fVectorData( [ imath.V3f( x ) for x in range( 0, 4 ) ] ) + ) + + with IECore.CapturingMessageHandler() as messageHandler : + renderer.object( "curves", curves, renderer.attributes( IECore.CompoundObject() ) ) + + renderer.render() + del renderer + + with IECoreArnold.UniverseBlock( writable = True ) as universe : + + arnold.AiSceneLoad( universe, str( self.temporaryDirectory() / "test.ass" ), None ) + + curves = arnold.AiNodeGetPtr( arnold.AiNodeLookUpByName( universe, "curves" ), "node" ) + self.assertEqual( arnold.AiNodeGetStr( curves, "wrap_mode" ), expectedWrap ) + + if expectedWarning is not None : + self.assertEqual( len( messageHandler.messages ), 1 ) + self.assertEqual( messageHandler.messages[0].level, IECore.Msg.Level.Warning ) + self.assertEqual( messageHandler.messages[0].message, expectedWarning ) + else : + self.assertEqual( len( messageHandler.messages ), 0 ) + def testCurvesAttributes( self ) : r = GafferScene.Private.IECoreScenePreview.Renderer.create( diff --git a/python/IECoreRenderManTest/RendererTest.py b/python/IECoreRenderManTest/RendererTest.py index cf429ae8a5c..7b08ed6099b 100644 --- a/python/IECoreRenderManTest/RendererTest.py +++ b/python/IECoreRenderManTest/RendererTest.py @@ -39,6 +39,7 @@ import time import unittest import random +import itertools import imath @@ -1301,6 +1302,55 @@ def testSubdivisionScheme( self ) : self.__assertNotInPrimitiveVariables( proto, "Ri:scheme" ) self.assertEqual( proto["type"], "Ri:PolygonMesh" ) + def testCurvesWrap( self ) : + + Wrap = IECoreScene.CurvesPrimitive.Wrap + for basis, wrap in [ + ( IECore.CubicBasisf.catmullRom(), Wrap.Pinned ), + ( IECore.CubicBasisf.bSpline(), Wrap.Pinned ), + ( IECore.CubicBasisf.linear(), Wrap.Pinned ), + ( IECore.CubicBasisf.bSpline(), Wrap.Periodic ), + ( IECore.CubicBasisf.bSpline(), Wrap.NonPeriodic ), + ] : + + with self.subTest( basis = basis, wrap = wrap ) : + + with IECoreRenderManTest.RileyCapture() as capture : + + renderer = GafferScene.Private.IECoreScenePreview.Renderer.create( + "RenderMan", + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch, + ) + + curves = IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), basis, wrap, IECore.V3fVectorData( [ imath.V3f( x ) for x in range( 0, 4 ) ] ) + ) + renderer.object( + "curves", curves, renderer.attributes( IECore.CompoundObject() ) + ) + + del renderer + + prototype = next( + x for x in capture.json if x["method"] == "CreateGeometryPrototype" + ) + + if IECoreScene.CurvesAlgo.isPinned( curves ) : + + self.__assertPrimitiveVariableEqual( + prototype, "P", + list( itertools.chain( *[ iter( [ x, x, x ] ) for x in range( -1, 5 ) ] ) ) + ) + self.__assertPrimitiveVariableEqual( prototype, "Ri:wrap", [ "nonperiodic" ] ) + + else : + + self.__assertPrimitiveVariableEqual( + prototype, "P", + list( itertools.chain( *[ iter( [ x, x, x ] ) for x in range( 0, 4 ) ] ) ) + ) + self.__assertPrimitiveVariableEqual( prototype, "Ri:wrap", [ "periodic" if wrap == Wrap.Periodic else "nonperiodic" ] ) + def testAutomaticInstancingAttribute( self ) : for instancingEnabled in ( True, False ) : diff --git a/src/GafferArnoldUI/GoboVisualiser.cpp b/src/GafferArnoldUI/GoboVisualiser.cpp index 71ad1dbc77c..483921fca42 100644 --- a/src/GafferArnoldUI/GoboVisualiser.cpp +++ b/src/GafferArnoldUI/GoboVisualiser.cpp @@ -86,7 +86,7 @@ IECoreGL::RenderablePtr quadWireframe( const V2f &size ) p.push_back( V3f( size.x/2, size.y/2, 0 ) ); p.push_back( V3f( -size.x/2, size.y/2, 0 ) ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 1.0f, 0.835f, 0.07f ) ) ) ); diff --git a/src/GafferArnoldUI/LightBlockerVisualiser.cpp b/src/GafferArnoldUI/LightBlockerVisualiser.cpp index 6454492786d..2c35d066b31 100644 --- a/src/GafferArnoldUI/LightBlockerVisualiser.cpp +++ b/src/GafferArnoldUI/LightBlockerVisualiser.cpp @@ -275,7 +275,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::boxShape( const IECore::Com addCube( /* origin */ { 0, 0, 0 }, /* size */ 1.0, vertsPerCurve, p ); - IECoreGL::CurvesPrimitivePtr cube = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr cube = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); cube->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); cube->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -283,7 +283,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::boxShape( const IECore::Com // Add falloff visualisation - IECoreGL::CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); falloff->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); falloff->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 0.0f, 0.0f, 0.0f ) ) ) ); @@ -316,7 +316,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::sphereShape( const IECore:: addCircle( { 0, 0, 0 }, 0.5, vertsPerCurve, p ); - IECoreGL::CurvesPrimitivePtr circleXY = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr circleXY = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); circleXY->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); circleXY->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -325,7 +325,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::sphereShape( const IECore:: group->addChild( xy ); - IECoreGL::CurvesPrimitivePtr circleYZ = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr circleYZ = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); circleYZ->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); circleYZ->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -338,7 +338,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::sphereShape( const IECore:: yz->setTransform( yzRotation ); - IECoreGL::CurvesPrimitivePtr circleXZ = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr circleXZ = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); circleXZ->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); circleXZ->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -383,7 +383,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::cylinderShape( const IECore addLine( { 0, radius, -radius }, { 0, radius, radius }, vertsPerCurve, p ); addLine( { 0, -radius, -radius }, { 0, -radius, radius }, vertsPerCurve, p ); - CurvesPrimitivePtr cylinder = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr cylinder = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); cylinder->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); cylinder->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -399,7 +399,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::cylinderShape( const IECore // Add falloff visualisation - CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); falloff->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); falloff->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 0.0f, 0.0f, 0.0f ) ) ) ); @@ -437,7 +437,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::planeShape( const IECore::C addQuad( /* origin */ { 0, 0, 0 }, /* size */ 1.0, vertsPerCurve, p ); - IECoreGL::CurvesPrimitivePtr quad = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr quad = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); quad->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); quad->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255 / 255.0f, 171 / 255.0f, 15 / 255.0f ) ) ) ); @@ -445,7 +445,7 @@ IECoreGL::ConstRenderablePtr LightBlockerVisualiser::planeShape( const IECore::C // Add falloff visualisation - CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr falloff = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); falloff->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); falloff->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 0.0f, 0.0f, 0.0f ) ) ) ); diff --git a/src/GafferRenderManUI/LightFilterVisualiserAlgo.cpp b/src/GafferRenderManUI/LightFilterVisualiserAlgo.cpp index 9aa668a7fba..595703f85ad 100644 --- a/src/GafferRenderManUI/LightFilterVisualiserAlgo.cpp +++ b/src/GafferRenderManUI/LightFilterVisualiserAlgo.cpp @@ -129,7 +129,7 @@ IECoreGL::GroupPtr GafferRenderManUI::lightFilterRectangles( const V2f &innerSiz addRect( innerSize, innerScale, innerOffset, radius, 0.f, V4f( 0.f ), innerVertsPerCurve, innerP ); - IECoreGL::CurvesPrimitivePtr rect = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic */ true, innerVertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr rect = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, innerVertsPerCurveData ); rect->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, innerPData ) ); rect->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 255.f / 255.f, 171.f / 255.f, 15.f / 255.f ) ) ) ); @@ -149,7 +149,7 @@ IECoreGL::GroupPtr GafferRenderManUI::lightFilterRectangles( const V2f &innerSiz addRect( innerSize, innerScale, innerOffset, radius, edge, falloffScale, edgeVertsPerCurve, edgeP ); - IECoreGL::CurvesPrimitivePtr edgeRect = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic */ true, edgeVertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr edgeRect = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, edgeVertsPerCurveData ); edgeRect->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, edgePData ) ); edgeRect->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( Color3f( 0.f ) ) ) ); diff --git a/src/GafferRenderManUI/RenderManLightVisualiser.cpp b/src/GafferRenderManUI/RenderManLightVisualiser.cpp index 924e2cffb0d..765a26825c4 100644 --- a/src/GafferRenderManUI/RenderManLightVisualiser.cpp +++ b/src/GafferRenderManUI/RenderManLightVisualiser.cpp @@ -197,7 +197,7 @@ IECoreGL::ConstRenderablePtr triangle( const V3f &p0, const V3f &p1, const V3f & { IntVectorDataPtr vertsPerCurveData = new IntVectorData( { 3 } ); result = new IECoreGL::CurvesPrimitive( - CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData + CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); } else @@ -242,7 +242,7 @@ IECoreGL::ConstRenderablePtr sunWireframe( const float radius ) IntVectorDataPtr vertsPerCurveData = new IntVectorData( std::vector( numSpikes, pointsPerSpike + 1 ) ); IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( - CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData + CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); V3fVectorDataPtr pData = new V3fVectorData( p ); @@ -326,7 +326,7 @@ IECoreGL::ConstRenderablePtr spotLightRound( const float angle, const float heig } ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, p ) ); const Color3fDataPtr color = new Color3fData( lineWidthScale < 1.0f ? Color3f( 0.627f, 0.580f, 0.352f ) : lightWireframeColor( muted ) ); @@ -365,7 +365,7 @@ IECoreGL::ConstRenderablePtr spotLightSquare( const float angle, const float hei } ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, p ) ); const Color3fDataPtr color = new Color3fData( lineWidthScale < 1.0f ? Color3f( 0.627f, 0.580f, 0.352f ) : lightWireframeColor( muted ) ); @@ -797,7 +797,7 @@ Visualisations RenderManLightVisualiser::visualise( const InternedString &attrib if( ornamentWireframePoints->readable().size() > 0 ) { IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( - CubicBasisf::linear(), false, ornamentWireframeVertsPerCurve + CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, ornamentWireframeVertsPerCurve ); curves->addPrimitiveVariable( "P", PrimitiveVariable( PrimitiveVariable::Vertex, ornamentWireframePoints ) ); curves->addPrimitiveVariable( @@ -811,4 +811,4 @@ Visualisations RenderManLightVisualiser::visualise( const InternedString &attrib } return result; -} \ No newline at end of file +} diff --git a/src/GafferScene/CurvesInterpolation.cpp b/src/GafferScene/CurvesInterpolation.cpp new file mode 100644 index 00000000000..8594b19cee8 --- /dev/null +++ b/src/GafferScene/CurvesInterpolation.cpp @@ -0,0 +1,183 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2026, Cinesite VFX Ltd. 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 John Haddon 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 "GafferScene/CurvesInterpolation.h" + +#include "IECoreScene/CurvesAlgo.h" +#include "IECoreScene/CurvesPrimitive.h" + +using namespace IECore; +using namespace IECoreScene; +using namespace Gaffer; +using namespace GafferScene; + +GAFFER_NODE_DEFINE_TYPE( CurvesInterpolation ); + +size_t CurvesInterpolation::g_firstPlugIndex = 0; + +CurvesInterpolation::CurvesInterpolation( const std::string &name ) + : ObjectProcessor( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + using Basis = IECore::StandardCubicBasis; + addChild( new OptionalValuePlug( "basis", new IntPlug( "value", Plug::In, (int)Basis::Linear, (int)Basis::Linear, (int)Basis::CatmullRom ) ) ); + using Wrap = IECoreScene::CurvesPrimitive::Wrap; + addChild( new OptionalValuePlug( "wrap", new IntPlug( "value", Plug::In, (int)Wrap::NonPeriodic, (int)Wrap::NonPeriodic, (int)Wrap::Pinned ) ) ); + addChild( new BoolPlug( "expandPinned" ) ); +} + +CurvesInterpolation::~CurvesInterpolation() +{ +} + +Gaffer::OptionalValuePlug *CurvesInterpolation::basisPlug() +{ + return getChild( g_firstPlugIndex ); +} + +const Gaffer::OptionalValuePlug *CurvesInterpolation::basisPlug() const +{ + return getChild( g_firstPlugIndex ); +} + +Gaffer::OptionalValuePlug *CurvesInterpolation::wrapPlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const Gaffer::OptionalValuePlug *CurvesInterpolation::wrapPlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + +Gaffer::BoolPlug *CurvesInterpolation::expandPinnedPlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const Gaffer::BoolPlug *CurvesInterpolation::expandPinnedPlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + +bool CurvesInterpolation::affectsProcessedObject( const Gaffer::Plug *input ) const +{ + return + ObjectProcessor::affectsProcessedObject( input ) || + input->parent() == basisPlug() || + input->parent() == wrapPlug() || + input == expandPinnedPlug() + ; +} + +void CurvesInterpolation::hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + const bool basisEnabled = basisPlug()->enabledPlug()->getValue(); + const bool wrapEnabled = wrapPlug()->enabledPlug()->getValue(); + + if( !wrapEnabled && !basisEnabled ) + { + h = inPlug()->objectPlug()->hash(); + return; + } + + ObjectProcessor::hashProcessedObject( path, context, h ); + + h.append( basisEnabled ); + if( basisEnabled ) + { + basisPlug()->valuePlug()->hash( h ); + } + + h.append( wrapEnabled ); + if( wrapEnabled ) + { + wrapPlug()->valuePlug()->hash( h ); + expandPinnedPlug()->hash( h ); + } +} + +IECore::ConstObjectPtr CurvesInterpolation::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const +{ + const CurvesPrimitive *inputCurves = runTimeCast( inputObject ); + if( !inputCurves ) + { + return inputObject; + } + + const bool basisEnabled = basisPlug()->enabledPlug()->getValue(); + const bool wrapEnabled = wrapPlug()->enabledPlug()->getValue(); + if( !wrapEnabled && !basisEnabled ) + { + return inputObject; + } + + CubicBasisf basis = inputCurves->basis(); + if( basisEnabled ) + { + basis = CubicBasisf( + (StandardCubicBasis)basisPlug()->valuePlug()->getValue() + ); + } + + CurvesPrimitive::Wrap wrap = inputCurves->wrap(); + if( wrapEnabled ) + { + wrap = (CurvesPrimitive::Wrap)wrapPlug()->valuePlug()->getValue(); + } + + if( wrap == inputCurves->wrap() && basis == inputCurves->basis() ) + { + return inputObject; + } + + IECoreScene::CurvesPrimitivePtr result = inputCurves->copy(); + + if( + CurvesAlgo::isPinned( inputCurves ) && + wrap == CurvesPrimitive::Wrap::NonPeriodic && + expandPinnedPlug()->getValue() + ) + { + CurvesAlgo::convertPinnedToNonPeriodic( result.get(), context->canceller() ); + } + else + { + result->setTopology( result->verticesPerCurve(), basis, wrap ); + } + + return result; +} diff --git a/src/GafferScene/IECoreGLPreview/ProceduralVisualiser.cpp b/src/GafferScene/IECoreGLPreview/ProceduralVisualiser.cpp index d4bcf1096fe..88da450afaa 100644 --- a/src/GafferScene/IECoreGLPreview/ProceduralVisualiser.cpp +++ b/src/GafferScene/IECoreGLPreview/ProceduralVisualiser.cpp @@ -119,7 +119,7 @@ class BoundVisualiser : public ObjectVisualiser p.push_back( V3f( b.min.x, b.min.y, b.max.z ) ); p.push_back( V3f( b.min.x, b.max.y, b.max.z ) ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); group->addChild( curves ); diff --git a/src/GafferScene/IECoreScenePreview/PrimitiveAlgo.cpp b/src/GafferScene/IECoreScenePreview/PrimitiveAlgo.cpp index 8c1c45307f0..2459cc51ade 100644 --- a/src/GafferScene/IECoreScenePreview/PrimitiveAlgo.cpp +++ b/src/GafferScene/IECoreScenePreview/PrimitiveAlgo.cpp @@ -794,7 +794,7 @@ class MergePrimitivesCurvesResult // This must be called after all calls to copyFromSource void finalize() { - result->setTopology( m_resultVerticesPerCurveData, result->basis(), result->periodic() ); + result->setTopology( m_resultVerticesPerCurveData.get(), result->basis(), result->wrap() ); } // Return an interpolation adequate to store data of either input interpolation @@ -844,16 +844,16 @@ class MergePrimitivesCurvesResult { const CurvesPrimitive *firstCurves = static_cast< const CurvesPrimitive * >( primitives[0].first ); CubicBasisf basis = firstCurves->basis(); - bool periodic = firstCurves->periodic(); + const CurvesPrimitive::Wrap wrap = firstCurves->wrap(); static const CubicBasisf invalidBasis( Imath::M44f( 0.0f ), 0 ); for( const auto & [prim, matrix] : primitives ) { const CurvesPrimitive *curves = static_cast< const CurvesPrimitive * >( prim ); - if( curves->periodic() != periodic ) + if( curves->wrap() != wrap ) { - throw IECore::Exception( "Cannot merge periodic and non-periodic curves" ); + throw IECore::Exception( "Cannot merge curves with mismatched wrap" ); } if( @@ -873,7 +873,7 @@ class MergePrimitivesCurvesResult basis = CubicBasisf::linear(); } - result->setTopology( result->verticesPerCurve(), basis, periodic ); + result->setTopology( result->verticesPerCurve(), basis, wrap ); } IntVectorDataPtr m_resultVerticesPerCurveData; diff --git a/src/GafferScene/PrimitiveSampler.cpp b/src/GafferScene/PrimitiveSampler.cpp index 461e3e8db40..c9c3f3273d1 100644 --- a/src/GafferScene/PrimitiveSampler.cpp +++ b/src/GafferScene/PrimitiveSampler.cpp @@ -326,7 +326,7 @@ IECore::ConstObjectPtr PrimitiveSampler::computeProcessedObject( const ScenePath const M44f primitiveVariableTransform = sourceTransform * transform.inverse(); vector outputVariables; - for( auto &p : preprocessedSourcePrimitive->variables ) + for( auto &p : evaluator->primitive()->variables ) { if( !StringAlgo::matchMultiple( p.first, primitiveVariables ) ) { diff --git a/src/GafferSceneModule/ObjectProcessorBinding.cpp b/src/GafferSceneModule/ObjectProcessorBinding.cpp index a417e0c0e4d..315b15b6f65 100644 --- a/src/GafferSceneModule/ObjectProcessorBinding.cpp +++ b/src/GafferSceneModule/ObjectProcessorBinding.cpp @@ -39,6 +39,7 @@ #include "ObjectProcessorBinding.h" #include "GafferScene/CopyPrimitiveVariables.h" +#include "GafferScene/CurvesInterpolation.h" #include "GafferScene/Deformer.h" #include "GafferScene/DeleteCurves.h" #include "GafferScene/DeleteFaces.h" @@ -110,6 +111,7 @@ void GafferSceneModule::bindObjectProcessor() GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); + GafferBindings::DependencyNodeClass(); { scope s = GafferBindings::DependencyNodeClass(); diff --git a/src/GafferSceneUI/CameraVisualiser.cpp b/src/GafferSceneUI/CameraVisualiser.cpp index 91d06414942..4bc35688dea 100644 --- a/src/GafferSceneUI/CameraVisualiser.cpp +++ b/src/GafferSceneUI/CameraVisualiser.cpp @@ -121,7 +121,7 @@ class CameraVisualiser : public ObjectVisualiser p.push_back( V3f( near.min.x, near.max.y, -clippingPlanes[0] ) ); p.push_back( V3f( far.min.x, far.max.y, -clippingPlanes[1] ) ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); return curves; @@ -250,7 +250,7 @@ class CameraVisualiser : public ObjectVisualiser p.push_back( V3f( handleXs[0], handleYs[2], handleZs[3] ) ); p.push_back( V3f( handleXs[1], handleYs[2], handleZs[3] ) ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); return curves; diff --git a/src/GafferSceneUI/ClippingPlaneVisualiser.cpp b/src/GafferSceneUI/ClippingPlaneVisualiser.cpp index fdb65a21c1e..dbce1c5500c 100644 --- a/src/GafferSceneUI/ClippingPlaneVisualiser.cpp +++ b/src/GafferSceneUI/ClippingPlaneVisualiser.cpp @@ -91,7 +91,7 @@ class ClippingPlaneVisualiser : public ObjectVisualiser p.push_back( V3f( 0, 0, 0.5 ) ); vertsPerCurve.push_back( 2 ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); group->addChild( curves ); } diff --git a/src/GafferSceneUI/CoordinateSystemVisualiser.cpp b/src/GafferSceneUI/CoordinateSystemVisualiser.cpp index f5465ff1d22..c639c6b0cdb 100644 --- a/src/GafferSceneUI/CoordinateSystemVisualiser.cpp +++ b/src/GafferSceneUI/CoordinateSystemVisualiser.cpp @@ -79,7 +79,7 @@ class CoordinateSystemVisualiser : public ObjectVisualiser IECore::IntVectorDataPtr vertsPerCurve = new IECore::IntVectorData; vertsPerCurve->writable().resize( 3, 2 ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); group->addChild( curves ); } diff --git a/src/GafferSceneUI/LightVisualiserAlgo.cpp b/src/GafferSceneUI/LightVisualiserAlgo.cpp index 8be6b816d84..f2c8f63135c 100644 --- a/src/GafferSceneUI/LightVisualiserAlgo.cpp +++ b/src/GafferSceneUI/LightVisualiserAlgo.cpp @@ -373,7 +373,7 @@ IECoreGL::ConstRenderablePtr ray( bool muted ) V3fVectorDataPtr p = new V3fVectorData; addRay( V3f( 0 ), V3f( 0, 0, -1 ), vertsPerCurve->writable(), p->writable() ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, p ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); @@ -399,7 +399,7 @@ IECoreGL::ConstRenderablePtr pointRays( float radius, bool muted ) addRay( dir * ( 0.2f + radius ), dir * ( 0.6f + radius ), vertsPerCurve->writable(), p->writable(), 0.1f ); } - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, p ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); @@ -445,7 +445,7 @@ IECoreGL::ConstRenderablePtr spotlightCone( float innerAngle, float outerAngle, addCone( outerAngle, lensRadius, vertsPerCurve->writable(), p->writable(), length, true ); } - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, p ) ); const Color3fDataPtr color = new Color3fData( lineWidthScale < 1.0f ? Color3f( 0.627f, 0.580f, 0.352f ) : ( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ); @@ -473,7 +473,7 @@ IECoreGL::ConstRenderablePtr pointShape( float radius, bool muted ) p *= t; } - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), /* periodic = */ false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); @@ -529,7 +529,7 @@ IECoreGL::ConstRenderablePtr roundedQuadWireframe( const V2f &size, const V2f &r vertsPerCurve.push_back( p.size() ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), /* periodic = */ true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", PrimitiveVariable( PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", PrimitiveVariable( PrimitiveVariable::Constant, new Color3fData( lightWireframeColor( muted ) ) ) ); @@ -772,7 +772,7 @@ IECoreGL::ConstRenderablePtr quadPortal( const V2f &size, float hatchingScale, b } } - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), true, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::Periodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : Color3f( 0.07f ) ) ) ); return curves; @@ -800,7 +800,7 @@ IECoreGL::ConstRenderablePtr sphereWireframe( float radius, const Vec3 &ax addCircle( Axis::Z, center, radius, vertsPerCurve->writable(), p->writable() ); } - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", PrimitiveVariable( PrimitiveVariable::Vertex, p ) ); curves->addPrimitiveVariable( "Cs", PrimitiveVariable( PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); @@ -854,7 +854,7 @@ IECoreGL::ConstRenderablePtr diskWireframe( float radius, float lineWidthScale, addCircle( Axis::Z, V3f( 0 ), radius, vertsPerCurveData->writable(), pData->writable() ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), /* periodic = */ false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", PrimitiveVariable( PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", PrimitiveVariable( PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); @@ -943,7 +943,7 @@ IECoreGL::ConstRenderablePtr cylinderWireframe( float radius, float length, floa p.push_back( V3f( 0, -radius, halfLength ) ); vertsPerCurve.push_back( 2 ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), false, vertsPerCurveData ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurveData ); curves->addPrimitiveVariable( "P", PrimitiveVariable( PrimitiveVariable::Vertex, pData ) ); curves->addPrimitiveVariable( "Cs", PrimitiveVariable( PrimitiveVariable::Constant, new Color3fData( muted ? g_mutedLightWireframeColor : g_lightWireframeColor ) ) ); diff --git a/src/GafferSceneUI/StandardLightVisualiser.cpp b/src/GafferSceneUI/StandardLightVisualiser.cpp index d19a833edcd..cff2d0ee944 100644 --- a/src/GafferSceneUI/StandardLightVisualiser.cpp +++ b/src/GafferSceneUI/StandardLightVisualiser.cpp @@ -359,7 +359,7 @@ Visualisations StandardLightVisualiser::visualise( const IECore::InternedString if( ornamentWireframePoints->readable().size() > 0 ) { - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, ornamentWireframeVertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, ornamentWireframeVertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, ornamentWireframePoints ) ); curves->addPrimitiveVariable( "Cs", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, new Color3fData( lightWireframeColor( muted ) ) ) ); result.push_back( Visualisation::createOrnament( curves, /* affectsFramingBound = */ false ) ); diff --git a/src/GafferSceneUIModule/SceneInspectorBinding.cpp b/src/GafferSceneUIModule/SceneInspectorBinding.cpp index 892b83fdc16..6f34d694caf 100644 --- a/src/GafferSceneUIModule/SceneInspectorBinding.cpp +++ b/src/GafferSceneUIModule/SceneInspectorBinding.cpp @@ -956,12 +956,13 @@ InspectorTree::Inspections objectTypeInspectionProvider( ScenePlug *scene, const const InspectorTree::Registration g_objectTypeInspectionRegistration( { "Location", "Object" }, objectTypeInspectionProvider ); -const vector> g_primitiveVariableInterpolations = { - { PrimitiveVariable::Constant, "Constant" }, - { PrimitiveVariable::Uniform, "Uniform" }, - { PrimitiveVariable::Vertex, "Vertex" }, - { PrimitiveVariable::Varying, "Varying" }, - { PrimitiveVariable::FaceVarying, "FaceVarying" } +const boost::container::flat_map g_primitiveVariableInterpolations = { + { PrimitiveVariable::Invalid, new StringData( "Invalid" ) }, + { PrimitiveVariable::Constant, new StringData( "Constant" ) }, + { PrimitiveVariable::Uniform, new StringData( "Uniform" ) }, + { PrimitiveVariable::Vertex, new StringData( "Vertex" ) }, + { PrimitiveVariable::Varying, new StringData( "Varying" ) }, + { PrimitiveVariable::FaceVarying, new StringData( "FaceVarying" ) } }; InspectorTree::Inspections primitiveTopologyInspectionProvider( ScenePlug *scene, const Gaffer::PlugPtr &editScope ) @@ -973,8 +974,12 @@ InspectorTree::Inspections primitiveTopologyInspectionProvider( ScenePlug *scene { for( const auto &[interpolation, interpolationName] : g_primitiveVariableInterpolations ) { + if( interpolation == PrimitiveVariable::Invalid ) + { + continue; + } result.push_back( { - { interpolationName }, + { interpolationName->readable() }, new GafferSceneUI::Private::BasicInspector( scene->objectPlug(), editScope, [ interpolation = interpolation ] ( const ObjectPlug *objectPlug ) -> ConstDataPtr { @@ -1059,6 +1064,21 @@ InspectorTree::Inspections meshTopologyInspectionProvider( ScenePlug *scene, con const InspectorTree::Registration g_meshTopologyInspectionRegistration( { "Location", "Object", "Mesh Topology" }, meshTopologyInspectionProvider ); +const boost::container::flat_map g_curveBases = { + { StandardCubicBasis::Unknown, new StringData( "Unknown" ) }, + { StandardCubicBasis::Linear, new StringData( "Linear" ) }, + { StandardCubicBasis::Bezier, new StringData( "Bezier" ) }, + { StandardCubicBasis::BSpline, new StringData( "BSpline" ) }, + { StandardCubicBasis::CatmullRom, new StringData( "CatmullRom" ) }, + { StandardCubicBasis::Constant, new StringData( "Constant" ) }, +}; + +const boost::container::flat_map g_curveWraps = { + { CurvesPrimitive::Wrap::NonPeriodic, new StringData( "NonPeriodic" ) }, + { CurvesPrimitive::Wrap::Periodic, new StringData( "Periodic" ) }, + { CurvesPrimitive::Wrap::Pinned, new StringData( "Pinned" ) } +}; + InspectorTree::Inspections curvesTopologyInspectionProvider( ScenePlug *scene, const Gaffer::PlugPtr &editScope ) { InspectorTree::Inspections result; @@ -1106,13 +1126,13 @@ InspectorTree::Inspections curvesTopologyInspectionProvider( ScenePlug *scene, c ) } ); result.push_back( { - { "Periodic" }, + { "Wrap" }, new GafferSceneUI::Private::BasicInspector( scene->objectPlug(), editScope, [] ( const ObjectPlug *objectPlug ) -> ConstDataPtr { if( auto curves = runTimeCast( objectPlug->getValue() ) ) { - return new BoolData( curves->periodic() ); + return g_curveWraps.at( curves->wrap() ); } return nullptr; } @@ -1125,15 +1145,7 @@ InspectorTree::Inspections curvesTopologyInspectionProvider( ScenePlug *scene, c [] ( const ObjectPlug *objectPlug ) -> ConstDataPtr { if( auto curves = runTimeCast( objectPlug->getValue() ) ) { - switch( curves->basis().standardBasis() ) - { - case StandardCubicBasis::Linear : return new StringData( "Linear" ); - case StandardCubicBasis::Bezier : return new StringData( "Bezier" ); - case StandardCubicBasis::BSpline : return new StringData( "BSpline" ); - case StandardCubicBasis::CatmullRom : return new StringData( "CatmullRom" ); - case StandardCubicBasis::Constant : return new StringData( "Constant" ); - default : return nullptr; - } + return g_curveBases.at( curves->basis().standardBasis() ); } return nullptr; } @@ -1189,13 +1201,6 @@ InspectorTree::Inspections objectParametersInspectionProvider( ScenePlug *scene, const InspectorTree::Registration g_objectParametersInspectionRegistration( { "Location", "Object", "Parameters" }, objectParametersInspectionProvider ); -ConstStringDataPtr g_invalidStringData = new StringData( "Invalid" ); -ConstStringDataPtr g_constantStringData = new StringData( "Constant" ); -ConstStringDataPtr g_uniformStringData = new StringData( "Uniform" ); -ConstStringDataPtr g_vertexStringData = new StringData( "Vertex" ); -ConstStringDataPtr g_varyingStringData = new StringData( "Varying" ); -ConstStringDataPtr g_faceVaryingStringData = new StringData( "FaceVarying" ); - const PrimitiveVariable *primitiveVariable( const Object *object, const std::string &name ) { auto primitive = runTimeCast( object ); @@ -1217,16 +1222,7 @@ ConstStringDataPtr primitiveVariableInterpolation( const std::string &name, cons return nullptr; } - switch( variable->interpolation ) - { - case PrimitiveVariable::Invalid : return g_invalidStringData; - case PrimitiveVariable::Constant : return g_constantStringData; - case PrimitiveVariable::Uniform : return g_uniformStringData; - case PrimitiveVariable::Vertex : return g_vertexStringData; - case PrimitiveVariable::Varying : return g_varyingStringData; - case PrimitiveVariable::FaceVarying : return g_faceVaryingStringData; - default : return nullptr; - } + return g_primitiveVariableInterpolations.at( variable->interpolation ); } ConstStringDataPtr primitiveVariableType( const std::string &name, const ObjectPlug *objectPlug ) diff --git a/src/GafferUI/DragEditGadget.cpp b/src/GafferUI/DragEditGadget.cpp index c2f2dac4457..f9fa15cb4e7 100644 --- a/src/GafferUI/DragEditGadget.cpp +++ b/src/GafferUI/DragEditGadget.cpp @@ -220,7 +220,7 @@ void DragEditGadget::renderLayer( Layer layer, const Style *style, RenderReason IntVectorDataPtr vertsPerCurve = new IntVectorData(); vertsPerCurve->writable().push_back( m_dragPositions->readable().size() ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, m_dragPositions ) ); group->addChild( curves ); diff --git a/src/GafferUI/StandardStyle.cpp b/src/GafferUI/StandardStyle.cpp index 3ae54b3a0e0..0560e15a9d0 100644 --- a/src/GafferUI/StandardStyle.cpp +++ b/src/GafferUI/StandardStyle.cpp @@ -104,7 +104,7 @@ IECoreGL::GroupPtr line( const V3f &p0, const V3f &p1 ) { IntVectorDataPtr vertsPerCurve = new IntVectorData(); vertsPerCurve->writable().push_back( 2 ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); V3fVectorDataPtr verts = new V3fVectorData(); verts->writable().push_back( p0 ); verts->writable().push_back( p1 ); diff --git a/src/GafferVDBUI/VDBVisualiser.cpp b/src/GafferVDBUI/VDBVisualiser.cpp index 0716a848ebc..b754ed9a835 100644 --- a/src/GafferVDBUI/VDBVisualiser.cpp +++ b/src/GafferVDBUI/VDBVisualiser.cpp @@ -303,7 +303,7 @@ class VDBVisualiser : public ObjectVisualiser IECore::IntVectorDataPtr vertsPerCurve = new IECore::IntVectorData; vertsPerCurve->writable().resize( 3, 2 ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, vertsPerCurve ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, vertsPerCurve ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, pData ) ); group->addChild( curves ); } @@ -349,7 +349,7 @@ class VDBVisualiser : public ObjectVisualiser group->getState()->add( new IECoreGL::WireframeColorStateComponent( colors[depth % colors.size()] ) ); group->getState()->add( new IECoreGL::CurvesPrimitive::GLLineWidth( 0.5f ) ); - IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), false, collector.vertsPerCurve[depth] ); + IECoreGL::CurvesPrimitivePtr curves = new IECoreGL::CurvesPrimitive( IECore::CubicBasisf::linear(), IECoreScene::CurvesPrimitive::Wrap::NonPeriodic, collector.vertsPerCurve[depth] ); curves->addPrimitiveVariable( "P", IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Vertex, collector.positions[depth] ) ); group->addChild( curves ); diff --git a/src/IECoreArnold/CurvesAlgo.cpp b/src/IECoreArnold/CurvesAlgo.cpp index 15c80b693f8..1c3bc3cd5a3 100644 --- a/src/IECoreArnold/CurvesAlgo.cpp +++ b/src/IECoreArnold/CurvesAlgo.cpp @@ -73,10 +73,17 @@ const AtString g_numPointsArnoldString("num_points"); const AtString g_orientationsArnoldString("orientations"); const AtString g_orientedArnoldString("oriented"); const AtString g_uvsArnoldString( "uvs" ); +const AtString g_wrapModeArnoldString( "wrap_mode" ); +const AtString g_pinnedArnoldString( "pinned" ); -ConstCurvesPrimitivePtr resampleCurves( const CurvesPrimitive *curves, const std::string &messageContext ) +ConstCurvesPrimitivePtr resampleVertexToVarying( const CurvesPrimitive *curves, const std::string &messageContext ) { - if( curves->basis().standardBasis() == StandardCubicBasis::Linear ) + if( + // For linear and pinned curves, Vertex and Varying both have the same + // `CurvesPrimitive::variableSize()`, so there is no need to resample + curves->basis().standardBasis() == StandardCubicBasis::Linear || + CurvesAlgo::isPinned( curves ) + ) { return curves; } @@ -175,6 +182,28 @@ AtNode *convertCommon( const IECoreScene::CurvesPrimitive *curves, AtUniverse *u // just accept the default } + // Set wrap + + switch( curves->wrap() ) + { + case CurvesPrimitive::Wrap::Pinned : + if( CurvesAlgo::isPinned( curves ) ) + { + AiNodeSetStr( result, g_wrapModeArnoldString, g_pinnedArnoldString ); + } + // Pinning does not apply to this basis, but Arnold will error if + // we request it. The default `nonperiodic` is what we want anyway. + break; + case CurvesPrimitive::Wrap::Periodic : + // Arnold has an enum value for this, but hasn't implemented it, and + // errors if we use it. We prefer a warning to an error. + msg( IECore::Msg::Warning, messageContext, "Arnold does not implement periodic wrap. Using nonperiodic instead." ); + break; + case CurvesPrimitive::Wrap::NonPeriodic : + // Arnold default. No need to set. + break; + } + // Add UVs and arbitrary user parameters convertUVs( curves, result, messageContext ); @@ -188,9 +217,9 @@ AtNode *convertCommon( const IECoreScene::CurvesPrimitive *curves, AtUniverse *u AtNode *convert( const IECoreScene::CurvesPrimitive *curves, AtUniverse *universe, const std::string &nodeName, const AtNode *parentNode, const std::string &messageContext ) { - // Arnold (and IECoreArnold::ShapeAlgo) does not support Vertex PrimitiveVariables for - // cubic CurvesPrimitives, so we resample the variables to Varying first. - ConstCurvesPrimitivePtr resampledCurves = ::resampleCurves( curves, messageContext ); + // Arnold does not support Vertex PrimitiveVariables (see `ShapeAlgo::convertPrimitiveVariable()`), + // so we must resample unless Vertex and Varying have equivalent variable sizes. + ConstCurvesPrimitivePtr resampledCurves = ::resampleVertexToVarying( curves, messageContext ); AtNode *result = convertCommon( resampledCurves.get(), universe, nodeName, parentNode, messageContext ); @@ -221,8 +250,8 @@ AtNode *convert( const IECoreScene::CurvesPrimitive *curves, AtUniverse *univers AtNode *convert( const std::vector &samples, float motionStart, float motionEnd, AtUniverse *universe, const std::string &nodeName, const AtNode *parentNode, const std::string &messageContext ) { - // Arnold (and IECoreArnold::ShapeAlgo) does not support Vertex PrimitiveVariables for - // cubic CurvesPrimitives, so we resample the variables to Varying first. + // Arnold does not support Vertex PrimitiveVariables (see `ShapeAlgo::convertPrimitiveVariable()`), + // so we must resample unless Vertex and Varying have equivalent variable sizes. std::vector updatedSamples; std::vector primitiveSamples; // Also convert "N" to orientations @@ -232,7 +261,7 @@ AtNode *convert( const std::vector &sample nSamples.reserve( samples.size() ); for( const CurvesPrimitive *curves : samples ) { - ConstCurvesPrimitivePtr resampledCurves = ::resampleCurves( curves, messageContext ); + ConstCurvesPrimitivePtr resampledCurves = ::resampleVertexToVarying( curves, messageContext ); updatedSamples.push_back( resampledCurves ); primitiveSamples.push_back( resampledCurves.get() ); diff --git a/src/IECoreArnold/ShapeAlgo.cpp b/src/IECoreArnold/ShapeAlgo.cpp index a01c70e3706..fcc1e951dfe 100644 --- a/src/IECoreArnold/ShapeAlgo.cpp +++ b/src/IECoreArnold/ShapeAlgo.cpp @@ -262,8 +262,6 @@ void convertPrimitiveVariable( const IECoreScene::Primitive *primitive, const Pr case PrimitiveVariable::Vertex : // Arnold doesn't appear to have vertex storage, but // fortunately for many primitives it is equivalent to varying. - // Unfortunately that is not the case for cubic CurvesPrimitives, so - // we resample the variables to Varying (see IECoreArnold::CurvesAlgo). if( primitive->variableSize( primitiveVariable.interpolation ) == primitive->variableSize( PrimitiveVariable::Varying ) ) { arnoldInterpolation = "varying"; diff --git a/src/IECoreDelight/CurvesAlgo.cpp b/src/IECoreDelight/CurvesAlgo.cpp index 57183bad8ec..b8808b2446a 100644 --- a/src/IECoreDelight/CurvesAlgo.cpp +++ b/src/IECoreDelight/CurvesAlgo.cpp @@ -35,6 +35,7 @@ #include "IECoreDelight/NodeAlgo.h" #include "IECoreDelight/ParameterList.h" +#include "IECoreScene/CurvesAlgo.h" #include "IECoreScene/CurvesPrimitive.h" #include "IECore/MessageHandler.h" @@ -51,23 +52,27 @@ namespace const char *g_catmullRom = "catmull-rom"; const char *g_bSpline = "b-spline"; +const char *g_linear = "linear"; +const int g_one = 1; void staticParameters( const IECoreScene::CurvesPrimitive *object, ParameterList ¶meters ) { parameters.add( "nvertices", object->verticesPerCurve(), false ); const char **basis = nullptr; - if( object->basis() == CubicBasisf::catmullRom() ) + switch( object->basis().standardBasis() ) { - basis = &g_catmullRom; - } - else if( object->basis() == CubicBasisf::bSpline() ) - { - basis = &g_bSpline; - } - else - { - IECore::msg( IECore::Msg::Warning, "IECoreDelight", "Unsupported curves basis" ); + case StandardCubicBasis::CatmullRom : + basis = &g_catmullRom; + break; + case StandardCubicBasis::BSpline : + basis = &g_bSpline; + break; + case StandardCubicBasis::Linear : + basis = &g_linear; + break; + default : + IECore::msg( IECore::Msg::Warning, "IECoreDelight", "Unsupported curves basis" ); } if( basis ) @@ -82,10 +87,15 @@ void staticParameters( const IECoreScene::CurvesPrimitive *object, ParameterList } ); } - if( object->periodic() ) + if( object->wrap() == CurvesPrimitive::Wrap::Periodic ) { IECore::msg( IECore::Msg::Warning, "IECoreDelight", "Periodic curves are not supported" ); } + else if( CurvesAlgo::isPinned( object ) ) + { + parameters.add( { "extrapolate", &g_one, NSITypeInteger, 0, 1, 0 } ); + } + } bool convertStatic( const IECoreScene::CurvesPrimitive *object, NSIContext_t context, const char *handle ) diff --git a/src/IECoreRenderMan/CurvesAlgo.cpp b/src/IECoreRenderMan/CurvesAlgo.cpp index d093ef445a8..4e8a08702ed 100644 --- a/src/IECoreRenderMan/CurvesAlgo.cpp +++ b/src/IECoreRenderMan/CurvesAlgo.cpp @@ -37,6 +37,7 @@ #include "GeometryAlgo.h" #include "Loader.h" +#include "IECoreScene/CurvesAlgo.h" #include "IECoreScene/CurvesPrimitive.h" using namespace IECore; @@ -83,6 +84,13 @@ void convertCurvesTopology( const IECoreScene::CurvesPrimitive *curves, RtPrimVa RtUString convertStaticCurves( const IECoreScene::CurvesPrimitive *curves, RtPrimVarList &primVars, const std::string &messageContext ) { + if( CurvesAlgo::isPinned( curves ) ) + { + CurvesPrimitivePtr processedCurves = curves->copy(); + CurvesAlgo::convertPinnedToNonPeriodic( processedCurves.get() ); + return convertStaticCurves( processedCurves.get(), primVars, messageContext ); + } + GeometryAlgo::convertPrimitive( curves, primVars, messageContext ); convertCurvesTopology( curves, primVars, messageContext ); return Loader::strings().k_Ri_Curves; @@ -90,6 +98,21 @@ RtUString convertStaticCurves( const IECoreScene::CurvesPrimitive *curves, RtPri RtUString convertAnimatedCurves( const std::vector &samples, const std::vector &sampleTimes, RtPrimVarList &primVars, const std::string &messageContext ) { + if( CurvesAlgo::isPinned( samples[0] ) ) + { + std::vector processedCurves; + std::vector processedSamples; + processedCurves.reserve( samples.size() ); + processedSamples.reserve( samples.size() ); + for( auto sample : samples ) + { + processedCurves.push_back( sample->copy() ); + CurvesAlgo::convertPinnedToNonPeriodic( processedCurves.back().get() ); + processedSamples.push_back( processedCurves.back().get() ); + } + return convertAnimatedCurves( processedSamples, sampleTimes, primVars, messageContext ); + } + GeometryAlgo::convertPrimitive( reinterpret_cast &>( samples ), sampleTimes, primVars, messageContext ); convertCurvesTopology( samples[0], primVars, messageContext ); return Loader::strings().k_Ri_Curves; diff --git a/startup/gui/menus.py b/startup/gui/menus.py index cd1fc42e18f..d2746a05544 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -305,6 +305,7 @@ def __lightCreator( nodeName, shaderName, shape ) : nodeMenu.append( "/Scene/Object/Merge Curves", GafferScene.MergeCurves, searchText = "MergeCurves" ) nodeMenu.append( "/Scene/Object/Mesh Subdivide", GafferScene.MeshTessellate, searchText = "MeshTessellate" ) nodeMenu.append( "/Scene/Object/Camera Tweaks", GafferScene.CameraTweaks, searchText = "CameraTweaks" ) +nodeMenu.append( "/Scene/Object/Curves Interpolation", GafferScene.CurvesInterpolation, searchText = "CurvesInterpolation" ) nodeMenu.append( "/Scene/Object/Curve Sampler", GafferScene.CurveSampler, searchText = "CurveSampler" ) nodeMenu.append( "/Scene/Object/Closest Point Sampler", GafferScene.ClosestPointSampler, searchText = "ClosestPointSampler" ) nodeMenu.append( "/Scene/Object/UV Sampler", GafferScene.UVSampler, searchText = "UVSampler" )