diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 385d7b8cb6e..4d4e7c0de0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,21 +29,20 @@ jobs: # and then use `include` to define their settings. name: [ - linux, - linux-debug, + linux-python2, + linux-python2-debug, linux-python3, - macos, + macos-python2, ] include: - - name: linux + - name: linux-python2 os: ubuntu-16.04 buildType: RELEASE - variant: linux-python2 publish: true containerImage: gafferhq/build:1.2.0 - dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/2.3.0/gafferDependencies-2.3.0-Python2-linux.tar.gz + dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/3.0.0/gafferDependencies-3.0.0-Python2-linux.tar.gz # GitHub container builds run as root. This causes failures for tests that # assert that filesystem permissions are respected, because root doesn't # respect permissions. So we run the final test suite as a dedicated @@ -51,13 +50,12 @@ jobs: testRunner: su testUser -c sconsCacheMegabytes: 400 - - name: linux-debug + - name: linux-python2-debug os: ubuntu-16.04 buildType: DEBUG - variant: linux-python2 publish: false containerImage: gafferhq/build:1.2.0 - dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/2.3.0/gafferDependencies-2.3.0-Python2-linux.tar.gz + dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/3.0.0/gafferDependencies-3.0.0-Python2-linux.tar.gz testRunner: su testUser -c # Debug builds are ludicrously big, so we must use a larger cache # limit. In practice this compresses down to 4-500Mb. @@ -66,20 +64,18 @@ jobs: - name: linux-python3 os: ubuntu-16.04 buildType: RELEASE - variant: linux-python3 publish: true containerImage: gafferhq/build:1.2.0 - dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/2.3.0/gafferDependencies-2.3.0-Python3-linux.tar.gz + dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/3.0.0/gafferDependencies-3.0.0-Python3-linux.tar.gz testRunner: su testUser -c sconsCacheMegabytes: 400 - - name: macos + - name: macos-python2 os: macos-10.15 buildType: RELEASE - variant: macos-python2 publish: true containerImage: - dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/2.3.0/gafferDependencies-2.3.0-Python2-osx.tar.gz + dependenciesURL: https://github.com/GafferHQ/dependencies/releases/download/3.0.0/gafferDependencies-3.0.0-Python2-osx.tar.gz testRunner: bash -c sconsCacheMegabytes: 400 @@ -129,7 +125,7 @@ jobs: echo GAFFER_SPHINX=`which sphinx-build` >> $GITHUB_ENV env: GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GAFFER_BUILD_VARIANT: ${{ matrix.variant }} + GAFFER_BUILD_VARIANT: ${{ matrix.name }} - name: Disable macOS PR Docs run: | @@ -161,6 +157,10 @@ jobs: scons -j 2 build BUILD_TYPE=${{ matrix.buildType }} OPTIONS=.github/workflows/main/sconsOptions - name: Test + # Tests should complete in well under an hour. If they don't it's most likely because + # of a hang, in which case we'd like to know more quickly than the default 6hr timeout + # allows. + timeout-minutes: 60 run: | echo "::add-matcher::./.github/workflows/main/problemMatchers/unittest.json" ${{ matrix.testRunner }} "${{ env.GAFFER_BUILD_DIR }}/bin/gaffer test" diff --git a/.github/workflows/main/installDependencies.py b/.github/workflows/main/installDependencies.py old mode 100644 new mode 100755 index e24a8459f9c..72f3dfa7d0a --- a/.github/workflows/main/installDependencies.py +++ b/.github/workflows/main/installDependencies.py @@ -37,13 +37,17 @@ import os import sys import argparse -import urllib import hashlib +if sys.version_info[0] < 3 : + from urllib import urlretrieve +else : + from urllib.request import urlretrieve + # Determine default archive URL. platform = "osx" if sys.platform == "darwin" else "linux" -defaultURL = "https://github.com/GafferHQ/dependencies/releases/download/2.1.1/gafferDependencies-2.1.1-Python2-" + platform + ".tar.gz" +defaultURL = "https://github.com/ImageEngine/cortex/releases/download/10.2.0.0-a2/cortex-10.2.0.0-a2-" + platform + "-python2.tar.gz" # Parse command line arguments. @@ -74,7 +78,7 @@ # Download and unpack the archive. sys.stderr.write( "Downloading dependencies \"%s\"\n" % args.archiveURL ) -archiveFileName, headers = urllib.urlretrieve( args.archiveURL ) +archiveFileName, headers = urlretrieve( args.archiveURL ) os.makedirs( args.dependenciesDir ) os.system( "tar xf %s -C %s --strip-components=1" % ( archiveFileName, args.dependenciesDir ) ) diff --git a/.github/workflows/main/setBuildVars.py b/.github/workflows/main/setBuildVars.py index 0e5e0d126dd..7d61d5a5b3b 100755 --- a/.github/workflows/main/setBuildVars.py +++ b/.github/workflows/main/setBuildVars.py @@ -118,7 +118,6 @@ # We have a couple of naming conventions for builds, depending on the nature of the trigger. formatVars = { - "buildTypeSuffix" : "-debug" if os.environ.get( "BUILD_TYPE", "" ) == "DEBUG" else "", "variant" : os.environ["GAFFER_BUILD_VARIANT"], "timestamp" : datetime.datetime.now().strftime( "%Y_%m_%d_%H%M" ), "pullRequest" : pullRequest, @@ -128,9 +127,9 @@ } nameFormats = { - "default" : "gaffer-{timestamp}-{shortCommit}-{variant}{buildTypeSuffix}", - "pull_request" : "gaffer-pr{pullRequest}-{branch}-{timestamp}-{shortCommit}-{variant}{buildTypeSuffix}", - "release" : "gaffer-{tag}-{variant}{buildTypeSuffix}" + "default" : "gaffer-{timestamp}-{shortCommit}-{variant}", + "pull_request" : "gaffer-pr{pullRequest}-{branch}-{timestamp}-{shortCommit}-{variant}", + "release" : "gaffer-{tag}-{variant}" } trigger = os.environ.get( 'GITHUB_EVENT_NAME', '' ) diff --git a/.github/workflows/whitespaceCheck.yml b/.github/workflows/whitespaceCheck.yml new file mode 100644 index 00000000000..a06236305e6 --- /dev/null +++ b/.github/workflows/whitespaceCheck.yml @@ -0,0 +1,13 @@ +name: Whitespace check +on: [ pull_request ] +jobs: + check: + runs-on: ubuntu-16.04 + steps: + - uses: actions/checkout@v2 + with: + # So that we get the branch for `base_ref`. + fetch-depth: 0 + - name: Check whitespace + run: | + git -c core.whitespace=indent-with-non-tab,tabwidth=1 diff --check refs/remotes/origin/${{ github.base_ref }} include src python diff --git a/.gitignore b/.gitignore index 0aa120ce601..5d677bba9fe 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.swp *.so *.tmp +*.desktop +*.code-workspace .gafferBackups .idea .sconf_temp diff --git a/Changes.md b/Changes.md index 05b0190b4d3..9e1ccf0bacb 100644 --- a/Changes.md +++ b/Changes.md @@ -1,3 +1,299 @@ +0.60.0.0 +======== + +> Note : This version is built against Arnold 6.2.0.1, and is not compatible with earlier Arnold versions. + +Features +-------- + +- Spreadsheet : Added drag and drop reordering of rows. +- InteractiveRender : Added support for motion blur. +- Profiling : Added "Tools/Profiling" menu to annotate nodes with performance metrics. + +Improvements +------------ + +- Serialisation : Reduced script save times by around 50%. +- Cancellation : Improved responsiveness by supporting cancellation of long computes in the following nodes : + - SceneReader + - LevelSetOffset + - MeshToLevelSet + - Plane + - Sphere + - DeleteFaces + - MeshDistortion + - MeshTangents + - MeshType + - PrimitiveSampler + - ResamplePrimitiveVariables + - ReverseWinding + - Seeds +- Expression : + - Improved performance of Python expression evaluation when the same result is required in multiple threads. Specific expression benchmarks have shown a 10x speedup and some production scenes show an overall 15-30% improvement. Caution : This can expose pre-existing bugs in other nodes - see Breaking Changes for details. + - Improved error message when Python expression assigns an invalid value. +- Numeric Bookmarks : Changed the Editor 1-9 hotkeys to follow the bookmark rather than pinning it (#4074). +- Editors : Simplified the Editor Focus Menu, removing some seldom used (but potentially ambiguous) modes (#4074). +- Timeline : + - Added support for sub-frame dragging with a Ctrl modifier, and fixed snapping of the frame indicator for regular drag operations. + - The current frame is now drawn next to the playhead. +- Increased image processing tile size from 64 pixels to 128 pixels. This reduces per-tile overhead on large images, dramatically increasing effective image performance in many cases. +- SceneNode/SceneProcessor : Enforced that the value of the `enabled` plug may not be varied using the `scene:path` context variable. Attempts to do so could result in the generation of invalid scenes. Filters are the appropriate way to enable or disable a node on a per-location basis, and should be used instead. This change yielded a 5-10% performance improvement for a moderately complex scene. +- OSLImage : Avoided some unnecessary computes and hashing when calculating channel names or passing through channel data unaltered. +- Context : Optimized `hash()` method, and reduced overhead in `EditableScope`. +- NameSwitch/Spreadsheet : Rows with an empty name are now treated as if they were disabled. See Breaking Changes for further details. +- ContextVariables : Improved performance by around 50%. +- FilterResults : Improved performance. +- SceneAlgo : Reduced threading overhead for `parallelProcessLocations()`, `parallelTraverse()` and `filteredParallelTraverse()`. This is particularly noticeable when visiting locations with many children. +- Set : Added wildcard support to the `name` plug. +- GraphEditor : Added tool menu with options to control visibility of annotations. +- Render : Improved scene generation times for renders that use `dispatcher.batchSize` to + render multiple frames at once. Previously Gaffer's cache was cleared after scene generation + on each frame, but this is now only done for single-frame batches. + +Fixes +----- + +- Instancer : Fixed variation of prototype root attributes using context variables. +- ScriptNode : Fixed bugs that allowed global variables to remain in the context after they had been disabled, renamed or deleted. +- SceneReader : + - Fixed crash when reading Alembic caches with non-scalar `userProperties`. + - Fixed crash when reading Alembic caches with invalid primitive variables. +- UDIMQuery and OSLImage : Fixed incorrectly isolated TBB which could cause hang when other nodes use Standard cache policy. Now uses TaskCollaboration to improve performance. +- Wrapper : Removed the `PYTHONHOME` environment variable. This fixes problems running Gaffer in python-enabled versions of `gdb`. +- CompoundNumericPlug : Fixed serialisation of dynamic plugs with non-default interpretations. + +API +--- + +- Context : + - Refactored to allow EditableScope to avoid memory allocation where possible. + - Added fast non-allocating `EditableScope::set( name, const T * )` overload. This should be used in preference to the old `set( name, const T & )` method. + - Added `EditableScope::setAllocated()` method to replace the old `set()` method in the rare circumstance where allocation is required. + - Added `variableHash()` method, which returns the hash for an individual variable. + - Added `getIfExists()` method, which returns `nullptr` if a variable doesn't exist. + - Added `getAsData()` method, which returns a copy of a variable as `IECore::Data`. + - Added `TypeDescription` registration class, which must be used to register any custom data types used in context variables. +- GraphComponent : Added `reorderChildren()` and `childrenReorderedSignal()` methods. +- Serialisation : Added `addModule()` method, for adding imports to the serialisation. +- Slider : + - Added optional value snapping for drag and button press operations. This is controlled via the `setSnapIncrement()` and `getSnapIncrement()` methods. + - Added `setHoverPositionVisible()` and `getHoverPositionVisible()` accessors to control an optional position indicator drawn under the pointer. +- Expression : Added `Engine::executeCachePolicy()` method which must be implemented by subclasses. +- ImageAlgo : Added constants for the default channel names - `channelNameR` etc. +- SceneAlgo : Added optional `root` argument to `filteredParallelTraverse( scene, pathMatcher )`. +- MetadataAlgo : + - Added optional `user` argument to `addAnnotationTemplate()`. + - Added optional `userOnly` argument to `annotationTemplates()`. +- AnnotationsGadget : Added `setVisibleAnnotations()` and `getVisibleAnnotations()` methods to allow filtering of annotations. +- MonitorAlgo : Added `removePerformanceAnnotations()` and `removeContextAnnotations()` methods. +- Deformer : Added `affectsProcessedObjectBound()`, `hashProcessedObjectBound()` and `computeProcessedObjectBound()` virtual + methods. These can optionally be overridden by derived classes to compute faster approximate bounds where possible. + +Breaking Changes +---------------- + +- NameSwitch/Spreadsheet : Rows with an empty name are now treated as if they were disabled. Previously they would cause confusion by being matched against empty selectors. Use the default row for empty selectors instead, or alternatively use a catch-all `*` row. +- Context : + - Removed `Ownership` enum. The copy constructor now always performs a full copy. + - Removed `changed()` method. + - Removed `_copy` argument from `get()` Python binding. +- Slider/NumericSlider : + - Refactored Slider to provide all the functionality of NumericSlider, and removed NumericSlider. + - Renamed initial constructor argument from `value` to `values`. + - Removed `setPositions()/getPositions()` and `setPosition()/getPosition()` methods from `Slider`. Use `setValues()/getValues()` and `setValue()/getValue()` instead. + - Removed `positionChangedSignal()` from `Slider`. Use `valueChangedSignal()` instead. + - Removed `PositionChangedReason` from `Slider`. Use `ValueChangedReason` instead. + - Removed `setPositionIncrement()/getPositionIncrement()` from `Slider`. Use `setIncrement()/getIncrement()` instead. + - Replaced `_drawPosition()` method with `_drawValue()`. +- StandardOptions : Removed `cameraBlur` plug. This never functioned as advertised, as the regular `transformBlur` and `deformationBlur` blur settings were applied to cameras instead. As before, a StandardAttributes node may be used to customise blur for individual cameras. +- SceneAlgo : + - Changed signature of the following methods to use `GafferScene::FilterPlug` : `matchingPaths`, `filteredParallelTraverse`, `Detail::ThreadableFilteredFunctor`. + - Removed `filteredParallelTraverse()` overload which accepted a `Filter *`. Pass `filter->outPlug()` instead. +- DeleteFaces / DeletePoints / DeleteCurves : The PrimitiveVariable name is now taken verbatim, rather than stripping whitespace. +- Serialisation : + - Disabled copy construction. + - The following methods now take a `const object &` where they used to take `object &` : + - `modulePath()` + - `classPath()` + - The following methods now take a `Serialisation &` argument where they used to take `const Serialisation &` : + - `constructor()` + - `postConstructor()` + - `postHierarchy()` + - `postScript()` +- ValuePlugBinding : + - `repr()` now takes a `Serialisation *` where it used to take a `const Serialisation *`. + - `valueRepr()` now has an optional `serialisation` argument. +- Metadata : Renamed signal types : + - `NodeValueChangedSignal` -> `LegacyNodeValueChangedSignal` + - `PlugValueChangedSignal` -> `LegacyPlugValueChangedSignal` + - `NodeValueChangedSignal2` -> `NodeValueChangedSignal` + - `PlugValueChangedSignal2` -> `PlugValueChangedSignal` +- MetadataBinding : + - Added `serialisation` required argument to `metadataSerialisation()`. + - Removed `metadataModuleDependencies()` method. Module dependencies are now declared automatically by `metadataSerialisation()`. +- Editors : Removed the 'Follow Scene Selection' mode from the Node Editor Focus menu (#4074). +- GafferSceneUI : Removed `SourceSet`. +- ScriptNode : Added private member data. +- Expression : Changed the Python expression cache policy to `Standard`. This executes expressions behind a lock, and can cause hangs if buggy upstream nodes perform TBB tasks without an appropriate `TaskIsolation` or `TaskCollaboration` policy. In this case, the `GAFFER_PYTHONEXPRESSION_CACHEPOLICY` environment variable may be set to `Legacy` or `TaskIsolation` while the bugs are fixed. +- Node : Removed `plugFlagsChangedSignal()`. We aim to phase flags out completely in future, and none of the current flags are expected to be changed after construction. +- ContextProcessor : Added `storage` argument to `processContext()` method. +- FilteredChildIterator/FilteredRecursiveChildIterator : Annotated all namespace-level typedefs with `[[deprecated]]`. These were already documented as deprecated in Gaffer 0.59.0.0, but their use will now trigger compiler warnings. Please use the class-level typedefs instead, for example `Plug::Iterator` in place of `PlugIterator`. +- RendererAlgo : Removed from the API. The render adaptor registry and `applyCameraGlobals()` are still available, but have been moved to SceneAlgo. +- MonitorAlgo : Removed deprecated `annotate()` overloads. Source compatibility is retained. +- Instancer : Attributes from the prototype root are now placed at the instance root, rather than on the instance group. This allows context variation to potentially vary these attributes. Usually attribute inheritance will mean that this behaves the same, but scenes which explicitly override attributes at specific locations in the hierarchy after an instancer could see modified behaviour. +- PointsGridToPoints : Changed default value of `filter` input, so that a filter must now be connected to specify the objects to modify. +- GafferVDB : Changed base class of the following nodes : + - LevelSetToMesh + - MeshToLevelSet + - LevelSetOffset + - PointsGridToPoints +- LevelSetToMesh : Changed default value for `adjustBounds` plug. + +Build +----- + +- Moved minimum required C++ standard to C++14. +- Moved minimum required TBB version to 2018 Update 3. +- Dependencies : Updated to GafferHQ/dependencies 3.0.0 : + - USD 21.05. + - OpenImageIO 2.2.15.1. + - OpenShadingLanguage 1.11.14.1. + - LibTIFF 4.1.0. + - LLVM 10.0.1. + - OpenVDB 7.2.2. + - Cortex 10.2.0.0. + +0.59.9.0 (relative to 0.59.8.0) +======== + +Features +-------- + +- TransformQuery : Added a new node to query the transform for a scene location. +- BoundQuery : Added a new node to query the bound for a scene location. +- ExistenceQuery : Added a new node to query whether a scene location exists. + +Improvements +------------ + +- OSLShader : Reduced loading times where many nodes reference the same shader. +- Spreadsheet : Added `activeRowNames` plug to the Node Editor UI, in the Advanced tab. + +Fixes +----- + +- ArnoldMeshLight : Fixed label for `cameraVisibility` plug. + +Documentation +------------- + +- Fixed code samples in "Tutorials : Querying a Scene". + +0.59.8.0 (relative to 0.59.7.0) +======== + +Features +-------- + +- Viewer : Added multiple color inspectors. Ctrl+click on an image + to create a pixel inspector, or Ctrl+drag to create an area + inspector. The image footer now shows the results from all your inspectors, + and allows you to add or delete them. +- FilterQuery : Added a new node for querying the results of a filter at a specific location. +- GraphEditor : Added "Annotate..." item to the node context menu. This can be configured with + multiple annotation templates using the `MetadataAlgo` API. + +Improvements +------------ + +- Set : Added `setVariable` plug to allow the input filter to be varied depending on the set name. +- TabbedContainer : Added menu button to allow selection of tabs that are not + visible due to a lack of horizontal space. + +Fixes +----- + +- Arnold : Fixed rendering of encapsulated objects for which automatic instancing + is not possible. Examples include curves with non-zero `ai:curves:min_pixel_width` + and meshes with non-zero `ai:polymesh:subdiv_adaptive_error`. +- PlugValueWidget : Fixed bug that tried to update the widget before all graph edits were complete. +- GraphEditor : Fixed framing of nodes dropped into the editor. This was incorrect when the editor was + not at the default zoom. +- OSL Constant : Fixed usage as a surface shader in Arnold. + +API +--- + +- Context : + - Added forwards compatibility for methods added to provide enhanced + performance in Gaffer 0.60. This allows the same code to be compiled for + both Gaffer 0.60 and Gaffer 0.59 (but with only the Gaffer 0.60 build + benefiting from improved performance). + - Added support for `IECore::InternedString` variables in `substitute()`. +- MetadataAlgo : Added functions for managing annotations on nodes. +- MonitorAlgo : Added `persistent` argument to `annotate()` functions. + +0.59.7.0 (relative to 0.59.6.0) +======== + +Improvements +------------ + +- Parent : + - Added `parentVariable` plug, to create a context variable that passes the + parent location to nodes upstream of the `children` plug. This allows the children + to be varied procedurally according to what they are parented to. + - Added `destination` plug, to allow children to be placed elsewhere in the scene + while still inheriting the transform of the "parent". This is particularly useful + when parenting lights to geometry. +- Seeds : Added `destination` plug, to control where the points are placed in the scene + relative to the meshes they are generated from. +- Duplicate : + - Added `filter` input, allowing multiple objects to be duplicated at once. + - Added `destination` plug, to control where the copies are placed relative to the + original. + - Improved performance for large numbers of copies. + - Deprecated the `target` plug. Please use filters instead. +- Outputs : Reduced the time taken to show the NodeEditor by around 90%. +- NodeEditor : The "Node Name" label is now draggable. For instance, it can be dragged to the PythonEditor to get a reference to the node or to the GraphEditor to find the node in the graph. +- GraphEditor : Improved framing of nodes dragged and dropped onto the GraphEditor : + - Changed pointer to indicate that framing will take place. + - Nodes are framed directly under the pointer instead of at the centre of the widget. + - Fixed framing of nodes not currently in the GraphEditor. + - Removed framing of _plugs_ dragged to the GraphEditor. This was unintuitive and interacted poorly with the dragging of plugs to make + connections. The NodeEditor's "Node Name" label can be dragged instead to locate a node from the NodeEditor. +- SceneInspector : Improved history view : + - Added the full path to nodes so that nodes nested in Boxes can be identified. + - Added edit button to open a NodeEditor for nodes in the history. + - Fixed gap in between sections. +- FilterResults : Added `root` plug. This can be used to limit the results to `root` and its descendants. +- CollectScenes : Added tab completion and a scene browser to the UI for the `sourceRoot` plug. +- BackgroundTaskDialogue : + - Removed focus from "Cancel" button to make it harder to cancel accidentally. + - Added Esc cancellation shortcut. + +Fixes +----- + +- Widget : Fixed drag handling bug that could cause `dragEnterSignal()` to be emitted again on a widget that had already accepted the drag. +- FilterResults : Fixed bug handling matches at the root location. +- NodeEditor : Fixed activator and summary updates which were skipped if the layout was not visible when the node was edited. +- Dispatcher : Fixed dispatching when `dispatcher.batchSize` or `dispatcher.immediate` are driven by context variables. +- SceneNode : Fixed bug hashing the transform for the root location. + +API +--- + +- SceneAlgo : + - Added overloads with `root` argument for `parallelTraverse()`, `filteredParallelTraverse()`, `matchingPaths()` and `matchingPathsHash()`. + - Deprecated `matchingPaths()` overloads taking `Filter *`. Pass a `Filter.out` plug instead. + - Added Python bindings for `matchingPathsHash()`. +- ScenePlug : + - Added support for `..` in `stringToPath()`. + - Added `stringToPath()` and `pathToString()` overloads that return a result rather than passing it by reference. +- GafferUI.FileMenu : Added `dialogueParentWindow` argument to `addScript()`. +- Spreadsheet : Added support for per-plug `ui:spreadsheet:selectorValue` metadata. This defines the initial value for `selector` when the UI is used to create a spreadsheet for the plug. + 0.59.6.0 (relative to 0.59.5.0) ======== @@ -401,6 +697,24 @@ Build - OpenSSL 1.1.1h - See https://github.com/GafferHQ/dependencies/releases/tag/2.1.1 for full details. +0.58.6.7 (relative to 0.58.6.6) +======== + +Fixes +----- + +- ArnoldMeshLight : Fixed label for `cameraVisibility` plug. + +0.58.6.6 (relative to 0.58.6.5) +======== + +Fixes +----- + +- Arnold : Fixed rendering of encapsulated objects for which automatic instancing + is not possible. Examples include curves with non-zero `ai:curves:min_pixel_width` + and meshes with non-zero `ai:polymesh:subdiv_adaptive_error`. + 0.58.6.5 (relative to 0.58.6.4) ======== diff --git a/SConstruct b/SConstruct index fc6ec6a3b27..6d860f04172 100644 --- a/SConstruct +++ b/SConstruct @@ -51,8 +51,8 @@ import subprocess ############################################################################################### gafferMilestoneVersion = 0 # for announcing major milestones - may contain all of the below -gafferMajorVersion = 59 # backwards-incompatible changes -gafferMinorVersion = 6 # new backwards-compatible features +gafferMajorVersion = 60 # backwards-incompatible changes +gafferMinorVersion = 0 # new backwards-compatible features gafferPatchVersion = 0 # bug fixes # All of the following must be considered when determining @@ -100,8 +100,8 @@ options.Add( options.Add( "CXXSTD", - "The C++ standard to build against. A minimum of C++11 is required.", - "c++11", + "The C++ standard to build against. A minimum of C++14 is required.", + "c++14", ) options.Add( @@ -398,7 +398,12 @@ elif env["PLATFORM"] == "posix" : if "g++" in os.path.basename( env["CXX"] ) : + # Get GCC version. gccVersion = subprocess.check_output( [ env["CXX"], "-dumpversion" ], env=env["ENV"] ).decode().strip() + if "." not in gccVersion : + # GCC 7 onwards requires `-dumpfullversion` to get minor/patch, but this + # flag does not exist on earlier GCCs, where minor/patch was provided by `-dumpversion`. + gccVersion = subprocess.check_output( [ env["CXX"], "-dumpfullversion" ], env=env["ENV"] ).decode().strip() gccVersion = [ int( v ) for v in gccVersion.split( "." ) ] # GCC 4.1.2 in conjunction with boost::flat_map produces crashes when @@ -421,6 +426,9 @@ elif env["PLATFORM"] == "posix" : if gccVersion >= [ 5, 1 ] : env.Append( CXXFLAGS = [ "-D_GLIBCXX_USE_CXX11_ABI=0" ] ) + if gccVersion >= [ 9, 2 ] : + env.Append( CXXFLAGS = [ "-Wsuggest-override" ] ) + env["GAFFER_PLATFORM"] = "linux" env.Append( CXXFLAGS = [ "-std=$CXXSTD", "-fvisibility=hidden" ] ) @@ -818,7 +826,7 @@ libraries = { "GafferImageUI" : { "envAppends" : { - "LIBS" : [ "IECoreGL$CORTEX_LIB_SUFFIX", "Gaffer", "GafferImage", "GafferUI", "OpenColorIO$OCIO_LIB_SUFFIX" ], + "LIBS" : [ "IECoreGL$CORTEX_LIB_SUFFIX", "Gaffer", "GafferImage", "GafferUI", "OpenColorIO$OCIO_LIB_SUFFIX", "IECoreScene$CORTEX_LIB_SUFFIX" ], }, "pythonEnvAppends" : { "LIBS" : [ "GafferBindings", "GafferUI", "GafferImage", "GafferImageUI" ], diff --git a/bin/gaffer b/bin/gaffer index 968ece40efc..cb04311a654 100755 --- a/bin/gaffer +++ b/bin/gaffer @@ -155,19 +155,12 @@ fi # Get python set up properly ########################################################################## -# Make sure PYTHONHOME is pointing to our internal python build. -# We only do this if Gaffer has been built with an internal version -# of python - otherwise we assume the existing environment is providing -# the right value. - +# Unset PYTHONHOME to make sure our internal Python build is used in preference +# to anything in the external environment. We only do this if Gaffer has been +# built with an internal version of Python - otherwise we assume the existing +# environment is providing the right value. if [[ -e $GAFFER_ROOT/bin/python ]] ; then - - if [[ `uname` = "Linux" ]] ; then - export PYTHONHOME="$GAFFER_ROOT" - else - export PYTHONHOME="$GAFFER_ROOT/lib/Python.framework/Versions/Current" - fi - + unset PYTHONHOME fi # Get python module path set up diff --git a/config/installArnold.sh b/config/installArnold.sh index 40f2e6a1f3f..98bff50c1e1 100755 --- a/config/installArnold.sh +++ b/config/installArnold.sh @@ -37,7 +37,7 @@ set -e -arnoldVersion=6.0.1.0 +arnoldVersion=6.2.0.1 if [[ `uname` = "Linux" ]] ; then arnoldPlatform=linux diff --git a/config/installDependencies.sh b/config/installDependencies.sh index 69fc9301d42..c61266b3373 100755 --- a/config/installDependencies.sh +++ b/config/installDependencies.sh @@ -55,14 +55,4 @@ buildDir=${1:-"build/gaffer-$gafferMilestoneVersion.$gafferMajorVersion.$gafferM # Get the prebuilt dependencies package and unpack it into the build directory -dependenciesVersion="2.1.1" -dependenciesVersionSuffix="" -dependenciesPythonVersion="2" -dependenciesFileName="gafferDependencies-$dependenciesVersion-Python$dependenciesPythonVersion-$platform.tar.gz" -downloadURL="https://github.com/GafferHQ/dependencies/releases/download/$dependenciesVersion$dependenciesVersionSuffix/$dependenciesFileName" - -echo "Downloading dependencies \"$downloadURL\"" -curl -L $downloadURL > $dependenciesFileName - -mkdir -p $buildDir -tar xf $dependenciesFileName -C $buildDir --strip-components=1 +.github/workflows/main/installDependencies.py --dependenciesDir "$buildDir" diff --git a/contrib/scripts/buildCompileCommands.py b/contrib/scripts/buildCompileCommands.py index 936d1db5828..86c78bcac03 100644 --- a/contrib/scripts/buildCompileCommands.py +++ b/contrib/scripts/buildCompileCommands.py @@ -16,7 +16,7 @@ # Make SCons tell us everything it would do to build Gaffer subprocess.check_call( [ "scons", "--clean" ] ) -sconsOutput = subprocess.check_output( [ "scons", "build", "--dry-run", "--no-cache" ] ) +sconsOutput = subprocess.check_output( [ "scons", "build", "--dry-run", "--no-cache" ], universal_newlines = True ) # Write that into a "compile_commands.json" file @@ -31,7 +31,7 @@ file = line.split()[-1] data.append( { - "directory" : "/Users/john/dev/gaffer", + "directory" : os.getcwd(), "command" : line, "file" : file, } diff --git a/doc/source/GettingStarted/TutorialAssemblingTheGafferBot/index.md b/doc/source/GettingStarted/TutorialAssemblingTheGafferBot/index.md index c1960366d83..a08139d69e6 100644 --- a/doc/source/GettingStarted/TutorialAssemblingTheGafferBot/index.md +++ b/doc/source/GettingStarted/TutorialAssemblingTheGafferBot/index.md @@ -309,7 +309,7 @@ To make switching between viewing Gaffy's geometry and the render easier, you ca 1. Select the InteractiveAppleseedRender node. -2. From the editor focus menu ![](images/editorFocusMenuNodeSelectionLinked.png "The editor focus menu") at the top-right of the top panel, choose _Node Selection_ in the _Pin_ group. The menu's icon will change to the pinned icon (![](images/nodeSetStandardSet.png "highlighted pin")) to show it is now locked to a specific node, and the Viewer's title in the tab bar will now include _[InteractiveAppleseedRender]_ to help you keep track of which node(s) are pinned in which editors. +2. From the editor focus menu ![](images/editorFocusMenuNodeSelectionLinked.png "The editor focus menu") at the top-right of the top panel, choose _Pin InteractiveAppleseedRender_. The menu's icon will change to the pinned icon (![](images/nodeSetStandardSet.png "highlighted pin")) to show it is now locked to a specific node, and the Viewer's title in the tab bar will now include _[InteractiveAppleseedRender]_ to help you keep track of which node(s) are pinned in which editors. ![](images/viewerPinnedMaster.png "The Viewer's tab bar and editor focus menu when pinned to a specific node") @@ -394,7 +394,7 @@ For lights to take effect, they need to be combined with the main scene. For sim - Set the Sun Phi Angle plug to `100`. - Set the Luminance plug to `2.5`. -4. Connect the node's out plug to the Group node's in3 plug. +4. Connect the node's out plug to the Group node's in2 plug. ![](images/graphEditorEnvironmentLightNode.png "A new environment light node") diff --git a/doc/source/WorkingWithThePythonScriptingAPI/TutorialQueryingAScene/index.md b/doc/source/WorkingWithThePythonScriptingAPI/TutorialQueryingAScene/index.md index 6874d5a9d5b..36528a2f034 100644 --- a/doc/source/WorkingWithThePythonScriptingAPI/TutorialQueryingAScene/index.md +++ b/doc/source/WorkingWithThePythonScriptingAPI/TutorialQueryingAScene/index.md @@ -11,181 +11,75 @@ First off, we'll create a simple scene using a network of basic nodes. Cut and p import Gaffer import GafferScene import IECore +import imath __children = {} __children["Sphere"] = GafferScene.Sphere( "Sphere" ) -root.addChild( __children["Sphere"] ) -__children["Sphere"]["enabled"].setValue( True ) -__children["Sphere"]["name"].setValue( 'sphere' ) -__children["Sphere"]["transform"]["translate"]["x"].setValue( 0.0 ) -__children["Sphere"]["transform"]["translate"]["y"].setValue( 1.0 ) -__children["Sphere"]["transform"]["translate"]["z"].setValue( 0.0 ) -__children["Sphere"]["transform"]["rotate"]["x"].setValue( 0.0 ) -__children["Sphere"]["transform"]["rotate"]["y"].setValue( 0.0 ) -__children["Sphere"]["transform"]["rotate"]["z"].setValue( 0.0 ) -__children["Sphere"]["transform"]["scale"]["x"].setValue( 1.0 ) -__children["Sphere"]["transform"]["scale"]["y"].setValue( 1.0 ) -__children["Sphere"]["transform"]["scale"]["z"].setValue( 1.0 ) -__children["Sphere"]["type"].setValue( 1 ) -__children["Sphere"]["radius"].setValue( 1.0 ) -__children["Sphere"]["zMin"].setValue( -1.0 ) -__children["Sphere"]["zMax"].setValue( 1.0 ) -__children["Sphere"]["thetaMax"].setValue( 360.0 ) -__children["Sphere"]["divisions"]["x"].setValue( 20 ) -__children["Sphere"]["divisions"]["y"].setValue( 40 ) -__children["Sphere"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Sphere"]["__uiPosition"]["x"].setValue( -9.311037063598633 ) -__children["Sphere"]["__uiPosition"]["y"].setValue( 13.027215003967285 ) -__children["Sphere"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Sphere"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["Sphere"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["Sphere"] ) +__children["Sphere"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Sphere"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Plane"] = GafferScene.Plane( "Plane" ) -root.addChild( __children["Plane"] ) -__children["Plane"]["enabled"].setValue( True ) -__children["Plane"]["name"].setValue( 'plane' ) -__children["Plane"]["transform"]["translate"]["x"].setValue( 0.0 ) -__children["Plane"]["transform"]["translate"]["y"].setValue( 0.0 ) -__children["Plane"]["transform"]["translate"]["z"].setValue( 0.0 ) -__children["Plane"]["transform"]["rotate"]["x"].setValue( 90.0 ) -__children["Plane"]["transform"]["rotate"]["y"].setValue( 0.0 ) -__children["Plane"]["transform"]["rotate"]["z"].setValue( 0.0 ) -__children["Plane"]["transform"]["scale"]["x"].setValue( 1.0 ) -__children["Plane"]["transform"]["scale"]["y"].setValue( 1.0 ) -__children["Plane"]["transform"]["scale"]["z"].setValue( 1.0 ) -__children["Plane"]["dimensions"]["x"].setValue( 10.0 ) -__children["Plane"]["dimensions"]["y"].setValue( 10.0 ) -__children["Plane"]["divisions"]["x"].setValue( 2 ) -__children["Plane"]["divisions"]["y"].setValue( 2 ) -__children["Plane"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Plane"]["__uiPosition"]["x"].setValue( -21.662202835083008 ) -__children["Plane"]["__uiPosition"]["y"].setValue( 13.181886672973633 ) -__children["Plane"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Plane"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["Plane"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["Plane"] ) +__children["Plane"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Plane"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Group"] = GafferScene.Group( "Group" ) -root.addChild( __children["Group"] ) -__children["Group"]["enabled"].setValue( True ) -__children["Group"]["name"].setValue( 'geometry' ) -__children["Group"]["transform"]["translate"]["x"].setValue( 0.0 ) -__children["Group"]["transform"]["translate"]["y"].setValue( 0.0 ) -__children["Group"]["transform"]["translate"]["z"].setValue( 0.0 ) -__children["Group"]["transform"]["rotate"]["x"].setValue( 0.0 ) -__children["Group"]["transform"]["rotate"]["y"].setValue( 0.0 ) -__children["Group"]["transform"]["rotate"]["z"].setValue( 0.0 ) -__children["Group"]["transform"]["scale"]["x"].setValue( 1.0 ) -__children["Group"]["transform"]["scale"]["y"].setValue( 1.0 ) -__children["Group"]["transform"]["scale"]["z"].setValue( 1.0 ) -__children["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Group"]["__uiPosition"]["x"].setValue( -15.069395065307617 ) -__children["Group"]["__uiPosition"]["y"].setValue( 1.615199089050293 ) -__children["Group"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Group"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["Group"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["Group"] ) +__children["Group"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Camera"] = GafferScene.Camera( "Camera" ) -root.addChild( __children["Camera"] ) -__children["Camera"]["enabled"].setValue( True ) -__children["Camera"]["name"].setValue( 'camera' ) -__children["Camera"]["transform"]["translate"]["x"].setValue( 0.0 ) -__children["Camera"]["transform"]["translate"]["y"].setValue( 1.100000023841858 ) -__children["Camera"]["transform"]["translate"]["z"].setValue( 5.300000190734863 ) -__children["Camera"]["transform"]["rotate"]["x"].setValue( 0.0 ) -__children["Camera"]["transform"]["rotate"]["y"].setValue( 0.0 ) -__children["Camera"]["transform"]["rotate"]["z"].setValue( 0.0 ) -__children["Camera"]["transform"]["scale"]["x"].setValue( 1.0 ) -__children["Camera"]["transform"]["scale"]["y"].setValue( 1.0 ) -__children["Camera"]["transform"]["scale"]["z"].setValue( 1.0 ) -__children["Camera"]["projection"].setValue( 'perspective' ) -__children["Camera"]["fieldOfView"].setValue( 50.0 ) -__children["Camera"]["clippingPlanes"]["x"].setValue( 0.009999999776482582 ) -__children["Camera"]["clippingPlanes"]["y"].setValue( 100000.0 ) -__children["Camera"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Camera"]["__uiPosition"]["x"].setValue( 3.1233882904052734 ) -__children["Camera"]["__uiPosition"]["y"].setValue( 13.14145278930664 ) -__children["Camera"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Camera"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["Camera"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["Camera"] ) +__children["Camera"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Camera"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["Group1"] = GafferScene.Group( "Group1" ) -root.addChild( __children["Group1"] ) -__children["Group1"]["enabled"].setValue( True ) -__children["Group1"]["name"].setValue( 'world' ) -__children["Group1"]["transform"]["translate"]["x"].setValue( 0.0 ) -__children["Group1"]["transform"]["translate"]["y"].setValue( 0.0 ) -__children["Group1"]["transform"]["translate"]["z"].setValue( 0.0 ) -__children["Group1"]["transform"]["rotate"]["x"].setValue( 0.0 ) -__children["Group1"]["transform"]["rotate"]["y"].setValue( 0.0 ) -__children["Group1"]["transform"]["rotate"]["z"].setValue( 0.0 ) -__children["Group1"]["transform"]["scale"]["x"].setValue( 1.0 ) -__children["Group1"]["transform"]["scale"]["y"].setValue( 1.0 ) -__children["Group1"]["transform"]["scale"]["z"].setValue( 1.0 ) -__children["Group1"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Group1"]["__uiPosition"]["x"].setValue( -8.414341926574707 ) -__children["Group1"]["__uiPosition"]["y"].setValue( -9.591415405273438 ) -__children["Group1"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["Group1"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["Group1"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["Group1"] ) +__children["Group1"]["in"].addChild( GafferScene.ScenePlug( "in1", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group1"]["in"].addChild( GafferScene.ScenePlug( "in2", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Group1"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["StandardOptions"] = GafferScene.StandardOptions( "StandardOptions" ) -root.addChild( __children["StandardOptions"] ) -__children["StandardOptions"]["enabled"].setValue( True ) -__children["StandardOptions"]["options"]["renderCamera"]["name"].setValue( 'render:camera' ) -__children["StandardOptions"]["options"]["renderCamera"]["value"].setValue( '/world/camera' ) -__children["StandardOptions"]["options"]["renderCamera"]["enabled"].setValue( True ) -__children["StandardOptions"]["options"]["renderResolution"]["name"].setValue( 'render:resolution' ) -__children["StandardOptions"]["options"]["renderResolution"]["value"]["x"].setValue( 1024 ) -__children["StandardOptions"]["options"]["renderResolution"]["value"]["y"].setValue( 778 ) -__children["StandardOptions"]["options"]["renderResolution"]["enabled"].setValue( True ) -__children["StandardOptions"]["options"]["cameraBlur"]["name"].setValue( 'render:cameraBlur' ) -__children["StandardOptions"]["options"]["cameraBlur"]["value"].setValue( False ) -__children["StandardOptions"]["options"]["cameraBlur"]["enabled"].setValue( False ) -__children["StandardOptions"]["options"]["transformBlur"]["name"].setValue( 'render:transformBlur' ) -__children["StandardOptions"]["options"]["transformBlur"]["value"].setValue( False ) -__children["StandardOptions"]["options"]["transformBlur"]["enabled"].setValue( False ) -__children["StandardOptions"]["options"]["deformationBlur"]["name"].setValue( 'render:deformationBlur' ) -__children["StandardOptions"]["options"]["deformationBlur"]["value"].setValue( False ) -__children["StandardOptions"]["options"]["deformationBlur"]["enabled"].setValue( False ) -__children["StandardOptions"]["options"]["shutter"]["name"].setValue( 'render:shutter' ) -__children["StandardOptions"]["options"]["shutter"]["value"]["x"].setValue( -0.25 ) -__children["StandardOptions"]["options"]["shutter"]["value"]["y"].setValue( 0.25 ) -__children["StandardOptions"]["options"]["shutter"]["enabled"].setValue( False ) -__children["StandardOptions"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["StandardOptions"]["__uiPosition"]["x"].setValue( -6.914341926574707 ) -__children["StandardOptions"]["__uiPosition"]["y"].setValue( -26.473411560058594 ) -__children["StandardOptions"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["StandardOptions"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["StandardOptions"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["StandardOptions"] ) +__children["StandardOptions"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["StandardOptions"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["CustomAttributes"] = GafferScene.CustomAttributes( "CustomAttributes" ) -root.addChild( __children["CustomAttributes"] ) -__children["CustomAttributes"]["enabled"].setValue( True ) -__children["CustomAttributes"]["attributes"].addChild( Gaffer.CompoundDataPlug.MemberPlug( "member1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["attributes"]["member1"].addChild( Gaffer.StringPlug( "name", defaultValue = '', flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["attributes"]["member1"]["name"].setValue( 'myString' ) -__children["CustomAttributes"]["attributes"]["member1"].addChild( Gaffer.StringPlug( "value", defaultValue = '', flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["attributes"]["member1"]["value"].setValue( 'aaa' ) -__children["CustomAttributes"]["attributes"]["member1"].addChild( Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["attributes"]["member1"]["enabled"].setValue( True ) -__children["CustomAttributes"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["__uiPosition"]["x"].setValue( -6.914341926574707 ) -__children["CustomAttributes"]["__uiPosition"]["y"].setValue( -18.878076553344727 ) -__children["CustomAttributes"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["CustomAttributes"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["CustomAttributes"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["CustomAttributes"] ) +__children["CustomAttributes"]["attributes"].addChild( Gaffer.NameValuePlug( "", Gaffer.StringPlug( "value", defaultValue = '', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), True, "member1", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) +__children["CustomAttributes"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["CustomAttributes"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) __children["PathFilter"] = GafferScene.PathFilter( "PathFilter" ) -root.addChild( __children["PathFilter"] ) -__children["PathFilter"]["paths"].setValue( IECore.StringVectorData( [ "/world/geometry/sphere" ] ) ) -__children["PathFilter"].addChild( Gaffer.V2fPlug( "__uiPosition", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["PathFilter"]["__uiPosition"]["x"].setValue( 3.9433956146240234 ) -__children["PathFilter"]["__uiPosition"]["y"].setValue( -9.495070457458496 ) -__children["PathFilter"].addChild( Gaffer.V2fPlug( "__uiPosition1", flags = Gaffer.Plug.Flags.Dynamic | Gaffer.Plug.Flags.Serialisable | Gaffer.Plug.Flags.AcceptsInputs | Gaffer.Plug.Flags.PerformsSubstitutions | Gaffer.Plug.Flags.Cacheable, ) ) -__children["PathFilter"]["__uiPosition1"]["x"].setValue( 0.0 ) -__children["PathFilter"]["__uiPosition1"]["y"].setValue( 0.0 ) +parent.addChild( __children["PathFilter"] ) +__children["PathFilter"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["PathFilter"].addChild( Gaffer.V2fPlug( "__uiPosition1", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) +__children["Sphere"]["transform"]["translate"].setValue( imath.V3f( 0, 1, 0 ) ) +__children["Sphere"]["__uiPosition"].setValue( imath.V2f( 8.01966095, 9.80717945 ) ) +__children["Plane"]["transform"]["rotate"].setValue( imath.V3f( 90, 0, 0 ) ) +__children["Plane"]["dimensions"].setValue( imath.V2f( 10, 10 ) ) +__children["Plane"]["divisions"].setValue( imath.V2i( 2, 2 ) ) +__children["Plane"]["__uiPosition"].setValue( imath.V2f( -4.33150482, 9.96185112 ) ) __children["Group"]["in"][0].setInput( __children["Plane"]["out"] ) __children["Group"]["in"][1].setInput( __children["Sphere"]["out"] ) +__children["Group"]["name"].setValue( 'geometry' ) +__children["Group"]["__uiPosition"].setValue( imath.V2f( 2.26130295, -1.60483646 ) ) +__children["Camera"]["transform"]["translate"].setValue( imath.V3f( 0, 1.10000002, 5.30000019 ) ) +__children["Camera"]["__uiPosition"].setValue( imath.V2f( 20.4540863, 9.92141724 ) ) __children["Group1"]["in"][0].setInput( __children["Group"]["out"] ) __children["Group1"]["in"][1].setInput( __children["Camera"]["out"] ) +__children["Group1"]["name"].setValue( 'world' ) +__children["Group1"]["__uiPosition"].setValue( imath.V2f( 8.91635609, -12.811451 ) ) __children["StandardOptions"]["in"].setInput( __children["CustomAttributes"]["out"] ) +__children["StandardOptions"]["options"]["renderCamera"]["value"].setValue( '/world/camera' ) +__children["StandardOptions"]["options"]["renderCamera"]["enabled"].setValue( True ) +__children["StandardOptions"]["options"]["renderResolution"]["enabled"].setValue( True ) +__children["StandardOptions"]["__uiPosition"].setValue( imath.V2f( 10.4163561, -29.6934471 ) ) __children["CustomAttributes"]["in"].setInput( __children["Group1"]["out"] ) __children["CustomAttributes"]["filter"].setInput( __children["PathFilter"]["out"] ) - +__children["CustomAttributes"]["attributes"]["member1"]["name"].setValue( 'myString' ) +__children["CustomAttributes"]["attributes"]["member1"]["value"].setValue( 'aaa' ) +__children["CustomAttributes"]["__uiPosition"].setValue( imath.V2f( 10.4163561, -22.0981121 ) ) +__children["PathFilter"]["paths"].setValue( IECore.StringVectorData( [ '/world/geometry/sphere' ] ) ) +__children["PathFilter"]["__uiPosition"].setValue( imath.V2f( 21.2740936, -12.715106 ) ) del __children ``` @@ -203,10 +97,10 @@ Note that we're just using Python dictionary syntax to access a node by name and ``` g = root["StandardOptions"]["out"]["globals"].getValue() -print type( g ) -print g.keys() -print g["option:render:camera"].value -print g["option:render:resolution"].value +print( type( g ) ) +print( g.keys() ) +print( g["option:render:camera"].value ) +print( g["option:render:resolution"].value ) ``` There are a couple of things to note here. Firstly, although the out plug appears as a single plug in the Graph Editor, it actually has several child plugs, which allow different aspects of the scene to be queried. We accessed the `globals` plug using dictionary syntax, and then retrieved its value using the `getValue()` method. The result was an `IECore::CompoundObject` which we can pretty much treat like a dictionary, with the minor annoyance that we need to use `.value` to actually retrieve the final value we want. @@ -215,7 +109,7 @@ The `option:render:camera` globals entry tells us that the user wants to render ``` g = root["StandardOptions"]["out"]["object"].getValue() -RuntimeError : line 1 : Exception : Context has no entry named "scene:path" +Gaffer.ProcessException : line 1 : Group1.out.object : Context has no variable named "scene:path" ``` That didn't work out so well did it? The problem is that whereas the globals are **global**, different objects are potentially available at each point in the scene hierarchy - we need to say which part of the hierarchy we want the object from. We do that as follows : @@ -224,7 +118,7 @@ That didn't work out so well did it? The problem is that whereas the globals are with Gaffer.Context( root.context() ) as context : context["scene:path"] = IECore.InternedStringVectorData( [ 'world', 'camera' ] ) camera = root["StandardOptions"]["out"]["object"].getValue() - print camera + print( camera ) ``` The `Context` class is central to the way Gaffer works - a single plug can output entirely different values depending on the [Context](../../WorkingWithTheNodeGraph/Contexts/index.md) in which `getValue()` is called. Here we provided a Context as a path within the scene, but for an image node we'd provide a Context with a tile location and channel name. Contexts allow Gaffer to multithread efficiently - each thread uses it's own Context so each thread can be querying a different part of the scene or a different location in an image. That was a bit wordy though wasn't it? For now let's pretend we didn't even take this detour and let's use a utility method that does the same thing instead : @@ -236,10 +130,10 @@ camera = root["StandardOptions"]["out"].object( "/world/camera" ) Much better. Let's take a look at what we got : ``` -print camera.parameters().keys() -print camera.parameters()["projection"].value -print camera.parameters()["projection:fov"].value -print camera.parameters()["clippingPlanes"].value +print( camera.parameters().keys() ) +print( camera.parameters()["projection"].value ) +print( camera.parameters()["focalLength"].value ) +print( camera.parameters()["clippingPlanes"].value ) ``` Again, the camera looks a lot like a dictionary, so queries aren't too hard. @@ -251,7 +145,7 @@ Having our camera is all well and good, but we don't know where it is located sp ``` transform = root["StandardOptions"]["out"].transform( "/world/camera" ) -print transform +print( transform ) ``` That gave us the local transform for the camera in the form of a matrix - we could also use the `fullTransform()` method if we wanted the global transform. @@ -267,8 +161,8 @@ But what about the CustomAttributes node that was applied to the sphere? How can ``` a = root["StandardOptions"]["out"].attributes( "/world/geometry/sphere" ) -print a.keys() -print a["myString"].value +print( a.keys() ) +print( a["myString"].value ) ``` If the sphere had a shader assigned to it, that would appear as `a["shader"]`, but we've deliberately left that out for now to keep this tutorial renderer agnostic. @@ -279,8 +173,8 @@ Traversing the hierarchy One of the key features of the queries above was that they were random access - we could query any location in the scene at any time, without needing to query the parent locations first. That's all well and good, but until now we've been using prior knowledge of the scene structure to decide what to query. In a real situation, our code doesn't know that `/world/geometry/sphere` even exists. We need a means of querying the structure of the scene first, so that we can then query the contents at each location. Oddly enough, the structure is just communicated with another plug alongside the others - this time one called `childNames`. And oddly enough, there's a utility method to help us get its value within the proper Context. Let's start at the root and see what we can find : ``` -print root["StandardOptions"]["out"].childNames( "/" ) -print root["StandardOptions"]["out"].childNames( "/world" ) +print( root["StandardOptions"]["out"].childNames( "/" ) ) +print( root["StandardOptions"]["out"].childNames( "/world" ) ) ``` Rather than continue this manual exploration, let's write a simple recursive function to traverse the scene and print what it finds : @@ -290,11 +184,11 @@ import os def visit( scene, path ) : - print path - print "\tTransform : " + str( scene.transform( path ) ) - print "\tObject : " + scene.object( path ).typeName() - print "\tAttributes : " + " ".join( scene.attributes( path ).keys() ) - print "\tBound : " + str( scene.bound( path ) ) + "\n" + print( path ) + print( "\tTransform : " + str( scene.transform( path ) ) ) + print( "\tObject : " + scene.object( path ).typeName() ) + print( "\tAttributes : " + " ".join( scene.attributes( path ).keys() ) ) + print( "\tBound : " + str( scene.bound( path ) ) + "\n" ) for childName in scene.childNames( path ) : visit( scene, os.path.join( path, str( childName ) ) ) diff --git a/include/Gaffer/ArrayPlug.h b/include/Gaffer/ArrayPlug.h index ddf0063c632..1aa7c6be416 100644 --- a/include/Gaffer/ArrayPlug.h +++ b/include/Gaffer/ArrayPlug.h @@ -105,12 +105,17 @@ class GAFFER_API ArrayPlug : public Plug IE_CORE_DECLAREPTR( ArrayPlug ); -/// \deprecated Use ArrayPlug::Iterator etc instead +[[deprecated("Use `ArrayPlug::Iterator` instead")]] typedef FilteredChildIterator > ArrayPlugIterator; +[[deprecated("Use `ArrayPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputArrayPlugIterator; +[[deprecated("Use `ArrayPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputArrayPlugIterator; +[[deprecated("Use `ArrayPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveArrayPlugIterator; +[[deprecated("Use `ArrayPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputArrayPlugIterator; +[[deprecated("Use `ArrayPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputArrayPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/Backdrop.h b/include/Gaffer/Backdrop.h index f43a276d453..f37e75c4656 100644 --- a/include/Gaffer/Backdrop.h +++ b/include/Gaffer/Backdrop.h @@ -74,8 +74,9 @@ class GAFFER_API Backdrop : public Node IE_CORE_DECLAREPTR( Backdrop ) -/// \deprecated Use Backdrop::Iterator etc instead. +[[deprecated("Use `Backdrop::Iterator` instead")]] typedef FilteredChildIterator > BackdropIterator; +[[deprecated("Use `Backdrop::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveBackdropIterator; } // namespace Gaffer diff --git a/include/Gaffer/BoxIO.h b/include/Gaffer/BoxIO.h index 22baba852a5..5099b3abf0e 100644 --- a/include/Gaffer/BoxIO.h +++ b/include/Gaffer/BoxIO.h @@ -188,8 +188,9 @@ class GAFFER_API BoxIO : public Node IE_CORE_DECLAREPTR( BoxIO ) -/// \deprecated Use BoxIO::Iterator etc instead. +[[deprecated("Use `BoxIO::Iterator` instead")]] typedef FilteredChildIterator > BoxIOIterator; +[[deprecated("Use `BoxIO::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveBoxIOIterator; } // namespace Gaffer diff --git a/include/Gaffer/BoxIn.h b/include/Gaffer/BoxIn.h index 29d9fbe9f80..2504a26f413 100644 --- a/include/Gaffer/BoxIn.h +++ b/include/Gaffer/BoxIn.h @@ -56,8 +56,9 @@ class GAFFER_API BoxIn : public BoxIO IE_CORE_DECLAREPTR( BoxIn ) -/// \deprecated Use BoxIn::Iterator etc instead. +[[deprecated("Use `BoxIn::Iterator` instead")]] typedef FilteredChildIterator > BoxInIterator; +[[deprecated("Use `BoxIn::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveBoxInIterator; } // namespace Gaffer diff --git a/include/Gaffer/BoxOut.h b/include/Gaffer/BoxOut.h index 33924978786..05b9a9ea005 100644 --- a/include/Gaffer/BoxOut.h +++ b/include/Gaffer/BoxOut.h @@ -71,8 +71,9 @@ class GAFFER_API BoxOut : public BoxIO IE_CORE_DECLAREPTR( BoxOut ) -/// \deprecated Use BoxOut::Iterator etc instead. +[[deprecated("Use `BoxOut::Iterator` instead")]] typedef FilteredChildIterator > BoxOutIterator; +[[deprecated("Use `BoxOut::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveBoxOutIterator; } // namespace Gaffer diff --git a/include/Gaffer/BoxPlug.h b/include/Gaffer/BoxPlug.h index 818079ce572..b07de52ad3a 100644 --- a/include/Gaffer/BoxPlug.h +++ b/include/Gaffer/BoxPlug.h @@ -120,30 +120,53 @@ IE_CORE_DECLAREPTR( Box3iPlug ); IE_CORE_DECLAREPTR( Box2fPlug ); IE_CORE_DECLAREPTR( Box3fPlug ); -/// \deprecated Use Box2iPlug::Iterator etc instead +[[deprecated("Use `Box2iPlug::Iterator` instead")]] typedef FilteredChildIterator > Box2iPlugIterator; +[[deprecated("Use `Box2iPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBox2iPlugIterator; +[[deprecated("Use `Box2iPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputBox2iPlugIterator; +[[deprecated("Use `Box3iPlug::Iterator` instead")]] typedef FilteredChildIterator > Box3iPlugIterator; +[[deprecated("Use `Box3iPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBox3iPlugIterator; +[[deprecated("Use `Box3iPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputBox3iPlugIterator; +[[deprecated("Use `Box2fPlug::Iterator` instead")]] typedef FilteredChildIterator > Box2fPlugIterator; +[[deprecated("Use `Box2fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBox2fPlugIterator; +[[deprecated("Use `Box2fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputBox2fPlugIterator; +[[deprecated("Use `Box3fPlug::Iterator` instead")]] typedef FilteredChildIterator > Box3fPlugIterator; +[[deprecated("Use `Box3fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBox3fPlugIterator; +[[deprecated("Use `Box3fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputBox3fPlugIterator; +[[deprecated("Use `Box2iPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBox2iPlugIterator; +[[deprecated("Use `Box2iPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBox2iPlugIterator; +[[deprecated("Use `Box2iPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBox2iPlugIterator; +[[deprecated("Use `Box3iPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBox3iPlugIterator; +[[deprecated("Use `Box3iPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBox3iPlugIterator; +[[deprecated("Use `Box3iPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBox3iPlugIterator; +[[deprecated("Use `Box2fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBox2fPlugIterator; +[[deprecated("Use `Box2fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBox2fPlugIterator; +[[deprecated("Use `Box2fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBox2fPlugIterator; +[[deprecated("Use `Box3fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBox3fPlugIterator; +[[deprecated("Use `Box3fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBox3fPlugIterator; +[[deprecated("Use `Box3fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBox3fPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/CompoundDataPlug.h b/include/Gaffer/CompoundDataPlug.h index aae213ac0c0..1607ef21c3f 100644 --- a/include/Gaffer/CompoundDataPlug.h +++ b/include/Gaffer/CompoundDataPlug.h @@ -90,12 +90,18 @@ class GAFFER_API CompoundDataPlug : public Gaffer::ValuePlug IE_CORE_DECLAREPTR( CompoundDataPlug ); +[[deprecated("Use `CompoundDataPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > CompoundDataPlugIterator; +[[deprecated("Use `CompoundDataPlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputCompoundDataPlugIterator; +[[deprecated("Use `CompoundDataPlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputCompoundDataPlugIterator; +[[deprecated("Use `CompoundDataPlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveCompoundDataPlugIterator; +[[deprecated("Use `CompoundDataPlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputCompoundDataPlugIterator; +[[deprecated("Use `CompoundDataPlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputCompoundDataPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/CompoundNumericPlug.h b/include/Gaffer/CompoundNumericPlug.h index a0a695bc088..1fc8335d7ad 100644 --- a/include/Gaffer/CompoundNumericPlug.h +++ b/include/Gaffer/CompoundNumericPlug.h @@ -143,42 +143,77 @@ IE_CORE_DECLAREPTR( V3iPlug ); IE_CORE_DECLAREPTR( Color3fPlug ); IE_CORE_DECLAREPTR( Color4fPlug ); -/// \deprecated Use V2fPlug::Iterator etc instead +[[deprecated("Use `V2fPlug::Iterator` instead")]] typedef FilteredChildIterator > V2fPlugIterator; +[[deprecated("Use `V2fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV2fPlugIterator; +[[deprecated("Use `V2fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV2fPlugIterator; +[[deprecated("Use `V3fPlug::Iterator` instead")]] typedef FilteredChildIterator > V3fPlugIterator; +[[deprecated("Use `V3fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV3fPlugIterator; +[[deprecated("Use `V3fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV3fPlugIterator; +[[deprecated("Use `V2iPlug::Iterator` instead")]] typedef FilteredChildIterator > V2iPlugIterator; +[[deprecated("Use `V2iPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV2iPlugIterator; +[[deprecated("Use `V2iPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV2iPlugIterator; +[[deprecated("Use `V3iPlug::Iterator` instead")]] typedef FilteredChildIterator > V3iPlugIterator; +[[deprecated("Use `V3iPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV3iPlugIterator; +[[deprecated("Use `V3iPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV3iPlugIterator; +[[deprecated("Use `Color3fPlug::Iterator` instead")]] typedef FilteredChildIterator > Color3fPlugIterator; +[[deprecated("Use `Color3fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputColor3fPlugIterator; +[[deprecated("Use `Color3fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputColor3fPlugIterator; +[[deprecated("Use `Color4fPlug::Iterator` instead")]] typedef FilteredChildIterator > Color4fPlugIterator; +[[deprecated("Use `Color4fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputColor4fPlugIterator; +[[deprecated("Use `Color4fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputColor4fPlugIterator; +[[deprecated("Use `V2fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV2fPlugIterator; +[[deprecated("Use `V2fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV2fPlugIterator; +[[deprecated("Use `V2fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV2fPlugIterator; +[[deprecated("Use `V3fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV3fPlugIterator; +[[deprecated("Use `V3fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV3fPlugIterator; +[[deprecated("Use `V3fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV3fPlugIterator; +[[deprecated("Use `V2iPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV2iPlugIterator; +[[deprecated("Use `V2iPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV2iPlugIterator; +[[deprecated("Use `V2iPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV2iPlugIterator; +[[deprecated("Use `V3iPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV3iPlugIterator; +[[deprecated("Use `V3iPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV3iPlugIterator; +[[deprecated("Use `V3iPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV3iPlugIterator; +[[deprecated("Use `Color3fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveColor3fPlugIterator; +[[deprecated("Use `Color3fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputColor3fPlugIterator; +[[deprecated("Use `Color3fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputColor3fPlugIterator; +[[deprecated("Use `Color4fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveColor4fPlugIterator; +[[deprecated("Use `Color4fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputColor4fPlugIterator; +[[deprecated("Use `Color4fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputColor4fPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/ComputeNode.h b/include/Gaffer/ComputeNode.h index b43aeded2be..bb84469c2ce 100644 --- a/include/Gaffer/ComputeNode.h +++ b/include/Gaffer/ComputeNode.h @@ -91,8 +91,9 @@ class GAFFER_API ComputeNode : public DependencyNode }; -/// \deprecated Use ComputeNode::Iterator etc instead. +[[deprecated("Use `ComputeNode::Iterator` instead")]] typedef FilteredChildIterator > ComputeNodeIterator; +[[deprecated("Use `ComputeNode::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveComputeNodeIterator; } // namespace Gaffer diff --git a/include/Gaffer/Context.h b/include/Gaffer/Context.h index a0e0f6554e8..8e674d91461 100644 --- a/include/Gaffer/Context.h +++ b/include/Gaffer/Context.h @@ -52,9 +52,9 @@ namespace Gaffer { -/// This class provides a dictionary of IECore::Data objects to define the context in which a -/// computation is performed. The most basic entry common to all Contexts is the frame number, -/// but a context may also hold entirely arbitrary entries useful to specific types of +/// This class provides a dictionary of variables to define the context in which a +/// computation is performed. The most basic variable common to all Contexts is the frame number, +/// but a context may also hold entirely arbitrary variables useful to specific types of /// computation. /// /// Contexts are made current using the nested Scope class - any computation triggered by @@ -77,39 +77,10 @@ class GAFFER_API Context : public IECore::RefCounted public : - /// Since there are costs associated with constructing, - /// copying and reference counting the Data values that make - /// up a Context, various ownership options are provided which - /// trade performance for additional constraints on client code. - enum Ownership - { - /// The Context takes its own copy of a value to be held - /// internally. This requires no additional constraints on the - /// part of client code, but has the worst performance. - Copied, - /// The Context shares the value with others, incrementing - /// the reference count to ensure it remains alive for as - /// long as the Context needs it. Because the Context - /// doesn't have sole ownership of the value, other code - /// could change the value without its knowledge. It is the - /// responsibility of client code to either ensure that this does - /// not happen, or to manually call Context::changed() as - /// necessary when it does. This avoids the overhead of copying - /// values when setting them. - Shared, - /// The Context simply references an existing value, and doesn't - /// even increment its reference count. In addition to the constraints - /// for shared ownership, it is also the responsibility - /// of client code to ensure that the value remains alive for the - /// lifetime of the Context. This is significantly faster than - /// either of the previous options. - Borrowed - }; Context(); - /// Copy constructor. The ownership argument is deprecated - use - /// an EditableScope instead of Borrowed ownership. - Context( const Context &other, Ownership ownership = Copied ); + /// Copy constructor + Context( const Context &other ); /// Copy constructor for creating a cancellable context. /// The canceller is referenced, not copied, and must remain /// alive for as long as the context is in use. @@ -123,36 +94,43 @@ class GAFFER_API Context : public IECore::RefCounted typedef boost::signal ChangedSignal; - template - struct Accessor; + /// Sets a variable to the specified value. A copy is taken so that + /// subsequent changes to `value` do not affect the context. + template::value>> + void set( const IECore::InternedString &name, const T &value ); + /// As above, but providing the value as a `Data *`. + void set( const IECore::InternedString &name, const IECore::Data *value ); - /// Calling with simple types (e.g float) will automatically - /// create a TypedData to store the value. + /// Returns a reference to the value of a variable, throwing if it doesn't exist or + /// has the wrong type : `float f = context->get( "myFloat" )`. template - void set( const IECore::InternedString &name, const T &value ); - /// Can be used to retrieve simple types : - /// float f = context->get( "myFloat" ) - /// And also IECore::Data types : - /// const FloatData *f = context->get( "myFloat" ) + const T &get( const IECore::InternedString &name ) const; + /// As above, but returns `defaultValue` if the variable doesn't exist. + /// Note that if you pass in a temporary as the `defaultValue`, you must + /// copy the return value (if you stored it as a reference, you would be + /// referencing the temporary after it was destroyed). template - typename Accessor::ResultType get( const IECore::InternedString &name ) const; - /// As above but returns defaultValue when an entry is not found, rather than throwing - /// an Exception. + const T &get( const IECore::InternedString &name, const T& defaultValue ) const; + /// Returns a pointer to the value for the variable if it exists and has + /// the requested type. Returns `nullptr` if the variable doesn't exist, + /// and throws if it exists but has the wrong type. template - typename Accessor::ResultType get( const IECore::InternedString &name, typename Accessor::ResultType defaultValue ) const; - - /// Removes an entry from the context if it exists - void remove( const IECore::InternedString& name ); - - /// Removes any entries whose names match the space separated patterns + const T *getIfExists( const IECore::InternedString &name ) const; + + /// Returns a copy of the variable if it exists, throwing if it doesn't. This + /// can be used when the type of the variable is unknown, but it is much more + /// expensive than the `get()` methods above because it allocates memory. + IECore::DataPtr getAsData( const IECore::InternedString &name ) const; + /// As above but returns `defaultValue` if the variable does not exist. + IECore::DataPtr getAsData( const IECore::InternedString &name, const IECore::DataPtr &defaultValue ) const; + + /// Removes a variable from the context, if it exists. + void remove( const IECore::InternedString &name ); + /// Removes any variables whose names match the space separated patterns /// provided. Matching is performed using `StringAlgo::matchMultiple()`. void removeMatching( const IECore::StringAlgo::MatchPattern &pattern ); - /// When a Shared or Borrowed value is changed behind the scenes, this method - /// must be called to notify the Context of the change. - void changed( const IECore::InternedString &name ); - - /// Fills the specified vector with the names of all items in the Context. + /// Fills the specified vector with the names of all variables in the Context. void names( std::vector &names ) const; /// @name Time @@ -189,6 +167,10 @@ class GAFFER_API Context : public IECore::RefCounted IECore::MurmurHash hash() const; + /// Return the hash of a particular variable ( or a default MurmurHash() if not present ) + /// Note that this hash includes the name of the variable + inline IECore::MurmurHash variableHash( const IECore::InternedString &name ) const; + bool operator == ( const Context &other ) const; bool operator != ( const Context &other ) const; @@ -242,13 +224,35 @@ class GAFFER_API Context : public IECore::RefCounted EditableScope( const ThreadState &threadState ); ~EditableScope(); + /// Sets a variable with a pointer to a typed value. It is the + /// caller's responsibility to ensure that the pointer remains + /// valid for the lifetime of the EditableScope. This is much + /// faster than `Context::set()` because it doesn't allocating + /// memory, and should be used in all performance-critical code. template + void set( const IECore::InternedString &name, const T *value ); + + template::value > > + [[deprecated("Use faster pointer version, or use the more explicit setAllocated if you actually need to allocate ")]] void set( const IECore::InternedString &name, const T &value ); + /// Sets a variable from a copy of `value`. This is more expensive than the + /// pointer version above, and should be avoided where possible. + template::value > > + void setAllocated( const IECore::InternedString &name, const T &value ); + /// As above, but providing the value as a `Data *`. + void setAllocated( const IECore::InternedString &name, const IECore::Data *value ); + + /// These are fast even though they don't take a pointer, + /// because the EditableScope has dedicated internal storage for + /// the frame. void setFrame( float frame ); - void setFramesPerSecond( float framesPerSecond ); void setTime( float timeInSeconds ); + [[deprecated("Use faster pointer version")]] + void setFramesPerSecond( float framesPerSecond ); + void setFramesPerSecond( const float *framesPerSecond ); + void remove( const IECore::InternedString &name ); void removeMatching( const IECore::StringAlgo::MatchPattern &pattern ); @@ -257,27 +261,114 @@ class GAFFER_API Context : public IECore::RefCounted private : Ptr m_context; + // Provides storage for `setFrame()` and `setTime()` to use + // (There is no easy way to provide external storage for + // setTime, because it multiplies the input value). + float m_frameStorage; }; /// Returns the current context for the calling thread. static const Context *current(); + /// Used to register a data type for use in variable values. + /// See `GafferImage::FormatData` for an example. + template + struct TypeDescription + { + TypeDescription(); + }; + private : - // Storage for each entry. - struct Storage + // Determines the operation of the private copy constructor. + enum class CopyMode { - Storage() : data( nullptr ), ownership( Copied ) {} - // We reference the data with a raw pointer to avoid the compulsory - // overhead of an intrusive pointer. - const IECore::Data *data; - // And use this ownership flag to tell us when we need to do explicit - // reference count management. - Ownership ownership; + // Shares ownership with the source context where possible, + // allocating copies where necessary. Used by all public copy + // constructors. + Owning, + // References existing values without taking ownership, relying on + // the source context to outlive this one. Used by EditableScopes. + NonOwning }; - typedef boost::container::flat_map Map; + Context( const Context &other, CopyMode mode ); + + // Type used for the value of a variable. Can refer to any type `T` for + // which `IECore::TypedData` is available and `registerType()` has + // been called. Values are stored as `const void *` pointing to `T`, + // along with the `IECore::TypeId` for `TypedData`, which is used to + // validate type-safe access. Does not manage memory or ownership in any + // way : this is the responsibility of calling code. + struct Value + { + + inline Value(); + template + Value( const IECore::InternedString &name, const T *value ); + Value( const IECore::InternedString &name, const IECore::Data *value ); + Value( const Value &other ) = default; + + Value &operator = ( const Value &other ) = default; + + template + inline const T &value() const; + IECore::TypeId typeId() const { return m_typeId; } + const void *rawValue() const { return m_value; } + // Note : This includes the hash of the name passed + // to the constructor. + const IECore::MurmurHash &hash() const { return m_hash; } + + bool operator == ( const Value &rhs ) const; + bool operator != ( const Value &rhs ) const; + bool references( const IECore::Data *data ) const; + + IECore::DataPtr makeData() const; + Value copy( IECore::ConstDataPtr &owner ) const; + + // Throws if the `hash()` no longer corresponds to `value()`. + // This can occur if the pointee is modified after calling + // `EditableScope::set( name, const T * )`. + void validate( const IECore::InternedString &name ) const; + + template + static void registerType(); + + private : + + Value( IECore::TypeId typeId, const void *value, const IECore::MurmurHash &hash ); + + IECore::TypeId m_typeId; + const void *m_value; + IECore::MurmurHash m_hash; + + struct TypeFunctions + { + IECore::DataPtr (*makeData)( const Value &value, const void **dataValue ); + bool (*isEqual)( const Value &a, const Value &b ); + Value (*constructor)( const IECore::InternedString &name, const IECore::Data *data ); + const void *(*valueFromData)( const IECore::Data *data ); + void (*validate)( const IECore::InternedString &name, const Value &v ); + }; + + using TypeMap = boost::container::flat_map; + static TypeMap &typeMap(); + static const TypeFunctions &typeFunctions( IECore::TypeId typeId ); + + }; + + // Sets a variable and emits `changedSignal()` as appropriate. Does not + // manage ownership in any way. Returns true if the value was assigned, + // and false if the value was not (due to it being equal to the + // previously stored value). + inline bool internalSet( const IECore::InternedString &name, const Value &value ); + // Throws if variable doesn't exist. + inline const Value &internalGet( const IECore::InternedString &name ) const; + // Returns nullptr if variable doesn't exist. + inline const Value *internalGetIfExists( const IECore::InternedString &name ) const; + + typedef boost::container::flat_map Map; Map m_map; ChangedSignal *m_changedSignal; @@ -285,6 +376,13 @@ class GAFFER_API Context : public IECore::RefCounted mutable bool m_hashValid; const IECore::Canceller *m_canceller; + // The alloc map holds a smart pointer to data that we allocate. It must keep the entries + // alive at least as long as the m_map used for actual accesses is using it, though it may + // hold data longer than it is actually in use. ( ie. a fast pointer based set through + // EditableScope could overwrite a variable without updating m_allocMap ) + typedef boost::container::flat_map AllocMap; + AllocMap m_allocMap; + }; IE_CORE_DECLAREPTR( Context ); diff --git a/include/Gaffer/Context.inl b/include/Gaffer/Context.inl index 884041b19fe..6d253c2a8d6 100644 --- a/include/Gaffer/Context.inl +++ b/include/Gaffer/Context.inl @@ -85,138 +85,192 @@ struct DataTraits > } // namespace Detail -template -struct Context::Accessor +Context::Value::Value() + : m_typeId( IECore::InvalidTypeId ), m_value( nullptr ) { - typedef const T &ResultType; - typedef typename Gaffer::Detail::DataTraits::DataType DataType; +} - /// Returns true if the value has changed - bool set( Storage &storage, const T &value ) +template +Context::Value::Value( const IECore::InternedString &name, const T *value ) + : m_typeId( Detail::DataTraits::DataType::staticTypeId() ), + m_value( value ) +{ + const std::string &nameStr = name.string(); + if( nameStr.size() > 2 && nameStr[0] == 'u' && nameStr[1] == 'i' && nameStr[2] == ':' ) { - const DataType *d = IECore::runTimeCast( storage.data ); - if( d ) - { - if( d->readable() == value ) - { - // no change so early out - return false; - } - else if( storage.ownership == Copied ) - { - // update in place to avoid allocations. the cast is ok - // because we created the value for our own use in the first - // place. storage.data is const to remind us not to mess - // with values we receive as Shared or Borrowed, but since this - // is Copied, we're free to do as we please. - const_cast( d )->writable() = value; - return true; - } - } - - // data wasn't of the right type or we didn't have sole ownership. - // remove the old value and replace it with a new one. - if( storage.data && storage.ownership != Borrowed ) - { - storage.data->removeRef(); - } - - storage.data = new DataType( value ); - storage.data->addRef(); - storage.ownership = Copied; - - return true; + m_hash = IECore::MurmurHash( 0, 0 ); } - - ResultType get( const IECore::Data *data ) + else { - if( !data->isInstanceOf( DataType::staticTypeId() ) ) - { - throw IECore::Exception( boost::str( boost::format( "Context entry is not of type \"%s\"" ) % DataType::staticTypeName() ) ); - } - return static_cast( data )->readable(); + m_hash.append( *value ); + m_hash.append( m_typeId ); + m_hash.append( (uint64_t)&nameStr ); } -}; +} template -struct Context::Accessor::type > >::type> +inline const T &Context::Value::value() const { - typedef typename boost::remove_pointer::type ValueType; - typedef const ValueType *ResultType; - - bool set( Storage &storage, const T &value ) + using DataType = typename Gaffer::Detail::DataTraits::DataType; + if( m_typeId == DataType::staticTypeId() ) { - const ValueType *d = IECore::runTimeCast( storage.data ); - if( d && d->isEqualTo( value ) ) + return *static_cast( m_value ); + } + throw IECore::Exception( boost::str( boost::format( "Context variable is not of type \"%s\"" ) % DataType::staticTypeName() ) ); +} + +template +void Context::Value::registerType() +{ + using ValueType = typename T::ValueType; + TypeFunctions &functions = typeMap()[T::staticTypeId()]; + functions.makeData = []( const Value &value, const void **dataValue ) -> IECore::DataPtr { + typename T::Ptr result = new T( *static_cast( value.rawValue() ) ); + if( dataValue ) { - return false; + *dataValue = &result->readable(); } - - if( storage.data && storage.ownership != Borrowed ) + return result; + }; + functions.isEqual = [] ( const Value &a, const Value &b ) { + // Type of both `a` and `b` has been checked already in `operator ==`. + return (*static_cast( a.rawValue() )) == (*static_cast( b.rawValue() )); + }; + functions.constructor = [] ( const IECore::InternedString &name, const IECore::Data *data ) { + return Value( name, &static_cast( data )->readable() ); + }; + functions.valueFromData = [] ( const IECore::Data *data ) -> const void * { + return &static_cast( data )->readable(); + }; + functions.validate = [] ( const IECore::InternedString &name, const Value &v ) { + const Value rehashed( name, static_cast( v.rawValue() ) ); + if( v.hash() != rehashed.hash() ) { - storage.data->removeRef(); + throw IECore::Exception( boost::str( + boost::format( "Context variable \"%1%\" has an invalid hash" ) % name + ) ); } + }; +} - IECore::DataPtr valueCopy = value->copy(); - storage.data = valueCopy.get(); - storage.data->addRef(); - storage.ownership = Copied; +template +void Context::set( const IECore::InternedString &name, const T &value ) +{ + // Allocate a new typed Data, store it in m_allocMap so that it won't be deallocated, + // and call internalSet to reference it in the main m_map + typedef typename Gaffer::Detail::DataTraits::DataType DataType; + typename DataType::Ptr d = new DataType( value ); + if( internalSet( name, Value( name, &d->readable() ) ) ) + { + m_allocMap[name] = d; + } +} +bool Context::internalSet( const IECore::InternedString &name, const Value &value ) +{ + if( !m_changedSignal ) + { + // Fast path, typically in an EditableScope, where we + // expect the value to have changed and don't want the + // expense of checking. + m_map[name] = value; + m_hashValid = false; return true; } - - ResultType get( const IECore::Data *data ) + else { - if( !data->isInstanceOf( T::staticTypeId() ) ) + // Avoid emitting `changedSignal` if the value hasn't + // actually changed. We want to avoid expensive re-evaluations + // that might otherwise be triggered in the UI. + Value &v = m_map[name]; + if( v != value ) { - throw IECore::Exception( boost::str( boost::format( "Context entry is not of type \"%s\"" ) % T::staticTypeName() ) ); + v = value; + m_hashValid = false; + (*m_changedSignal)( this, name ); + return true; + } + else + { + return false; } - return static_cast( data ); } -}; +} -template -void Context::set( const IECore::InternedString &name, const T &value ) +inline const Context::Value &Context::internalGet( const IECore::InternedString &name ) const { - Storage &s = m_map[name]; - if( Accessor().set( s, value ) ) + const Value *result = internalGetIfExists( name ); + if( !result ) { - m_hashValid = false; - if( m_changedSignal ) - { - (*m_changedSignal)( this, name ); - } + throw IECore::Exception( boost::str( boost::format( "Context has no variable named \"%s\"" ) % name.value() ) ); } + +#ifndef NDEBUG + result->validate( name ); +#endif + + return *result; } -template -typename Context::Accessor::ResultType Context::get( const IECore::InternedString &name ) const +inline const Context::Value *Context::internalGetIfExists( const IECore::InternedString &name ) const { Map::const_iterator it = m_map.find( name ); - if( it == m_map.end() ) + return it != m_map.end() ? &it->second : nullptr; +} + +template +const T &Context::get( const IECore::InternedString &name ) const +{ + return internalGet( name ).value(); +} + +template +const T &Context::get( const IECore::InternedString &name, const T &defaultValue ) const +{ + if( const Value *value = internalGetIfExists( name ) ) + { + return internalGet( name ).value(); + } + return defaultValue; +} + +inline IECore::MurmurHash Context::variableHash( const IECore::InternedString &name ) const +{ + if( const Value *value = internalGetIfExists( name ) ) { - throw IECore::Exception( boost::str( boost::format( "Context has no entry named \"%s\"" ) % name.value() ) ); + return value->hash(); } - return Accessor().get( it->second.data ); + return IECore::MurmurHash(); } template -typename Context::Accessor::ResultType Context::get( const IECore::InternedString &name, typename Accessor::ResultType defaultValue ) const +const T *Context::getIfExists( const IECore::InternedString &name ) const { - Map::const_iterator it = m_map.find( name ); - if( it == m_map.end() ) + if( const Value *value = internalGetIfExists( name ) ) { - return defaultValue; + return &value->value(); } - return Accessor().get( it->second.data ); + return nullptr; } template +void Context::EditableScope::set( const IECore::InternedString &name, const T *value ) +{ + m_context->internalSet( name, Value( name, value ) ); +} + +template void Context::EditableScope::set( const IECore::InternedString &name, const T &value ) { m_context->set( name, value ); } +template +void Context::EditableScope::setAllocated( const IECore::InternedString &name, const T &value ) +{ + m_context->set( name, value ); +} + const IECore::Canceller *Context::canceller() const { return m_canceller; @@ -239,6 +293,12 @@ class Context::SubstitutionProvider : public IECore::StringAlgo::VariableProvide }; +template< typename T > +Context::TypeDescription::TypeDescription() +{ + Context::Value::registerType(); +} + } // namespace Gaffer #endif // GAFFER_CONTEXT_INL diff --git a/include/Gaffer/ContextProcessor.h b/include/Gaffer/ContextProcessor.h index 961a8dbe18e..20615317b09 100644 --- a/include/Gaffer/ContextProcessor.h +++ b/include/Gaffer/ContextProcessor.h @@ -88,7 +88,7 @@ class IECORE_EXPORT ContextProcessor : public ComputeNode /// Must be implemented to return true if the input is used in `processContext()`. virtual bool affectsContext( const Plug *input ) const = 0; /// Must be implemented to modify context in place. - virtual void processContext( Context::EditableScope &context ) const = 0; + virtual void processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const = 0; private : diff --git a/include/Gaffer/ContextVariables.h b/include/Gaffer/ContextVariables.h index 59a09764eb5..ba67b755653 100644 --- a/include/Gaffer/ContextVariables.h +++ b/include/Gaffer/ContextVariables.h @@ -60,13 +60,22 @@ class IECORE_EXPORT ContextVariables : public ContextProcessor AtomicCompoundDataPlug *extraVariablesPlug(); const AtomicCompoundDataPlug *extraVariablesPlug() const; + void affects( const Plug *input, DependencyNode::AffectedPlugsContainer &outputs ) const override; + protected : + /// Implemented to compute combinedVariablesPlug + void hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const override; + void compute( ValuePlug *output, const Context *context ) const override; + bool affectsContext( const Plug *input ) const override; - void processContext( Context::EditableScope &context ) const override; + void processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const override; private : + AtomicCompoundDataPlug *combinedVariablesPlug(); + const AtomicCompoundDataPlug *combinedVariablesPlug() const; + static size_t g_firstPlugIndex; }; diff --git a/include/Gaffer/DeleteContextVariables.h b/include/Gaffer/DeleteContextVariables.h index 8d53f895c9c..c7f7069375f 100644 --- a/include/Gaffer/DeleteContextVariables.h +++ b/include/Gaffer/DeleteContextVariables.h @@ -59,7 +59,7 @@ class IECORE_EXPORT DeleteContextVariables : public ContextProcessor protected : bool affectsContext( const Plug *input ) const override; - void processContext( Context::EditableScope &context ) const override; + void processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const override; private : diff --git a/include/Gaffer/DependencyNode.h b/include/Gaffer/DependencyNode.h index 742bafc4b6e..0d0c861e037 100644 --- a/include/Gaffer/DependencyNode.h +++ b/include/Gaffer/DependencyNode.h @@ -91,8 +91,9 @@ class GAFFER_API DependencyNode : public Node }; -/// \deprecated Use DependencyNode::Iterator etc instead. +[[deprecated("Use `DependencyNode::Iterator` instead")]] typedef FilteredChildIterator > DependencyNodeIterator; +[[deprecated("Use `DependencyNode::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveDependencyNodeIterator; } // namespace Gaffer diff --git a/include/Gaffer/Dot.h b/include/Gaffer/Dot.h index 6c9d180af06..51669166209 100644 --- a/include/Gaffer/Dot.h +++ b/include/Gaffer/Dot.h @@ -105,8 +105,9 @@ class GAFFER_API Dot : public DependencyNode IE_CORE_DECLAREPTR( Dot ) -/// \deprecated Use Dot::Iterator etc instead. +[[deprecated("Use `Dot::Iterator` instead")]] typedef FilteredChildIterator > DotIterator; +[[deprecated("Use `Dot::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveDotIterator; } // namespace Gaffer diff --git a/include/Gaffer/Expression.h b/include/Gaffer/Expression.h index acdaccc1e89..15c8da2c051 100644 --- a/include/Gaffer/Expression.h +++ b/include/Gaffer/Expression.h @@ -120,6 +120,8 @@ class GAFFER_API Expression : public ComputeNode /// to apply them to each of the individual output plugs. /// \threading This function may be called concurrently. virtual IECore::ConstObjectVectorPtr execute( const Context *context, const std::vector &proxyInputs ) const = 0; + /// What cache policy should be used for executing the expression. + virtual Gaffer::ValuePlug::CachePolicy executeCachePolicy() const = 0; //@} /// @name Language utilities @@ -179,6 +181,8 @@ class GAFFER_API Expression : public ComputeNode void hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const override; void compute( ValuePlug *output, const Context *context ) const override; + Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; + private : static size_t g_firstPlugIndex; diff --git a/include/Gaffer/FilteredChildIterator.h b/include/Gaffer/FilteredChildIterator.h index 797ae182d71..894fb84ccf5 100644 --- a/include/Gaffer/FilteredChildIterator.h +++ b/include/Gaffer/FilteredChildIterator.h @@ -105,7 +105,7 @@ class FilteredChildIterator : public boost::filter_iterator( BaseIterator::operator->() ); } - FilteredChildIterator &operator++() + FilteredChildIterator &operator++() { BaseIterator::operator++(); return *this; diff --git a/include/Gaffer/GraphComponent.h b/include/Gaffer/GraphComponent.h index ef106a4b653..208686f57ec 100644 --- a/include/Gaffer/GraphComponent.h +++ b/include/Gaffer/GraphComponent.h @@ -38,6 +38,7 @@ #ifndef GAFFER_GRAPHCOMPONENT_H #define GAFFER_GRAPHCOMPONENT_H +#include "Gaffer/CatchingSignalCombiner.h" #include "Gaffer/Export.h" #include "Gaffer/TypeIds.h" @@ -88,8 +89,9 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig GAFFER_GRAPHCOMPONENT_DECLARE_TYPE( Gaffer::GraphComponent, GraphComponentTypeId, IECore::RunTimeTyped ); - typedef boost::signal UnarySignal; - typedef boost::signal BinarySignal; + using UnarySignal = boost::signal; + using BinarySignal = boost::signal; + using ChildrenReorderedSignal = boost::signal &originalIndices ), CatchingSignalCombiner>; /// @name Naming /// All GraphComponents have a name, which must be unique among @@ -127,8 +129,8 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig //////////////////////////////////////////////////////////////////// //@{ /// The datatype used internally to store children. - typedef std::vector ChildContainer; - typedef ChildContainer::const_iterator ChildIterator; + using ChildContainer = std::vector; + using ChildIterator = ChildContainer::const_iterator; /// Components can accept or reject potential children by implementing this /// call. By default all children are accepted. virtual bool acceptsChild( const GraphComponent *potentialChild ) const; @@ -154,9 +156,6 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig /// \todo Do we need acceptsRemoval()? /// \undoable void removeChild( GraphComponentPtr child ); - /// Removes all the children. - /// \undoable - void clearChildren(); /// Get an immediate child by name, performing a runTimeCast to T. template T *getChild( const IECore::InternedString &name ); @@ -174,6 +173,12 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig /// Read only access to the internal container of children. This /// is useful for iteration over children. const ChildContainer &children() const; + /// Reorders the existing children. + /// \undoable + void reorderChildren( const ChildContainer &newOrder ); + /// Removes all the children. + /// \undoable + void clearChildren(); /// Returns a descendant of this node specified by a "." separated /// relative path, performing a runTimeCast to T. template @@ -221,6 +226,10 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig /// of a child being removed from the destructor of the parent, oldParent /// will be null as it is no longer available. BinarySignal &parentChangedSignal(); + /// A signal emitted when children have been reordered. The original indices + /// corresponding to each child are passed, to facilitate reordering of any + /// mirrored data structures or UI. + ChildrenReorderedSignal &childrenReorderedSignal(); //@} protected : @@ -258,6 +267,11 @@ class GAFFER_API GraphComponent : public IECore::RunTimeTyped, public boost::sig /// to maintain a consistent state even if badly behaved observers are /// connected to the signal. virtual void parentChanged( Gaffer::GraphComponent *oldParent ); + // Called by `reorderChildren()` immediately before `childrenReorderedSignal()` + // is emitted. This provides an opportunity to respond to reordering before + // outside observers are notified. Implementations should call the base class + // implementation before doing their own work. + virtual void childrenReordered( const std::vector &oldIndices ); /// It is common for derived classes to provide accessors for /// constant-time access to specific children, as this can be diff --git a/include/Gaffer/Metadata.h b/include/Gaffer/Metadata.h index 2cd37988ba7..ab38570ad08 100644 --- a/include/Gaffer/Metadata.h +++ b/include/Gaffer/Metadata.h @@ -152,14 +152,14 @@ class GAFFER_API Metadata }; using ValueChangedSignal = boost::signal>; - using NodeValueChangedSignal2 = boost::signal>; - using PlugValueChangedSignal2 = boost::signal>; + using NodeValueChangedSignal = boost::signal>; + using PlugValueChangedSignal = boost::signal>; static ValueChangedSignal &valueChangedSignal(); /// Returns a signal that will be emitted when metadata has changed for `node`. - static NodeValueChangedSignal2 &nodeValueChangedSignal( Node *node ); + static NodeValueChangedSignal &nodeValueChangedSignal( Node *node ); /// Returns a signal that will be emitted when metadata has changed for any plug on `node`. - static PlugValueChangedSignal2 &plugValueChangedSignal( Node *node ); + static PlugValueChangedSignal &plugValueChangedSignal( Node *node ); /// Legacy signals /// ============== @@ -168,15 +168,15 @@ class GAFFER_API Metadata /// plug. Their usage leads to performance bottlenecks whereby all observers /// are triggered by all edits. They will be removed in future. - using NodeValueChangedSignal = boost::signal>; - using PlugValueChangedSignal = boost::signal>; + using LegacyNodeValueChangedSignal = boost::signal>; + using LegacyPlugValueChangedSignal = boost::signal>; /// Deprecated, but currently necessary for tracking inherited /// changes to read-only metadata. /// \deprecated - static NodeValueChangedSignal &nodeValueChangedSignal(); + static LegacyNodeValueChangedSignal &nodeValueChangedSignal(); /// \deprecated - static PlugValueChangedSignal &plugValueChangedSignal(); + static LegacyPlugValueChangedSignal &plugValueChangedSignal(); private : diff --git a/include/Gaffer/MetadataAlgo.h b/include/Gaffer/MetadataAlgo.h index 65967ae555c..b9f1d3b205e 100644 --- a/include/Gaffer/MetadataAlgo.h +++ b/include/Gaffer/MetadataAlgo.h @@ -40,9 +40,12 @@ #include "Gaffer/Export.h" #include "Gaffer/Node.h" +#include "IECore/SimpleTypedData.h" #include "IECore/StringAlgo.h" #include "IECore/TypeIds.h" +#include "OpenEXR/ImathColor.h" + #include namespace Gaffer @@ -135,6 +138,60 @@ GAFFER_API Node *getNumericBookmark( ScriptNode *script, int bookmark ); GAFFER_API int numericBookmark( const Node *node ); GAFFER_API bool numericBookmarkAffectedByChange( const IECore::InternedString &changedKey ); +/// Annotations +/// =========== +/// +/// Annotations define arbitrary text to be displayed in a coloured area +/// next to a node. Each node can have arbitrary numbers of annotations, +/// with different annotations being distinguished by their `name`. +/// Templates can be used to define defaults for standard annotation types. +/// The text from the template is used as a default when first creating +/// an annotation via the UI, and the colour from the template provides +/// the default colour if one is not specified explicitly by an annotation +/// itself. + +struct GAFFER_API Annotation +{ + + Annotation() = default; + Annotation( const std::string &text ); + Annotation( const std::string &text, const Imath::Color3f &color ); + Annotation( const IECore::ConstStringDataPtr &text, const IECore::ConstColor3fDataPtr &color = nullptr ); + Annotation( const Annotation &other ) = default; + Annotation( Annotation &&other ) = default; + + operator bool() const { return textData.get(); } + + bool operator == ( const Annotation &rhs ); + bool operator != ( const Annotation &rhs ) { return !(*this == rhs); }; + + IECore::ConstStringDataPtr textData; + IECore::ConstColor3fDataPtr colorData; + + const std::string &text() const { return textData ? textData->readable() : g_defaultText; } + const Imath::Color3f &color() const { return colorData ? colorData->readable() : g_defaultColor; } + + private : + + static std::string g_defaultText; + static Imath::Color3f g_defaultColor; + +}; + +GAFFER_API void addAnnotation( Node *node, const std::string &name, const Annotation &annotation, bool persistent = true ); +GAFFER_API Annotation getAnnotation( const Node *node, const std::string &name, bool inheritTemplate = false ); +GAFFER_API void removeAnnotation( Node *node, const std::string &name ); +GAFFER_API void annotations( const Node *node, std::vector &names ); + +/// Pass `user = false` for annotations not intended for creation directly by the user. +GAFFER_API void addAnnotationTemplate( const std::string &name, const Annotation &annotation, bool user = true ); +GAFFER_API Annotation getAnnotationTemplate( const std::string &name ); +GAFFER_API void removeAnnotationTemplate( const std::string &name ); +/// Pass `userOnly = true` to omit templates registered with `user = false`. +GAFFER_API void annotationTemplates( std::vector &names, bool userOnly = false ); + +GAFFER_API bool annotationsAffectedByChange( const IECore::InternedString &changedKey ); + /// Change queries /// ============== diff --git a/include/Gaffer/Monitor.h b/include/Gaffer/Monitor.h index 0e3c0b7566c..8355edacb68 100644 --- a/include/Gaffer/Monitor.h +++ b/include/Gaffer/Monitor.h @@ -53,7 +53,7 @@ class GAFFER_API Monitor : public IECore::RefCounted public : - virtual ~Monitor(); + ~Monitor() override; IE_CORE_DECLAREMEMBERPTR( Monitor ) diff --git a/include/Gaffer/MonitorAlgo.h b/include/Gaffer/MonitorAlgo.h index e691672914b..920ac6a48ef 100644 --- a/include/Gaffer/MonitorAlgo.h +++ b/include/Gaffer/MonitorAlgo.h @@ -70,9 +70,12 @@ enum PerformanceMetric GAFFER_API std::string formatStatistics( const PerformanceMonitor &monitor, size_t maxLinesPerMetric = 50 ); GAFFER_API std::string formatStatistics( const PerformanceMonitor &monitor, PerformanceMetric metric, size_t maxLines = 50 ); -GAFFER_API void annotate( Node &root, const PerformanceMonitor &monitor ); -GAFFER_API void annotate( Node &root, const PerformanceMonitor &monitor, PerformanceMetric metric ); -GAFFER_API void annotate( Node &root, const ContextMonitor &monitor ); +GAFFER_API void annotate( Node &root, const PerformanceMonitor &monitor, bool persistent = true ); +GAFFER_API void annotate( Node &root, const PerformanceMonitor &monitor, PerformanceMetric metric, bool persistent = true ); +GAFFER_API void annotate( Node &root, const ContextMonitor &monitor, bool persistent = true ); + +GAFFER_API void removePerformanceAnnotations( Node &root ); +GAFFER_API void removeContextAnnotations( Node &root ); } // namespace MonitorAlgo diff --git a/include/Gaffer/NameValuePlug.h b/include/Gaffer/NameValuePlug.h index a8b53894f0b..2835ddb4207 100644 --- a/include/Gaffer/NameValuePlug.h +++ b/include/Gaffer/NameValuePlug.h @@ -134,7 +134,7 @@ class GAFFER_API NameValuePlug : public Gaffer::ValuePlug }; -/// \deprecated Use NameValuePlug::Iterator instead +[[deprecated("Use `NameValuePlug::Iterator` instead")]] typedef FilteredChildIterator > NameValuePlugIterator; IE_CORE_DECLAREPTR( NameValuePlug ); diff --git a/include/Gaffer/Node.h b/include/Gaffer/Node.h index 8d4fd212bed..4236bf1dc4f 100644 --- a/include/Gaffer/Node.h +++ b/include/Gaffer/Node.h @@ -116,8 +116,6 @@ class GAFFER_API Node : public GraphComponent /// onto an input plug of a plain Node (and potentially onwards if that plug /// has its own output connections). UnaryPlugSignal &plugDirtiedSignal(); - /// Emitted when the flags are changed for a plug of this node. - UnaryPlugSignal &plugFlagsChangedSignal(); //@} /// It's common for users to want to create their own plugs on @@ -190,7 +188,6 @@ class GAFFER_API Node : public GraphComponent UnaryPlugSignal m_plugSetSignal; UnaryPlugSignal m_plugInputChangedSignal; - UnaryPlugSignal m_plugFlagsChangedSignal; UnaryPlugSignal m_plugDirtiedSignal; ErrorSignal m_errorSignal; @@ -198,8 +195,9 @@ class GAFFER_API Node : public GraphComponent IE_CORE_DECLAREPTR( Node ) -/// \deprecated Use Node::Iterator etc instead. +[[deprecated("Use `Node::Iterator` instead")]] typedef FilteredChildIterator > NodeIterator; +[[deprecated("Use `Node::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveNodeIterator; } // namespace Gaffer diff --git a/include/Gaffer/NumericPlug.h b/include/Gaffer/NumericPlug.h index d05628c1cb0..1f37374183e 100644 --- a/include/Gaffer/NumericPlug.h +++ b/include/Gaffer/NumericPlug.h @@ -105,18 +105,29 @@ typedef NumericPlug IntPlug; IE_CORE_DECLAREPTR( FloatPlug ); IE_CORE_DECLAREPTR( IntPlug ); -/// \deprecated Use FloatPlug::Iterator etc instead +[[deprecated("Use `FloatPlug::Iterator` instead")]] typedef FilteredChildIterator > FloatPlugIterator; +[[deprecated("Use `FloatPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputFloatPlugIterator; +[[deprecated("Use `FloatPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputFloatPlugIterator; +[[deprecated("Use `IntPlug::Iterator` instead")]] typedef FilteredChildIterator > IntPlugIterator; +[[deprecated("Use `IntPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputIntPlugIterator; +[[deprecated("Use `IntPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputIntPlugIterator; +[[deprecated("Use `FloatPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveFloatPlugIterator; +[[deprecated("Use `FloatPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputFloatPlugIterator; +[[deprecated("Use `FloatPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputFloatPlugIterator; +[[deprecated("Use `IntPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveIntPlugIterator; +[[deprecated("Use `IntPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputIntPlugIterator; +[[deprecated("Use `IntPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputIntPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/Plug.h b/include/Gaffer/Plug.h index d9bbea86a14..5be9702a315 100644 --- a/include/Gaffer/Plug.h +++ b/include/Gaffer/Plug.h @@ -228,6 +228,7 @@ class GAFFER_API Plug : public GraphComponent void parentChanging( Gaffer::GraphComponent *newParent ) override; void parentChanged( Gaffer::GraphComponent *oldParent ) override; + void childrenReordered( const std::vector &oldIndices ) override; /// Initiates the propagation of dirtiness from the specified /// plug to its outputs and affected plugs (as defined by @@ -317,12 +318,17 @@ struct PlugPredicate } }; -/// \deprecated Use Plug::Iterator etc instead +[[deprecated("Use `Plug::Iterator` instead")]] typedef FilteredChildIterator > PlugIterator; +[[deprecated("Use `Plug::InputIterator` instead")]] typedef FilteredChildIterator > InputPlugIterator; +[[deprecated("Use `Plug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputPlugIterator; +[[deprecated("Use `Plug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursivePlugIterator; +[[deprecated("Use `Plug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputPlugIterator; +[[deprecated("Use `Plug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/Private/IECorePreview/ParallelAlgo.h b/include/Gaffer/Private/IECorePreview/ParallelAlgo.h deleted file mode 100644 index 97a0d3ba46c..00000000000 --- a/include/Gaffer/Private/IECorePreview/ParallelAlgo.h +++ /dev/null @@ -1,68 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// Copyright (c) 2019, 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 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. -// -////////////////////////////////////////////////////////////////////////// - -#ifndef IECOREPREVIEW_PARALLELALGO_H -#define IECOREPREVIEW_PARALLELALGO_H - -#include "tbb/task_arena.h" - -namespace IECorePreview -{ - -namespace ParallelAlgo -{ - -// Calls `f` such that any TBB tasks it spawns will run in isolation, -// and cannot steal work from outer tasks. This is of fundamental importance -// if you hold a lock while running any TBB code. See : -// -// https://software.intel.com/en-us/blogs/2018/08/16/the-work-isolation-functionality-in-intel-threading-building-blocks-intel-tbb -template -void isolate( const F &f ) -{ -#if TBB_INTERFACE_VERSION >= 10000 - tbb::this_task_arena::isolate( f ); -#else - tbb::task_arena arena; - arena.execute( f ); -#endif -} - -} // namespace ParallelAlgo - -} // namespace IECorePreview - -#endif // IECOREPREVIEW_PARALLELALGO_H diff --git a/include/Gaffer/Private/IECorePreview/TaskMutex.h b/include/Gaffer/Private/IECorePreview/TaskMutex.h index 93778b16bc1..1d19b395dc2 100644 --- a/include/Gaffer/Private/IECorePreview/TaskMutex.h +++ b/include/Gaffer/Private/IECorePreview/TaskMutex.h @@ -189,30 +189,13 @@ class TaskMutex : boost::noncopyable m_mutex->m_executionState->arena.execute( [this, &fWrapper] { -#if TBB_INTERFACE_VERSION >= 10003 + // Prior to TBB 2018 Update 3, `run_and_wait()` is buggy, + // causing calls to `wait()` on other threads to return + // immediately rather than do the work we want. Use + // `static_assert()` to ensure we never build with a buggy + // version. + static_assert( TBB_INTERFACE_VERSION >= 10003, "Minumum of TBB 2018 Update 3 required" ); m_mutex->m_executionState->taskGroup.run_and_wait( fWrapper ); -#else - // The `run_and_wait()` method is buggy until - // TBB 2018 Update 3, causing calls to `wait()` on other threads to - // return immediately rather than do the work we want. - // So we call `run()` and `wait()` separately. This has two - // downsides though : - // - // 1. It appears to trigger a TBB bug whereby it is sometimes - // unable to destroy the internals of the `task_arena`. It - // then spends increasing amounts of time in - // `market::try_destroy_arena()`, doing a linear search through - // all the zombie arenas until it finds the one it - // wants to destroy, decides for some reason it can't destroy it, - // and gives up. With large numbers of arenas this can add - // huge overhead. - // 2. It does not guarantee that `fWrapper` runs on the same thread - // that `run()` is called on (`run_and_wait()` does provide - // that guarantee). This forces us into nasty ThreadStateFixer - // workarounds in Gaffer/ValuePlug.cpp. - m_mutex->m_executionState->taskGroup.run( fWrapper ); - m_mutex->m_executionState->taskGroup.wait(); -#endif } ); @@ -359,7 +342,7 @@ class TaskMutex : boost::noncopyable observe( true ); } - ~ArenaObserver() + ~ArenaObserver() override { observe( false ); } @@ -403,7 +386,7 @@ class TaskMutex : boost::noncopyable } // Work around https://bugs.llvm.org/show_bug.cgi?id=32978 - ~ExecutionState() noexcept( true ) + ~ExecutionState() noexcept( true ) override { } diff --git a/include/Gaffer/Random.h b/include/Gaffer/Random.h index cc38be7c746..f0d34525072 100644 --- a/include/Gaffer/Random.h +++ b/include/Gaffer/Random.h @@ -96,6 +96,8 @@ class GAFFER_API Random : public ComputeNode }; +IE_CORE_DECLAREPTR( Random ) + } // namespace Gaffer #endif // GAFFER_RANDOM_H diff --git a/include/Gaffer/Reference.h b/include/Gaffer/Reference.h index 209ce59e096..18c24add9f9 100644 --- a/include/Gaffer/Reference.h +++ b/include/Gaffer/Reference.h @@ -92,8 +92,9 @@ class GAFFER_API Reference : public SubGraph IE_CORE_DECLAREPTR( Reference ) -/// \deprecated Use Reference::Iterator etc instead. +[[deprecated("Use `Reference::Iterator` instead")]] typedef FilteredChildIterator > ReferenceIterator; +[[deprecated("Use `Reference::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator > RecursiveReferenceIterator; } // namespace Gaffer diff --git a/include/Gaffer/ScriptNode.h b/include/Gaffer/ScriptNode.h index 1fd0d6b5f10..0c644b75ba6 100644 --- a/include/Gaffer/ScriptNode.h +++ b/include/Gaffer/ScriptNode.h @@ -46,6 +46,8 @@ #include "Gaffer/TypedPlug.h" #include "Gaffer/UndoScope.h" +#include "boost/container/flat_set.hpp" + #include #include @@ -327,7 +329,11 @@ class GAFFER_API ScriptNode : public Node // ================= ContextPtr m_context; + // The names of variables that we have added to `m_context` + // from `variablesPlug()`. + boost::container::flat_set m_currentVariables; + void updateContextVariables(); void plugSet( Plug *plug ); void contextChanged( const Context *context, const IECore::InternedString &name ); diff --git a/include/Gaffer/ShufflePlug.h b/include/Gaffer/ShufflePlug.h index a41ca4dfc91..62f260b5243 100644 --- a/include/Gaffer/ShufflePlug.h +++ b/include/Gaffer/ShufflePlug.h @@ -75,7 +75,7 @@ class GAFFER_API ShufflePlug : public ValuePlug }; -/// \deprecated Use ShufflePlug::Iterator instead +[[deprecated("Use `ShufflePlug::Iterator` instead")]] typedef FilteredChildIterator > ShufflePlugIterator; IE_CORE_DECLAREPTR( ShufflePlug ) diff --git a/include/Gaffer/ShufflePlug.inl b/include/Gaffer/ShufflePlug.inl index 120cc6ba90a..ab9abf23cdd 100644 --- a/include/Gaffer/ShufflePlug.inl +++ b/include/Gaffer/ShufflePlug.inl @@ -85,7 +85,9 @@ T ShufflesPlug::shuffle( const T &sourceContainer ) const for( const auto &sourceData : sourceContainer ) { - scope.set( g_sourceVariable, sourceData.first ); + // Quick way to get a string from a key that could be std::string or IECore::InternedString + const std::string &source = sourceData.first; + scope.set( g_sourceVariable, &source ); i = 0; bool deleteSource = false; diff --git a/include/Gaffer/SplinePlug.h b/include/Gaffer/SplinePlug.h index 4020661b2a6..8924161452b 100644 --- a/include/Gaffer/SplinePlug.h +++ b/include/Gaffer/SplinePlug.h @@ -190,24 +190,41 @@ IE_CORE_DECLAREPTR( SplineffPlug ); IE_CORE_DECLAREPTR( SplinefColor3fPlug ); IE_CORE_DECLAREPTR( SplinefColor4fPlug ); -/// \deprecated Use SplineffPlug::Iterator etc instead +[[deprecated("Use `SplineffPlug::Iterator` instead")]] typedef FilteredChildIterator > SplineffPlugIterator; +[[deprecated("Use `SplineffPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputSplineffPlugIterator; +[[deprecated("Use `SplineffPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputSplineffPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::Iterator` instead")]] typedef FilteredChildIterator > SplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputSplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputSplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::Iterator` instead")]] typedef FilteredChildIterator > SplinefColor4fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputSplinefColor4fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputSplinefColor4fPlugIterator; +[[deprecated("Use `SplineffPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveSplineffPlugIterator; +[[deprecated("Use `SplineffPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputSplineffPlugIterator; +[[deprecated("Use `SplineffPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputSplineffPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveSplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputSplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor3fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputSplinefColor3fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveSplinefColor4fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputSplinefColor4fPlugIterator; +[[deprecated("Use `SplinefColor4fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputSplinefColor4fPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/Spreadsheet.h b/include/Gaffer/Spreadsheet.h index f76519d9e72..eef2156db47 100644 --- a/include/Gaffer/Spreadsheet.h +++ b/include/Gaffer/Spreadsheet.h @@ -76,7 +76,7 @@ class GAFFER_API Spreadsheet : public ComputeNode public : RowsPlug( const std::string &name = defaultName(), Direction direction = In, unsigned flags = Default ); - virtual ~RowsPlug(); + ~RowsPlug() override; GAFFER_PLUG_DECLARE_TYPE( Gaffer::Spreadsheet::RowsPlug, Gaffer::SpreadsheetRowsPlugTypeId, Gaffer::ValuePlug ); diff --git a/include/Gaffer/StringPlug.h b/include/Gaffer/StringPlug.h index b9893366fd1..edbc475da04 100644 --- a/include/Gaffer/StringPlug.h +++ b/include/Gaffer/StringPlug.h @@ -129,12 +129,17 @@ class GAFFER_API StringPlug : public ValuePlug IE_CORE_DECLAREPTR( StringPlug ); -/// \deprecated Use StringPlug::Iterator etc instead +[[deprecated("Use `StringPlug::Iterator` instead")]] typedef FilteredChildIterator > StringPlugIterator; +[[deprecated("Use `StringPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputStringPlugIterator; +[[deprecated("Use `StringPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputStringPlugIterator; +[[deprecated("Use `StringPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveStringPlugIterator; +[[deprecated("Use `StringPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputStringPlugIterator; +[[deprecated("Use `StringPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputStringPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/TimeWarp.h b/include/Gaffer/TimeWarp.h index a5c6e92ecbd..33eccca931c 100644 --- a/include/Gaffer/TimeWarp.h +++ b/include/Gaffer/TimeWarp.h @@ -62,7 +62,7 @@ class IECORE_EXPORT TimeWarp : public ContextProcessor protected : bool affectsContext( const Plug *input ) const override; - void processContext( Context::EditableScope &context ) const override; + void processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const override; private : diff --git a/include/Gaffer/Transform2DPlug.h b/include/Gaffer/Transform2DPlug.h index a996007b905..715535971eb 100644 --- a/include/Gaffer/Transform2DPlug.h +++ b/include/Gaffer/Transform2DPlug.h @@ -81,12 +81,17 @@ class GAFFER_API Transform2DPlug : public ValuePlug IE_CORE_DECLAREPTR( Transform2DPlug ); -/// \deprecated Use Transform2DPlug::Iterator etc instead +[[deprecated("Use `Transform2DPlug::Iterator` instead")]] typedef FilteredChildIterator > Transform2DPlugIterator; +[[deprecated("Use `Transform2DPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputTransform2DPlugIterator; +[[deprecated("Use `Transform2DPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputTransform2DPlugIterator; +[[deprecated("Use `Transform2DPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveTransform2DPlugPlugIterator; +[[deprecated("Use `Transform2DPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputTransform2DPlugPlugIterator; +[[deprecated("Use `Transform2DPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputTransform2DPlugPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/TransformPlug.h b/include/Gaffer/TransformPlug.h index 52f718341c2..4462678a6d7 100644 --- a/include/Gaffer/TransformPlug.h +++ b/include/Gaffer/TransformPlug.h @@ -83,12 +83,17 @@ class GAFFER_API TransformPlug : public ValuePlug IE_CORE_DECLAREPTR( TransformPlug ); -/// \deprecated Use TransformPlug::Iterator etc instead +[[deprecated("Use `TransformPlug::Iterator` instead")]] typedef FilteredChildIterator > TransformPlugIterator; +[[deprecated("Use `TransformPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputTransformPlugIterator; +[[deprecated("Use `TransformPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputTransformPlugIterator; +[[deprecated("Use `TransformPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveTransformPlugIterator; +[[deprecated("Use `TransformPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputTransformPlugIterator; +[[deprecated("Use `TransformPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputTransformPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/TypedObjectPlug.h b/include/Gaffer/TypedObjectPlug.h index 35de5618ffc..912e3ddbcfd 100644 --- a/include/Gaffer/TypedObjectPlug.h +++ b/include/Gaffer/TypedObjectPlug.h @@ -172,93 +172,179 @@ IE_CORE_DECLAREPTR( CompoundObjectPlug ); IE_CORE_DECLAREPTR( AtomicCompoundDataPlug ); IE_CORE_DECLAREPTR( PathMatcherDataPlug ); -/// \deprecated Use ObjectPlug::Iterator etc instead +[[deprecated("Use `ObjectPlug::Iterator` instead")]] typedef FilteredChildIterator > ObjectPlugIterator; +[[deprecated("Use `ObjectPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputObjectPlugIterator; +[[deprecated("Use `ObjectPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputObjectPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > BoolVectorDataPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBoolVectorDataPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputBoolVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > IntVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputIntVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputIntVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > FloatVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputFloatVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputFloatVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > StringVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputStringVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > InternedStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputInternedStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputInternedStringVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > V2iVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV2iVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV2iVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > V3fVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputV3fVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputV3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > Color3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputColor3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputColor3fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > M44fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputM44fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputM44fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::Iterator` instead")]] typedef FilteredChildIterator > M33fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputM33fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputM33fVectorDataPlugIterator; +[[deprecated("Use `ObjectVectorPlug::Iterator` instead")]] typedef FilteredChildIterator > ObjectVectorPlugIterator; +[[deprecated("Use `ObjectVectorPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputObjectVectorPlugIterator; +[[deprecated("Use `ObjectVectorPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputObjectVectorPlugIterator; +[[deprecated("Use `CompoundObjectPlug::Iterator` instead")]] typedef FilteredChildIterator > CompoundObjectPlugIterator; +[[deprecated("Use `CompoundObjectPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputCompoundObjectPlugIterator; +[[deprecated("Use `CompoundObjectPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputCompoundObjectPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::Iterator` instead")]] typedef FilteredChildIterator > AtomicCompoundDataPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputAtomicCompoundDataPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputAtomicCompoundDataPlugIterator; +[[deprecated("Use `ObjectPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveObjectPlugIterator; +[[deprecated("Use `ObjectPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputObjectPlugIterator; +[[deprecated("Use `ObjectPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputObjectPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBoolVectorDataPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBoolVectorDataPlugIterator; +[[deprecated("Use `BoolVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBoolVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveIntVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputIntVectorDataPlugIterator; +[[deprecated("Use `IntVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputIntVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveFloatVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputFloatVectorDataPlugIterator; +[[deprecated("Use `FloatVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputFloatVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveStringVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputStringVectorDataPlugIterator; +[[deprecated("Use `StringVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInternedStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputInternedStringVectorDataPlugIterator; +[[deprecated("Use `InternedStringVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputInternedStringVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV2iVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV2iVectorDataPlugIterator; +[[deprecated("Use `V2iVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV2iVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveV3fVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputV3fVectorDataPlugIterator; +[[deprecated("Use `V3fVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputV3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveColor3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputColor3fVectorDataPlugIterator; +[[deprecated("Use `Color3fVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputColor3fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveM44fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputM44fVectorDataPlugIterator; +[[deprecated("Use `M44fVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputM44fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveM33fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputM33fVectorDataPlugIterator; +[[deprecated("Use `M33fVectorDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputM33fVectorDataPlugIterator; +[[deprecated("Use `ObjectVectorPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveObjectVectorPlugIterator; +[[deprecated("Use `ObjectVectorPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputObjectVectorPlugIterator; +[[deprecated("Use `ObjectVectorPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputObjectVectorPlugIterator; +[[deprecated("Use `CompoundObjectPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveCompoundObjectPlugIterator; +[[deprecated("Use `CompoundObjectPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputCompoundObjectPlugIterator; +[[deprecated("Use `CompoundObjectPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputCompoundObjectPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveAtomicCompoundDataPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputAtomicCompoundDataPlugIterator; +[[deprecated("Use `AtomicCompoundDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputAtomicCompoundDataPlugIterator; +[[deprecated("Use `PathMatcherDataPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursivePathMatcherDataPlugIterator; +[[deprecated("Use `PathMatcherDataPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputPathMatcherDataPlugIterator; +[[deprecated("Use `PathMatcherDataPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputPathMatcherDataPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/TypedPlug.h b/include/Gaffer/TypedPlug.h index 7fe4e90ceff..f119a347539 100644 --- a/include/Gaffer/TypedPlug.h +++ b/include/Gaffer/TypedPlug.h @@ -107,42 +107,77 @@ IE_CORE_DECLAREPTR( AtomicBox2fPlug ); IE_CORE_DECLAREPTR( AtomicBox3fPlug ); IE_CORE_DECLAREPTR( AtomicBox2iPlug ); -/// \deprecated Use BoolPlug::Iterator etc instead +[[deprecated("Use `BoolPlug::Iterator` instead")]] typedef FilteredChildIterator > BoolPlugIterator; +[[deprecated("Use `BoolPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputBoolPlugIterator; +[[deprecated("Use `BoolPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputBoolPlugIterator; +[[deprecated("Use `M33fPlug::Iterator` instead")]] typedef FilteredChildIterator > M33fPlugIterator; +[[deprecated("Use `M33fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputM33fPlugIterator; +[[deprecated("Use `M33fPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputM33fPlugIterator; +[[deprecated("Use `M44fPlug::Iterator` instead")]] typedef FilteredChildIterator > M44fPlugIterator; +[[deprecated("Use `M44fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputM44fPlugIterator; +[[deprecated("Use `M44fPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputM44fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::Iterator` instead")]] typedef FilteredChildIterator > AtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputAtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputAtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::Iterator` instead")]] typedef FilteredChildIterator > AtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputAtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputAtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::Iterator` instead")]] typedef FilteredChildIterator > AtomicBox2iPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::InputIterator` instead")]] typedef FilteredChildIterator > InputAtomicBox2iPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::Iterator` instead")]] typedef FilteredChildIterator > OutputAtomicBox2iPlugIterator; +[[deprecated("Use `BoolPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveBoolPlugIterator; +[[deprecated("Use `BoolPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputBoolPlugIterator; +[[deprecated("Use `BoolPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputBoolPlugIterator; +[[deprecated("Use `M33fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveM33fPlugIterator; +[[deprecated("Use `M33fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputM33fPlugIterator; +[[deprecated("Use `M33fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputM33fPlugIterator; +[[deprecated("Use `M44fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveM44fPlugIterator; +[[deprecated("Use `M44fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputM44fPlugIterator; +[[deprecated("Use `M44fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputM44fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveAtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputAtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox2fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputAtomicBox2fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveAtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputAtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox3fPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputAtomicBox3fPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveAtomicBox2iPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputAtomicBox2iPlugIterator; +[[deprecated("Use `AtomicBox2iPlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputAtomicBox2iPlugIterator; } // namespace Gaffer diff --git a/include/Gaffer/ValuePlug.h b/include/Gaffer/ValuePlug.h index b4002ef1973..467092e23b5 100644 --- a/include/Gaffer/ValuePlug.h +++ b/include/Gaffer/ValuePlug.h @@ -287,12 +287,17 @@ class GAFFER_API ValuePlug : public Plug IE_CORE_DECLAREPTR( ValuePlug ) -/// \deprecated Use ValuePlug::Iterator etc instead +[[deprecated("Use `ValuePlug::Iterator` instead")]] typedef FilteredChildIterator > ValuePlugIterator; +[[deprecated("Use `ValuePlug::InputIterator` instead")]] typedef FilteredChildIterator > InputValuePlugIterator; +[[deprecated("Use `ValuePlug::OutputIterator` instead")]] typedef FilteredChildIterator > OutputValuePlugIterator; +[[deprecated("Use `ValuePlug::RecursiveIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveValuePlugIterator; +[[deprecated("Use `ValuePlug::RecursiveInputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveInputValuePlugIterator; +[[deprecated("Use `ValuePlug::RecursiveOutputIterator` instead")]] typedef FilteredRecursiveChildIterator, PlugPredicate<> > RecursiveOutputValuePlugIterator; } // namespace Gaffer diff --git a/include/GafferBindings/MetadataBinding.h b/include/GafferBindings/MetadataBinding.h index e6ba89a40f2..ddc442dfa55 100644 --- a/include/GafferBindings/MetadataBinding.h +++ b/include/GafferBindings/MetadataBinding.h @@ -38,14 +38,13 @@ #define GAFFERBINDINGS_METADATABINDING_H #include "GafferBindings/Export.h" +#include "GafferBindings/Serialisation.h" -#include "Gaffer/Node.h" namespace GafferBindings { -GAFFERBINDINGS_API void metadataModuleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules ); -GAFFERBINDINGS_API std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, const std::string &identifier ); +GAFFERBINDINGS_API std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ); } // namespace GafferBindings diff --git a/include/GafferBindings/NodeBinding.h b/include/GafferBindings/NodeBinding.h index c0ff8fe1751..0169eeff3dc 100644 --- a/include/GafferBindings/NodeBinding.h +++ b/include/GafferBindings/NodeBinding.h @@ -149,7 +149,7 @@ class GAFFERBINDINGS_API NodeSerialiser : public Serialisation::Serialiser void moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const override; /// Implemented to serialise per-instance metadata. - std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override; + std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override; /// Implemented so that only plugs are serialised - child nodes are expected to /// be a part of the implementation of the node rather than something the user /// has created themselves. diff --git a/include/GafferBindings/PlugBinding.h b/include/GafferBindings/PlugBinding.h index 01bfa3ddd25..f6e044e8f0f 100644 --- a/include/GafferBindings/PlugBinding.h +++ b/include/GafferBindings/PlugBinding.h @@ -164,8 +164,8 @@ class GAFFERBINDINGS_API PlugSerialiser : public Serialisation::Serialiser public : void moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const override; - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override; - std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override; + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override; + std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override; bool childNeedsSerialisation( const Gaffer::GraphComponent *child, const Serialisation &serialisation ) const override; bool childNeedsConstruction( const Gaffer::GraphComponent *child, const Serialisation &serialisation ) const override; diff --git a/include/GafferBindings/Serialisation.h b/include/GafferBindings/Serialisation.h index 941113fa0bc..0213d063051 100644 --- a/include/GafferBindings/Serialisation.h +++ b/include/GafferBindings/Serialisation.h @@ -49,7 +49,7 @@ namespace GafferBindings { -class GAFFERBINDINGS_API Serialisation +class GAFFERBINDINGS_API Serialisation : boost::noncopyable { public : @@ -74,13 +74,16 @@ class GAFFERBINDINGS_API Serialisation /// necessary. std::string childIdentifier( const std::string &parentIdentifier, Gaffer::GraphComponent::ChildIterator child ) const; + /// Ensures that `import moduleName` is included in the result. + void addModule( const std::string &moduleName ); + /// Returns the result of the serialisation. std::string result() const; /// Convenience function to return the name of the module where object is defined. static std::string modulePath( const IECore::RefCounted *object ); /// As above, but returns the empty string for built in python types. - static std::string modulePath( boost::python::object &object ); + static std::string modulePath( const boost::python::object &object ); /// Convenience function to return the name of the class which object is an instance of. /// \note Prior to Python 3.3 there is no way to automatically obtain a qualified name for /// a nested class (see http://www.python.org/dev/peps/pep-3155). In the meantime, @@ -90,7 +93,7 @@ class GAFFERBINDINGS_API Serialisation /// Convenience function to return the name of the class which object is an instance of. /// If object is a type object rather than an instance, then the path for the type /// object itself is returned. - static std::string classPath( boost::python::object &object ); + static std::string classPath( const boost::python::object &object ); /// Encodes any IECore::Object into a base64 encoded string. /// \todo Perhaps these would be useful as `IECore::ObjectAlgo`? @@ -100,6 +103,16 @@ class GAFFERBINDINGS_API Serialisation /// The Serialiser class may be implemented differently for specific types to customise /// their serialisation. + /// + /// \todo This was initially conceived as a read-only API, where Serialisation + /// made requests of Serialiser and used the results. That has proved to be a bit + /// awkward and slow as each of the virtual methods often do duplicate work such as + /// converting `graphComponent` to Python. The addition of `Serialisation::addModule()` + /// marked a departure from this design, allowing Serialisers to modify the serialisation + /// directly. It may make sense to continue in this direction by reducing the methods + /// on Serialiser and adding to the methods on Serialisation. The logical conclusion + /// may be a single `Serialiser::serialise()` method and various `Serialisation::add*()` + /// methods. class Serialiser : public IECore::RefCounted { @@ -107,24 +120,26 @@ class GAFFERBINDINGS_API Serialisation IE_CORE_DECLAREMEMBERPTR( Serialiser ); - /// Should be implemented to insert the names of any modules the serialiser will need + /// May be implemented to insert the names of any modules the serialiser will need /// into the modules set. The default implementation returns modulePath( graphComponent ). + /// > Note : It is often more convenient to call `serialisation.addModule()` from one of + /// > the other virtual methods. virtual void moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const; /// Should be implemented to return a string which when executed will reconstruct the specified object. /// The default implementation uses repr(). - virtual std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const; + virtual std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const; /// May be implemented to return a string which will be executed immediately after the object has been constructed and /// parented. identifier is the name of a variable which refers to the object. The Serialisation may be used to query /// the identifiers for other objects, but note that at this stage those objects may not have been constructed so it /// is not safe to use them directly. Default implementation returns the empty string. - virtual std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const; + virtual std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const; /// May be implemented to return a string which will be executed once all objects have been constructed and parented. /// At this point it is possible to request the identifiers of other objects via the Serialisation and refer to them in the result. /// Typically this would be used for forming connections between plugs. The default implementation returns the empty string. - virtual std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const; + virtual std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const; /// May be implemented to return a string to be executed after all the postHierarchy strings. This /// can be used to perform a final setup step. The default implementation returns an empty string. - virtual std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const; + virtual std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const; /// May be implemented to say whether or not the child needs to be serialised. The default /// implementation returns true. virtual bool childNeedsSerialisation( const Gaffer::GraphComponent *child, const Serialisation &serialisation ) const; diff --git a/include/GafferBindings/SerialisationBinding.h b/include/GafferBindings/SerialisationBinding.h index b6b22b2102f..43ae611ef26 100644 --- a/include/GafferBindings/SerialisationBinding.h +++ b/include/GafferBindings/SerialisationBinding.h @@ -79,7 +79,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper boost::python::object f = this->methodOverride( "moduleDependencies" ); if( f ) { - boost::python::object mo = f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), serialisation ); + boost::python::object mo = f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), boost::ref( serialisation ) ); std::vector mv; boost::python::container_utils::extend_container( mv, mo ); modules.insert( mv.begin(), mv.end() ); @@ -94,7 +94,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper WrappedType::moduleDependencies( graphComponent, modules, serialisation ); } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { if( this->isSubclassed() ) { @@ -105,7 +105,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper if( f ) { return boost::python::extract( - f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), serialisation ) + f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), boost::ref( serialisation ) ) ); } } @@ -117,7 +117,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper return WrappedType::constructor( graphComponent, serialisation ); } - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { if( this->isSubclassed() ) { @@ -128,7 +128,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper if( f ) { return boost::python::extract( - f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, serialisation ) + f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, boost::ref( serialisation ) ) ); } } @@ -140,7 +140,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper return WrappedType::postConstructor( graphComponent, identifier, serialisation ); } - std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { if( this->isSubclassed() ) { @@ -151,7 +151,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper if( f ) { return boost::python::extract( - f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, serialisation ) + f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, boost::ref( serialisation ) ) ); } } @@ -163,7 +163,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper return WrappedType::postHierarchy( graphComponent, identifier, serialisation ); } - std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { if( this->isSubclassed() ) { @@ -174,7 +174,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper if( f ) { return boost::python::extract( - f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, serialisation ) + f( Gaffer::GraphComponentPtr( const_cast( graphComponent ) ), identifier, boost::ref( serialisation ) ) ); } } @@ -196,7 +196,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper boost::python::object f = this->methodOverride( "childNeedsSerialisation" ); if( f ) { - return f( Gaffer::GraphComponentPtr( const_cast( child ) ), serialisation ); + return f( Gaffer::GraphComponentPtr( const_cast( child ) ), boost::ref( serialisation ) ); } } catch( const boost::python::error_already_set &e ) @@ -217,7 +217,7 @@ class SerialiserWrapper : public IECorePython::RefCountedWrapper boost::python::object f = this->methodOverride( "childNeedsConstruction" ); if( f ) { - return f( Gaffer::GraphComponentPtr( const_cast( child ) ), serialisation ); + return f( Gaffer::GraphComponentPtr( const_cast( child ) ), boost::ref( serialisation ) ); } } catch( const boost::python::error_already_set &e ) diff --git a/include/GafferBindings/SerialisationBinding.inl b/include/GafferBindings/SerialisationBinding.inl index 200890fa762..c2e9cb036f1 100644 --- a/include/GafferBindings/SerialisationBinding.inl +++ b/include/GafferBindings/SerialisationBinding.inl @@ -115,7 +115,7 @@ boost::python::object moduleDependencies( const T *self, const Gaffer::GraphComp } template -std::string constructor( const T *self, const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) +std::string constructor( const T *self, const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) { if( dynamic_cast( self ) ) { @@ -128,7 +128,7 @@ std::string constructor( const T *self, const Gaffer::GraphComponent *graphCompo } template -std::string postConstructor( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) +std::string postConstructor( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) { if( dynamic_cast( self ) ) { @@ -141,7 +141,7 @@ std::string postConstructor( const T *self, const Gaffer::GraphComponent *graphC } template -std::string postHierarchy( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) +std::string postHierarchy( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) { if( dynamic_cast( self ) ) { @@ -154,7 +154,7 @@ std::string postHierarchy( const T *self, const Gaffer::GraphComponent *graphCom } template -std::string postScript( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) +std::string postScript( const T *self, const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) { if( dynamic_cast( self ) ) { diff --git a/include/GafferBindings/SignalBinding.inl b/include/GafferBindings/SignalBinding.inl index dec3b2ca227..0b28ffab39a 100644 --- a/include/GafferBindings/SignalBinding.inl +++ b/include/GafferBindings/SignalBinding.inl @@ -51,305 +51,42 @@ namespace GafferBindings namespace Detail { -template -struct DefaultSignalCallerBase; - -template -struct DefaultSignalCallerBase<0, Signal> -{ - static typename Signal::result_type call( Signal &s ) - { - IECorePython::ScopedGILRelease gilRelease; - return s(); - } -}; - -template -struct DefaultSignalCallerBase<1, Signal> -{ -#if BOOST_VERSION < 103900 - static typename Signal::result_type call( Signal &s, typename Signal::arg2_type a1 ) -#else - static typename Signal::result_type call( Signal &s, typename Signal::arg1_type a1 ) -#endif - { - IECorePython::ScopedGILRelease gilRelease; - return s( a1 ); - } -}; - -template -struct DefaultSignalCallerBase<2, Signal> -{ -#if BOOST_VERSION < 103900 - static typename Signal::result_type call( Signal &s, typename Signal::arg2_type a1, typename Signal::arg3_type a2 ) -#else - static typename Signal::result_type call( Signal &s, typename Signal::arg1_type a1, typename Signal::arg2_type a2 ) -#endif - { - IECorePython::ScopedGILRelease gilRelease; - return s( a1, a2 ); - } -}; - -template -struct DefaultSignalCallerBase<3, Signal> -{ -#if BOOST_VERSION < 103900 - static typename Signal::result_type call( Signal &s, typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3 ) -#else - static typename Signal::result_type call( Signal &s, typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3 ) -#endif - { - IECorePython::ScopedGILRelease gilRelease; - return s( a1, a2, a3 ); - } -}; - -template -struct DefaultSignalCallerBase<4, Signal> -{ -#if BOOST_VERSION < 103900 - static typename Signal::result_type call( Signal &s, typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3, typename Signal::arg5_type a4 ) -#else - static typename Signal::result_type call( Signal &s, typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3, typename Signal::arg4_type a4 ) -#endif - { - IECorePython::ScopedGILRelease gilRelease; - return s( a1, a2, a3, a4 ); - } -}; - -template -struct DefaultSlotCallerBase; - -template -struct DefaultSlotCallerBase<0, Signal> -{ - typename Signal::slot_result_type operator()( boost::python::object slot ) - { - return boost::python::extract( slot() )(); - } -}; - -template -struct DefaultSlotCallerBase<1, Signal> -{ -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg2_type a1 ) -#else - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg1_type a1 ) -#endif - { - return boost::python::extract( slot( a1 ) )(); - } -}; - -template -struct DefaultSlotCallerBase<2, Signal> -{ -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg2_type a1, typename Signal::arg3_type a2 ) -#else - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg1_type a1, typename Signal::arg2_type a2 ) -#endif - { - return boost::python::extract( slot( a1, a2 ) )(); - } -}; - -template -struct DefaultSlotCallerBase<3, Signal> -{ -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3 ) -#else - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3 ) -#endif - { - return boost::python::extract( slot( a1, a2, a3 ) )(); - } -}; +template +struct Slot; -template -struct DefaultSlotCallerBase<4, Signal> +template +struct Slot, Caller> { -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3, typename Signal::arg5_type a4 ) -#else - typename Signal::slot_result_type operator()( boost::python::object slot, typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3, typename Signal::arg4_type a4 ) -#endif - { - return boost::python::extract( slot( a1, a2, a3, a4 ) )(); - } -}; - -template -struct SlotBase; -template -struct SlotBase<0, Signal, Caller> -{ - SlotBase( boost::python::object slot ) + Slot( boost::python::object slot ) : m_slot( boost::python::borrowed( slot.ptr() ) ) { - } - ~SlotBase() - { - IECorePython::ScopedGILLock gilLock; - m_slot.reset(); - } - typename Signal::slot_result_type operator()() - { - IECorePython::ScopedGILLock gilLock; - try - { - return Caller()( boost::python::object( m_slot ) ); - } - catch( const boost::python::error_already_set& e ) - { - IECorePython::ExceptionAlgo::translatePythonException(); - } - return typename Signal::slot_result_type(); - } - boost::python::handle m_slot; -}; -template -struct SlotBase<1, Signal, Caller> -{ - SlotBase( boost::python::object slot ) - : m_slot( boost::python::borrowed( slot.ptr() ) ) - { } - ~SlotBase() - { - IECorePython::ScopedGILLock gilLock; - m_slot.reset(); - } -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( typename Signal::arg2_type a1 ) -#else - typename Signal::slot_result_type operator()( typename Signal::arg1_type a1 ) -#endif - { - IECorePython::ScopedGILLock gilLock; - try - { - return Caller()( boost::python::object( m_slot ), a1 ); - } - catch( const boost::python::error_already_set& e ) - { - IECorePython::ExceptionAlgo::translatePythonException(); - } - return typename Signal::slot_result_type(); - } - boost::python::handle m_slot; -}; -template -struct SlotBase<2, Signal, Caller> -{ - SlotBase( boost::python::object slot ) - : m_slot( boost::python::borrowed( slot.ptr() ) ) - { - } - ~SlotBase() + ~Slot() { IECorePython::ScopedGILLock gilLock; m_slot.reset(); } -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( typename Signal::arg2_type a1, typename Signal::arg3_type a2 ) -#else - typename Signal::slot_result_type operator()( typename Signal::arg1_type a1, typename Signal::arg2_type a2 ) -#endif - { - IECorePython::ScopedGILLock gilLock; - try - { - return Caller()( boost::python::object( m_slot ), a1, a2 ); - } - catch( const boost::python::error_already_set& e ) - { - IECorePython::ExceptionAlgo::translatePythonException(); - } - return typename Signal::slot_result_type(); - } - boost::python::handle m_slot; -}; -template -struct SlotBase<3, Signal, Caller> -{ - SlotBase( boost::python::object slot ) - : m_slot( boost::python::borrowed( slot.ptr() ) ) - { - } - ~SlotBase() - { - IECorePython::ScopedGILLock gilLock; - m_slot.reset(); - } -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3 ) -#else - typename Signal::slot_result_type operator()( typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3 ) -#endif - { - IECorePython::ScopedGILLock gilLock; - try - { - return Caller()( boost::python::object( m_slot ), a1, a2, a3 ); - } - catch( const boost::python::error_already_set& e ) - { - IECorePython::ExceptionAlgo::translatePythonException(); - } - return typename Signal::slot_result_type(); - } - boost::python::handle m_slot; -}; + using Signal = boost::signal; -template -struct SlotBase<4, Signal, Caller> -{ - SlotBase( boost::python::object slot ) - : m_slot( boost::python::borrowed( slot.ptr() ) ) - { - } - ~SlotBase() - { - IECorePython::ScopedGILLock gilLock; - m_slot.reset(); - } -#if BOOST_VERSION < 103900 - typename Signal::slot_result_type operator()( typename Signal::arg2_type a1, typename Signal::arg3_type a2, typename Signal::arg4_type a3, typename Signal::arg5_type a4 ) -#else - typename Signal::slot_result_type operator()( typename Signal::arg1_type a1, typename Signal::arg2_type a2, typename Signal::arg3_type a3, typename Signal::arg4_type a4 ) -#endif + typename Signal::slot_result_type operator()( Args&&... args ) { IECorePython::ScopedGILLock gilLock; try { - return Caller()( boost::python::object( m_slot ), a1, a2, a3, a4 ); + return Caller()( boost::python::object( m_slot ), std::forward( args )... ); } - catch( const boost::python::error_already_set& e ) + catch( const boost::python::error_already_set &e ) { IECorePython::ExceptionAlgo::translatePythonException(); } - return typename Signal::slot_result_type(); } + boost::python::handle m_slot; -}; -template -struct Slot : public SlotBase -{ - Slot( boost::python::object slot ) - : SlotBase( slot ) - { - } }; // Ideally we would bind `boost::signals::trackable` to Python @@ -400,14 +137,36 @@ boost::python::object connectInGroup( Signal &s, int group, boost::python::objec } // namespace Detail template -struct DefaultSignalCaller : public Detail::DefaultSignalCallerBase +struct DefaultSignalCaller; + +template +struct DefaultSignalCaller> { + using Signal = boost::signal; + + static Result call( Signal &s, Args... args ) + { + IECorePython::ScopedGILRelease gilRelease; + return s( args... ); + } + }; template -struct DefaultSlotCaller : public Detail::DefaultSlotCallerBase +struct DefaultSlotCaller; + +template +struct DefaultSlotCaller> { + + using Signal = boost::signal; + + typename Signal::slot_result_type operator()( boost::python::object slot, Args&&... args ) + { + return boost::python::extract( slot( std::forward( args )... ) )(); + } + }; template diff --git a/include/GafferBindings/ValuePlugBinding.h b/include/GafferBindings/ValuePlugBinding.h index 8efbcde8be3..1bb21fd053e 100644 --- a/include/GafferBindings/ValuePlugBinding.h +++ b/include/GafferBindings/ValuePlugBinding.h @@ -57,13 +57,13 @@ class GAFFERBINDINGS_API ValuePlugSerialiser : public PlugSerialiser public : - void moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const override; - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override; - std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override; + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override; + std::string postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override; - static std::string repr( const Gaffer::ValuePlug *plug, const std::string &extraArguments = "", const Serialisation *serialisation = nullptr ); + static std::string repr( const Gaffer::ValuePlug *plug, const std::string &extraArguments = "", Serialisation *serialisation = nullptr ); /// Returns a serialisation suitable for use in a `setValue()` or `setDefaultValue()` call. - static std::string valueRepr( const boost::python::object &value ); + /// If `serialisation` is provided then `serialisation->addModule()` will be called appropriately. + static std::string valueRepr( const boost::python::object &value, Serialisation *serialisation = nullptr ); }; diff --git a/include/GafferDispatch/TaskNode.h b/include/GafferDispatch/TaskNode.h index 8425adbd024..0a65adfa6b0 100644 --- a/include/GafferDispatch/TaskNode.h +++ b/include/GafferDispatch/TaskNode.h @@ -160,6 +160,7 @@ class GAFFERDISPATCH_API TaskNode : public Gaffer::DependencyNode }; + [[deprecated("Use `TaskPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > TaskPlugIterator; /// Input plugs to which upstream tasks may be connected to cause them diff --git a/include/GafferImage/AtomicFormatPlug.h b/include/GafferImage/AtomicFormatPlug.h index 691b5fe0584..4017743a4ca 100644 --- a/include/GafferImage/AtomicFormatPlug.h +++ b/include/GafferImage/AtomicFormatPlug.h @@ -49,12 +49,18 @@ typedef Gaffer::TypedPlug AtomicFormatPlug; IE_CORE_DECLAREPTR( AtomicFormatPlug ); +[[deprecated("Use `AtomicFormatPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > AtomicFormatPlugIterator; +[[deprecated("Use `AtomicFormatPlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputAtomicFormatPlugIterator; +[[deprecated("Use `AtomicFormatPlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputAtomicFormatPlugIterator; +[[deprecated("Use `AtomicFormatPlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveAtomicFormatPlugIterator; +[[deprecated("Use `AtomicFormatPlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputAtomicFormatPlugIterator; +[[deprecated("Use `AtomicFormatPlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputAtomicFormatPlugIterator; } // namespace GafferImage diff --git a/include/GafferImage/Catalogue.h b/include/GafferImage/Catalogue.h index a41ed993baf..2638bf8c39f 100644 --- a/include/GafferImage/Catalogue.h +++ b/include/GafferImage/Catalogue.h @@ -108,6 +108,7 @@ class GAFFERIMAGE_API Catalogue : public ImageNode }; + [[deprecated("Use `Image::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ImageIterator; Gaffer::Plug *imagesPlug(); diff --git a/include/GafferImage/Display.h b/include/GafferImage/Display.h index 35e6e01fa74..768f2f97002 100644 --- a/include/GafferImage/Display.h +++ b/include/GafferImage/Display.h @@ -123,7 +123,9 @@ class GAFFERIMAGE_API Display : public ImageNode IE_CORE_DECLAREPTR( Display ); +[[deprecated("Use `Display::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > DisplayIterator; +[[deprecated("Use `Display::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveDisplayIterator; } // namespace GafferImage diff --git a/include/GafferImage/FilterAlgo.inl b/include/GafferImage/FilterAlgo.inl index 91d4dceb8f7..3cd7ccde48f 100644 --- a/include/GafferImage/FilterAlgo.inl +++ b/include/GafferImage/FilterAlgo.inl @@ -76,5 +76,3 @@ Imath::V2f FilterAlgo::derivativesToAxisAligned( const Imath::V2f &p, const Imat } } - - diff --git a/include/GafferImage/Format.h b/include/GafferImage/Format.h index 85ffcdda4c7..acf1e639e57 100644 --- a/include/GafferImage/Format.h +++ b/include/GafferImage/Format.h @@ -40,6 +40,7 @@ #include "GafferImage/Export.h" #include "IECore/Export.h" +#include "IECore/MurmurHash.h" IECORE_PUSH_DEFAULT_VISIBILITY #include "OpenEXR/ImathBox.h" @@ -130,6 +131,8 @@ class GAFFERIMAGE_API Format /// where possible. Note that this is unrelated to Format::name(). GAFFERIMAGE_API std::ostream & operator << ( std::ostream &os, const GafferImage::Format &format ); +inline void murmurHashAppend( IECore::MurmurHash &h, const GafferImage::Format &data ); + } // namespace GafferImage #include "GafferImage/Format.inl" diff --git a/include/GafferImage/Format.inl b/include/GafferImage/Format.inl index 0503e060528..229da913061 100644 --- a/include/GafferImage/Format.inl +++ b/include/GafferImage/Format.inl @@ -161,6 +161,13 @@ inline Imath::Box2i Format::toEXRSpace( const Imath::Box2i &internalSpace ) cons ); } +inline void murmurHashAppend( IECore::MurmurHash &h, const GafferImage::Format &data ) +{ + h.append( data.getDisplayWindow() ); + h.append( data.getPixelAspect() ); +} + } // namespace GafferImage + #endif // GAFFERIMAGE_FORMAT_INL diff --git a/include/GafferImage/FormatPlug.h b/include/GafferImage/FormatPlug.h index 01817f6a876..cb5c13e2f5b 100644 --- a/include/GafferImage/FormatPlug.h +++ b/include/GafferImage/FormatPlug.h @@ -135,12 +135,18 @@ class GAFFERIMAGE_API FormatPlug : public Gaffer::ValuePlug IE_CORE_DECLAREPTR( FormatPlug ); +[[deprecated("Use `FormatPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > FormatPlugIterator; +[[deprecated("Use `FormatPlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputFormatPlugIterator; +[[deprecated("Use `FormatPlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputFormatPlugIterator; +[[deprecated("Use `FormatPlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveFormatPlugIterator; +[[deprecated("Use `FormatPlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputFormatPlugIterator; +[[deprecated("Use `FormatPlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputFormatPlugIterator; } // namespace GafferImage diff --git a/include/GafferImage/Grade.h b/include/GafferImage/Grade.h index 03139d058f9..71321ed4233 100644 --- a/include/GafferImage/Grade.h +++ b/include/GafferImage/Grade.h @@ -60,10 +60,6 @@ class GAFFERIMAGE_API Grade : public ChannelDataProcessor GAFFER_NODE_DECLARE_TYPE( GafferImage::Grade, GradeTypeId, ChannelDataProcessor ); - //! @name Plug Accessors - /// Returns a pointer to the node's plugs. - ////////////////////////////////////////////////////////////// - //@{ Gaffer::Color4fPlug *blackPointPlug(); const Gaffer::Color4fPlug *blackPointPlug() const; Gaffer::Color4fPlug *whitePointPlug(); @@ -82,7 +78,6 @@ class GAFFERIMAGE_API Grade : public ChannelDataProcessor const Gaffer::BoolPlug *blackClampPlug() const; Gaffer::BoolPlug *whiteClampPlug(); const Gaffer::BoolPlug *whiteClampPlug() const; - //@} void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; diff --git a/include/GafferImage/ImageAlgo.h b/include/GafferImage/ImageAlgo.h index 6ab42783e09..b0ad3ec18e3 100644 --- a/include/GafferImage/ImageAlgo.h +++ b/include/GafferImage/ImageAlgo.h @@ -102,6 +102,18 @@ inline bool channelExists( const ImagePlug *image, const std::string &channelNam /// Returns true if the specified channel exists in channelNames inline bool channelExists( const std::vector &channelNames, const std::string &channelName ); +/// Default channel names +/// ============================== +/// +/// You can just use your own strings, but it can be convenient to use these + +GAFFERIMAGE_API extern const std::string channelNameA; +GAFFERIMAGE_API extern const std::string channelNameR; +GAFFERIMAGE_API extern const std::string channelNameG; +GAFFERIMAGE_API extern const std::string channelNameB; +GAFFERIMAGE_API extern const std::string channelNameZ; +GAFFERIMAGE_API extern const std::string channelNameZBack; + /// Parallel processing functions /// ============================== /// diff --git a/include/GafferImage/ImageAlgo.inl b/include/GafferImage/ImageAlgo.inl index 2c663667272..4ea62d663e2 100644 --- a/include/GafferImage/ImageAlgo.inl +++ b/include/GafferImage/ImageAlgo.inl @@ -283,7 +283,7 @@ void parallelProcessTiles( const ImagePlug *imagePlug, TileFunctor &&functor, co [ imagePlug, &functor, &threadState ] ( const Imath::V2i &tileOrigin ) { ImagePlug::ChannelDataScope channelDataScope( threadState ); - channelDataScope.setTileOrigin( tileOrigin ); + channelDataScope.setTileOrigin( &tileOrigin ); functor( imagePlug, tileOrigin ); } @@ -322,7 +322,7 @@ void parallelProcessTiles( const ImagePlug *imagePlug, const std::vector > ImagePlugIterator; +[[deprecated("Use `ImagePlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputImagePlugIterator; +[[deprecated("Use `ImagePlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputImagePlugIterator; +[[deprecated("Use `ImagePlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveImagePlugIterator; +[[deprecated("Use `ImagePlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputImagePlugIterator; +[[deprecated("Use `ImagePlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputImagePlugIterator; } // namespace GafferImage diff --git a/include/GafferImage/ImageStats.h b/include/GafferImage/ImageStats.h index 39fcbdac00f..37c13b7940e 100644 --- a/include/GafferImage/ImageStats.h +++ b/include/GafferImage/ImageStats.h @@ -54,7 +54,7 @@ class GAFFERIMAGE_API ImageStats : public Gaffer::ComputeNode public : - ImageStats( const std::string &name=staticTypeName() ); + ImageStats( const std::string &name=defaultName() ); ~ImageStats() override; GAFFER_NODE_DECLARE_TYPE( GafferImage::ImageStats, ImageStatsTypeId, Gaffer::ComputeNode ); diff --git a/include/GafferImage/Shuffle.h b/include/GafferImage/Shuffle.h index 1491600bb86..c9d0e4227a8 100644 --- a/include/GafferImage/Shuffle.h +++ b/include/GafferImage/Shuffle.h @@ -88,6 +88,7 @@ class GAFFERIMAGE_API Shuffle : public ImageProcessor IE_CORE_DECLAREPTR( ChannelPlug ) + [[deprecated("Use `ChannelPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ChannelPlugIterator; Gaffer::ValuePlug *channelsPlug(); diff --git a/include/GafferImageUI/ImageGadget.h b/include/GafferImageUI/ImageGadget.h index 96e7d784712..542f85f8e57 100644 --- a/include/GafferImageUI/ImageGadget.h +++ b/include/GafferImageUI/ImageGadget.h @@ -322,7 +322,9 @@ IE_CORE_DECLAREPTR( ImageGadget ) size_t tbb_hasher( const ImageGadget::TileIndex &tileIndex ); +[[deprecated("Use `ImageGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ImageGadgetIterator; +[[deprecated("Use `ImageGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveImageGadgetIterator; } // namespace GafferImageUI diff --git a/include/GafferImageUI/ImageView.h b/include/GafferImageUI/ImageView.h index f7e16a98ed1..3f58ce80067 100644 --- a/include/GafferImageUI/ImageView.h +++ b/include/GafferImageUI/ImageView.h @@ -43,6 +43,8 @@ #include "GafferUI/View.h" +#include "Gaffer/BoxPlug.h" +#include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/NumericPlug.h" #include "Gaffer/TypedPlug.h" @@ -115,6 +117,33 @@ class GAFFERIMAGEUI_API ImageView : public GafferUI::View static void registeredDisplayTransforms( std::vector &names ); static GafferImage::ImageProcessorPtr createDisplayTransform( const std::string &name ); + class ColorInspectorPlug : public Gaffer::ValuePlug + { + public : + enum class Mode + { + Cursor, + Pixel, + Area + }; + + GAFFER_PLUG_DECLARE_TYPE( ColorInspectorPlug, ColorInspectorPlugTypeId, Gaffer::ValuePlug ); + + ColorInspectorPlug( const std::string &name = defaultName(), Direction direction=In, unsigned flags=Default ); + + Gaffer::IntPlug *modePlug(); + const Gaffer::IntPlug *modePlug() const; + + Gaffer::V2iPlug *pixelPlug(); + const Gaffer::V2iPlug *pixelPlug() const; + + Gaffer::Box2iPlug *areaPlug(); + const Gaffer::Box2iPlug *areaPlug() const; + + bool acceptsChild( const GraphComponent *potentialChild ) const override; + Gaffer::PlugPtr createCounterpart( const std::string &name, Plug::Direction direction ) const override; + }; + protected : /// May be called from a subclass constructor to add a converter diff --git a/include/GafferImageUI/TypeIds.h b/include/GafferImageUI/TypeIds.h index 5b5d6c39f07..006bc98f0c6 100644 --- a/include/GafferImageUI/TypeIds.h +++ b/include/GafferImageUI/TypeIds.h @@ -45,6 +45,10 @@ enum TypeId ImageViewTypeId = 110850, ImageGadgetTypeId = 110851, V2fContextVariableTypeId = 110852, + Box2iContextVariableTypeId = 110853, + ColorInspectorPlugTypeId = 110854, + Box2iGadgetTypeId = 110855, + V2iGadgetTypeId = 110856, LastTypeId = 110899 }; diff --git a/include/GafferOSL/OSLImage.h b/include/GafferOSL/OSLImage.h index 9b9d79e1df3..51d47d29fe6 100644 --- a/include/GafferOSL/OSLImage.h +++ b/include/GafferOSL/OSLImage.h @@ -99,6 +99,13 @@ class GAFFEROSL_API OSLImage : public GafferImage::ImageProcessor Gaffer::ObjectPlug *shadingPlug(); const Gaffer::ObjectPlug *shadingPlug() const; + // Sorted list of affected channels, used to calculate outPlug()->channelNames(), and + // bypass computeChannelData for channels which we don't affect. This can usually be + // evaluated without evaluating the shading, but if closure plugs are present, evaluating + // this will also evaluate shadingPlug() + Gaffer::StringVectorDataPlug *affectedChannelsPlug(); + const Gaffer::StringVectorDataPlug *affectedChannelsPlug() const; + void hashShading( const Gaffer::Context *context, IECore::MurmurHash &h ) const; IECore::ConstCompoundDataPtr computeShading( const Gaffer::Context *context ) const; diff --git a/include/GafferScene/BoundQuery.h b/include/GafferScene/BoundQuery.h new file mode 100644 index 00000000000..aafb57bdf84 --- /dev/null +++ b/include/GafferScene/BoundQuery.h @@ -0,0 +1,103 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERSCENE_BOUNDQUERY_H +#define GAFFERSCENE_BOUNDQUERY_H + +#include "GafferScene/Export.h" +#include "GafferScene/ScenePlug.h" +#include "GafferScene/TypeIds.h" + +#include "Gaffer/BoxPlug.h" +#include "Gaffer/CompoundNumericPlug.h" +#include "Gaffer/ComputeNode.h" +#include "Gaffer/StringPlug.h" +#include "Gaffer/TypedPlug.h" + +#include + +namespace GafferScene +{ + +struct GAFFERSCENE_API BoundQuery : Gaffer::ComputeNode +{ + enum class Space + { + Local = 0x00, + World = 0x01, + Relative = 0x02 + }; + + BoundQuery( std::string const& name = defaultName< BoundQuery >() ); + ~BoundQuery() override; + + GAFFER_NODE_DECLARE_TYPE( GafferScene::BoundQuery, BoundQueryTypeId, Gaffer::ComputeNode ); + + ScenePlug* scenePlug(); + ScenePlug const* scenePlug() const; + Gaffer::StringPlug* locationPlug(); + Gaffer::StringPlug const* locationPlug() const; + Gaffer::IntPlug* spacePlug(); + Gaffer::IntPlug const* spacePlug() const; + Gaffer::StringPlug* relativeLocationPlug(); + Gaffer::StringPlug const* relativeLocationPlug() const; + Gaffer::Box3fPlug* boundPlug(); + Gaffer::Box3fPlug const* boundPlug() const; + Gaffer::V3fPlug* centerPlug(); + Gaffer::V3fPlug const* centerPlug() const; + Gaffer::V3fPlug* sizePlug(); + Gaffer::V3fPlug const* sizePlug() const; + + void affects( Gaffer::Plug const* input, AffectedPlugsContainer& outputs ) const override; + +protected: + + void hash( Gaffer::ValuePlug const* output, Gaffer::Context const* context, IECore::MurmurHash& hash ) const override; + void compute( Gaffer::ValuePlug* output, Gaffer::Context const* context ) const override; + +private: + + Gaffer::AtomicBox3fPlug* internalBoundPlug(); + Gaffer::AtomicBox3fPlug const* internalBoundPlug() const; + + static size_t g_firstPlugIndex; +}; + +IE_CORE_DECLAREPTR( BoundQuery ) + +} // GafferScene + +#endif // GAFFERSCENE_BOUNDQUERY_H diff --git a/include/GafferScene/BranchCreator.h b/include/GafferScene/BranchCreator.h index 10dbfc0bd15..00bd44c4100 100644 --- a/include/GafferScene/BranchCreator.h +++ b/include/GafferScene/BranchCreator.h @@ -56,6 +56,27 @@ IE_CORE_FORWARDDECLARE( StringPlug ) namespace GafferScene { +/// A base class to simplify the process of creating new branches in the scene +/// hierarchy. The source data for each branch is specified by the input +/// locations matched by the filter. By default, the branches are made +/// underneath the source locations, but they can be relocated by using the +/// `destination` plug. We use the following terminology : +/// +/// - `sourcePath` : An input location matched by `filter`. Each source will create +/// exactly one branch. +/// - `destinationPath` : The location where branch will be created in the output scene. +/// This is specified by `destinationPlug()`, and defaults to `sourcePath`. Multiple +/// branches may have the same destination, but this is handled transparently and +/// doesn't affect derived classes. In the case of branches at the same destination +/// having identical names, numeric suffixes are appended automatically to uniquefy them. +/// - `branchPath` : A path to a location within a branch, specified relative to `destinationPath`. +/// The primary responsibility of derived classes is to generate data for `branchPath` from the +/// information provided by `sourcePath`. +/// +/// > Note : The unfortunately-named `parent` plug specifies a `sourcePath` to be used when +/// > no filter is connected. It is a historical artifact from when BranchCreator didn't +/// > support filtering. It remains for backwards compatibility and because it is useful for +/// > simple uses in the Parent node. class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor { @@ -68,6 +89,9 @@ class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor Gaffer::StringPlug *parentPlug(); const Gaffer::StringPlug *parentPlug() const; + Gaffer::StringPlug *destinationPlug(); + const Gaffer::StringPlug *destinationPlug() const; + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; protected : @@ -99,6 +123,7 @@ class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor IECore::ConstPathMatcherDataPtr computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const override; Gaffer::ValuePlug::CachePolicy hashCachePolicy( const Gaffer::ValuePlug *output ) const override; + Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; /// @name Branch evaluation methods /// These must be implemented by derived classes. The hashBranch*() methods must either : @@ -114,95 +139,99 @@ class GAFFERSCENE_API BranchCreator : public FilteredSceneProcessor /// \todo Make all these methods pure virtual. //@{ virtual bool affectsBranchBound( const Gaffer::Plug *input ) const; - virtual void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; - virtual Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; + virtual void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; + virtual Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; virtual bool affectsBranchTransform( const Gaffer::Plug *input ) const; - virtual void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; - virtual Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; + virtual void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; + virtual Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; virtual bool affectsBranchAttributes( const Gaffer::Plug *input ) const; - virtual void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; - virtual IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; + virtual void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; + virtual IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; virtual bool affectsBranchObject( const Gaffer::Plug *input ) const; - /// Called to determine if the parent object is affected. If true, hashBranchObject and computeBranchObject - /// will be called with an empty branchPath when the parentPath is an exact match. The default implementation - /// returns false as most BranchCreators should leave the parent object intact. + /// \deprecated virtual bool processesRootObject() const; - virtual void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; - virtual IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; + virtual void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; + virtual IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; virtual bool affectsBranchChildNames( const Gaffer::Plug *input ) const; - virtual void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; - virtual IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; + virtual void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const = 0; + virtual IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const = 0; virtual bool affectsBranchSetNames( const Gaffer::Plug *input ) const; - virtual void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; - virtual IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const; + virtual void hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + virtual IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const; /// Called to determine if all branches have the same set names. If it returns true, - /// `computeSetNames()` calls `computeBranchSetNames()` just once, with an empty `parentPath`, + /// `computeSetNames()` calls `computeBranchSetNames()` just once, with an empty `sourcePath`, /// rather than having to accumulate all names from all branches. The default implementation /// returns true. virtual bool constantBranchSetNames() const; virtual bool affectsBranchSet( const Gaffer::Plug *input ) const; - virtual void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const; - virtual IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const; + virtual void hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + virtual IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const; //@} - // Computes the relevant parent and branch paths for computing the result - // at the specified path. Returns a PathMatcher::Result to describe where path is - // relative to the parent, as follows : - // - // AncestorMatch - // - // The path is on a branch below the parent, parentPath and branchPath - // are filled in appropriately, and branchPath will not be empty. - // - // ExactMatch - // - // The path is at the parent exactly, parentPath will be filled - // in appropriately and branchPath will be empty. - // - // DescendantMatch - // - // The path is above one or more parents. Neither parentPath nor branchPath - // will be filled in. - // - // NoMatch - // - // The path is a direct pass through from the input - neither - // parentPath nor branchPath will be filled in. + /// \deprecated IECore::PathMatcher::Result parentAndBranchPaths( const ScenePath &path, ScenePath &parentPath, ScenePath &branchPath ) const; private : + IE_CORE_FORWARDDECLARE( BranchesData ); + /// Returns the path specified by `parentPlug()`, only if it is non-empty /// and is valid within the input scene. boost::optional parentPlugPath() const; - /// All the results from `filterPlug()`. - Gaffer::PathMatcherDataPlug *filteredPathsPlug(); - const Gaffer::PathMatcherDataPlug *filteredPathsPlug() const; - - /// All the parent locations at which we need to create a branch. - Gaffer::PathMatcherDataPlug *parentPathsPlug(); - const Gaffer::PathMatcherDataPlug *parentPathsPlug() const; - - /// Used to calculate the name remapping needed to prevent name clashes with - /// the existing scene. Must be evaluated in a context where "scene:path" is - /// one of the parent paths. + /// BranchesData telling us what branches we need to make. + Gaffer::ObjectPlug *branchesPlug(); + const Gaffer::ObjectPlug *branchesPlug() const; + /// Calls `branchesPlug()->getValue()` in a clean context and + /// returns the result. This must be used for all access to + /// `branchesPlug()`. + ConstBranchesDataPtr branches( const Gaffer::Context *context ) const; + + /// Used to calculate the name remapping needed to prevent name clashes + /// with the existing scene. Must be evaluated in a context where + /// "scene:path" is one of the destination paths. This is mapping is + /// computed separately from `branchesPlug()` so that we can delay calls + /// to `hashBranch*()` and `computeBranch*()` till as late as possible. Gaffer::ObjectPlug *mappingPlug(); const Gaffer::ObjectPlug *mappingPlug() const; void hashMapping( const Gaffer::Context *context, IECore::MurmurHash &h ) const; IECore::ConstDataPtr computeMapping( const Gaffer::Context *context ) const; - // Returns the parent paths that should be used to compute a set. If these are empty, - // the input set will be passed through unchanged. - IECore::PathMatcher parentPathsForSet( const IECore::InternedString &setName, const Gaffer::Context *context ) const; - bool affectsParentPathsForSet( const Gaffer::Plug *input ) const; + // Returns `branches()` if it should be used to compute a set, otherwise `nullptr`. + ConstBranchesDataPtr branchesForSet( const IECore::InternedString &setName, const Gaffer::Context *context ) const; + bool affectsBranchesForSet( const Gaffer::Plug *input ) const; + + // Classification that determines how locations are + // processed. + enum LocationType + { + // On a branch, delegated to the `computeBranch*()` methods. + Branch, + // Destination which forms the root for a branch. Several + // source locations may map to the same destination. + Destination, + // As above, but the location does not exist in the input scene. + NewDestination, + // Ancestor of a Destination location. + Ancestor, + // As above, but the location does not exist in the input scene. + NewAncestor, + // Location is unrelated to any branches, and is a direct pass + // through of the input scene. + PassThrough + }; + + // Returns the classification for `path`. If `Branch`, fills in `sourcePath` + // and `branchPath`. If `newChildNames` is passed, then it will be assigned + // the names of any `NewDestination/NewAncestor` children at this location. + LocationType sourceAndBranchPaths( const ScenePath &path, ScenePath &sourcePath, ScenePath &branchPath, IECore::ConstInternedStringVectorDataPtr *newChildNames = nullptr ) const; static size_t g_firstPlugIndex; diff --git a/include/GafferScene/Deformer.h b/include/GafferScene/Deformer.h index 4f733dd7010..2ea623ec160 100644 --- a/include/GafferScene/Deformer.h +++ b/include/GafferScene/Deformer.h @@ -80,6 +80,26 @@ class GAFFERSCENE_API Deformer : public ObjectProcessor /// > accessed by `adjustBounds()`. virtual bool adjustBounds() const; + /// If `computeProcessedObjectBound()` is overridden, this must be overriden + /// to return true for any plugs it uses. Unlike other affects methods, overrides + /// should _not_ call the base class implementation. + virtual bool affectsProcessedObjectBound( const Gaffer::Plug *input ) const; + /// If `computeProcessedObjectBound()` is overridden, this must be + /// be overridden to match. Unlike other hash methods, overrides should + /// _not_ call the base class implementation. + virtual void hashProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + /// May be implemented by derived classes to return a bound for the + /// result of `computeProcessedObject()`. This will only be called if + /// `adjustBounds()` returns true. The default implementation uses + /// the brute force approach of actually processing the object, so + /// reimplementing to provide a cheaper approximate bound may improve + /// performance considerably. + /// > Note : Implementations are currently hampered by the fact that + /// > `in.bound` provides the bound for the input object _and_ its + /// > children. We could consider having separate `in.objectBound` + /// > and `in.childBound` plugs instead. + virtual Imath::Box3f computeProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context ) const; + private : void init(); diff --git a/include/GafferScene/DeleteCurves.h b/include/GafferScene/DeleteCurves.h index 1442dd0901d..7931540e8d1 100644 --- a/include/GafferScene/DeleteCurves.h +++ b/include/GafferScene/DeleteCurves.h @@ -86,4 +86,3 @@ IE_CORE_DECLAREPTR( DeleteCurves ) } // namespace GafferScene #endif // GAFFERSCENE_DELETECURVES_H - diff --git a/include/GafferScene/DeleteFaces.h b/include/GafferScene/DeleteFaces.h index 8bba02e4b5b..567025fe06a 100644 --- a/include/GafferScene/DeleteFaces.h +++ b/include/GafferScene/DeleteFaces.h @@ -86,4 +86,3 @@ IE_CORE_DECLAREPTR( DeleteFaces ) } // namespace GafferScene #endif // GAFFERSCENE_DELETEFACES_H - diff --git a/include/GafferScene/DeletePoints.h b/include/GafferScene/DeletePoints.h index e5e34233f30..9e3088062a6 100644 --- a/include/GafferScene/DeletePoints.h +++ b/include/GafferScene/DeletePoints.h @@ -86,4 +86,3 @@ IE_CORE_DECLAREPTR( DeletePoints ) } // namespace GafferScene #endif // GAFFERSCENE_DELETEPOINTS_H - diff --git a/include/GafferScene/Duplicate.h b/include/GafferScene/Duplicate.h index 94f4602dc29..2e30c81f999 100644 --- a/include/GafferScene/Duplicate.h +++ b/include/GafferScene/Duplicate.h @@ -54,6 +54,7 @@ class GAFFERSCENE_API Duplicate : public BranchCreator GAFFER_NODE_DECLARE_TYPE( GafferScene::Duplicate, DuplicateTypeId, BranchCreator ); + /// \deprecated Use a filter instead. Gaffer::StringPlug *targetPlug(); const Gaffer::StringPlug *targetPlug() const; @@ -74,50 +75,44 @@ class GAFFERSCENE_API Duplicate : public BranchCreator void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; bool affectsBranchBound( const Gaffer::Plug *input ) const override; - void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchTransform( const Gaffer::Plug *input ) const override; - void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchAttributes( const Gaffer::Plug *input ) const override; - void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchObject( const Gaffer::Plug *input ) const override; - void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; - void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchSetNames( const Gaffer::Plug *input ) const override; - void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const override; + void hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const override; bool affectsBranchSet( const Gaffer::Plug *input ) const override; - void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; + void hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; private : - // The BranchCreator::parentPlug() must be used to define the place where the duplicates - // are to be parented, but it's much more natural for the user to simply specify which object - // they want to duplicate, and expect that the duplicates will appear alongside the original. - // This output plug is used to compute the appropriate parent from the target, and is connected - // into BranchCreator::parentPlug() so that the user doesn't need to worry about it. - Gaffer::StringPlug *outParentPlug(); - const Gaffer::StringPlug *outParentPlug() const; - - // We need the list of names of the duplicates in both computeBranchChildNames() - // and computeBranchTransform(), so we compute it on this intermediate plug so that - // the list is cached and the work is shared between the two methods. - Gaffer::InternedStringVectorDataPlug *childNamesPlug(); - const Gaffer::InternedStringVectorDataPlug *childNamesPlug() const; - - void sourcePath( const ScenePath &branchPath, ScenePath &source ) const; + IE_CORE_FORWARDDECLARE( DuplicatesData ); + + // Used to store the names and transforms for each copy. Must be + // evaluated in a context where `scene:path` is one of the source + // locations. + Gaffer::ObjectPlug *duplicatesPlug(); + const Gaffer::ObjectPlug *duplicatesPlug() const; + + void branchSource( const ScenePath &sourcePath, const ScenePath &branchPath, ScenePath &source ) const; static size_t g_firstPlugIndex; diff --git a/src/GafferSceneUIModule/SourceSetBinding.cpp b/include/GafferScene/ExistenceQuery.h similarity index 56% rename from src/GafferSceneUIModule/SourceSetBinding.cpp rename to include/GafferScene/ExistenceQuery.h index e4081c891a2..afb113bf06d 100644 --- a/src/GafferSceneUIModule/SourceSetBinding.cpp +++ b/include/GafferScene/ExistenceQuery.h @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2019, Cinesite VFX Ltd. All rights reserved. +// Copyright (c) 2021, 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 @@ -34,52 +34,50 @@ // ////////////////////////////////////////////////////////////////////////// -#include "boost/python.hpp" +#ifndef GAFFERSCENE_EXISTENCEQUERY_H +#define GAFFERSCENE_EXISTENCEQUERY_H -#include "IECorePython/RunTimeTypedBinding.h" +#include "GafferScene/Export.h" +#include "GafferScene/ScenePlug.h" +#include "GafferScene/TypeIds.h" -#include "SourceSetBinding.h" +#include "Gaffer/ComputeNode.h" +#include "Gaffer/StringPlug.h" +#include "Gaffer/TypedPlug.h" -#include "GafferSceneUI/SourceSet.h" +#include -using namespace Gaffer; -using namespace GafferSceneUI; - -using namespace IECorePython; -using namespace boost::python; - -namespace +namespace GafferScene { -SourceSetPtr sourceSetConstructor( Context &c, Set &s ) +struct GAFFERSCENE_API ExistenceQuery : Gaffer::ComputeNode { - // Must release GIL because SourceSet constructor triggers computes. - ScopedGILRelease gilRelease; - return new SourceSet( &c, &s ); -} + ExistenceQuery( const std::string& name = defaultName< ExistenceQuery >() ); + ~ExistenceQuery() override; -void setContext( SourceSet &s, Context &c ) -{ - IECorePython::ScopedGILRelease gilRelease; - s.setContext( &c ); -} + GAFFER_NODE_DECLARE_TYPE( GafferScene::ExistenceQuery, ExistenceQueryTypeId, Gaffer::ComputeNode ); -void setNodeSet( SourceSet &s, Set &t ) -{ - IECorePython::ScopedGILRelease gilRelease; - s.setNodeSet( &t ); -} + ScenePlug* scenePlug(); + const ScenePlug* scenePlug() const; + Gaffer::StringPlug* locationPlug(); + const Gaffer::StringPlug* locationPlug() const; + Gaffer::BoolPlug* existsPlug(); + const Gaffer::BoolPlug* existsPlug() const; + Gaffer::StringPlug* closestAncestorPlug(); + const Gaffer::StringPlug* closestAncestorPlug() const; -} // namespace + void affects( const Gaffer::Plug* input, AffectedPlugsContainer& outputs ) const override; -void GafferSceneUIModule::bindSourceSet() -{ - RunTimeTypedClass() - .def( "__init__", make_constructor( &sourceSetConstructor, default_call_policies() ) ) - .def( "setContext", &setContext ) - .def( "getContext", &SourceSet::getContext, return_value_policy() ) - .def( "setNodeSet", &setNodeSet ) - .def( "getNodeSet", &SourceSet::getNodeSet, return_value_policy() ) - ; -} +protected: + + void hash( const Gaffer::ValuePlug* output, const Gaffer::Context* context, IECore::MurmurHash& h ) const override; + void compute( Gaffer::ValuePlug* output, const Gaffer::Context* context ) const override; + +private: + + static size_t g_firstPlugIndex; +}; + +} // GafferScene +#endif // GAFFERSCENE_EXISTENCEQUERY_H diff --git a/include/GafferScene/FilterPlug.h b/include/GafferScene/FilterPlug.h index 408c5081b4b..d0568d37869 100644 --- a/include/GafferScene/FilterPlug.h +++ b/include/GafferScene/FilterPlug.h @@ -100,18 +100,26 @@ class GAFFERSCENE_API FilterPlug : public Gaffer::IntPlug struct SceneScope : public Gaffer::Context::EditableScope { SceneScope( const Gaffer::Context *context, const ScenePlug *scenePlug ); + private : + const ScenePlug *m_scenePlug; }; }; IE_CORE_DECLAREPTR( FilterPlug ); +[[deprecated("Use `FilterPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > FilterPlugIterator; +[[deprecated("Use `FilterPlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputFilterPlugIterator; +[[deprecated("Use `FilterPlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputFilterPlugIterator; +[[deprecated("Use `FilterPlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveFilterPlugIterator; +[[deprecated("Use `FilterPlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputFilterPlugIterator; +[[deprecated("Use `FilterPlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputFilterPlugIterator; } // namespace GafferScene diff --git a/include/GafferScene/FilterQuery.h b/include/GafferScene/FilterQuery.h new file mode 100644 index 00000000000..4b0cdb94563 --- /dev/null +++ b/include/GafferScene/FilterQuery.h @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERSCENE_FILTERQUERY_H +#define GAFFERSCENE_FILTERQUERY_H + +#include "GafferScene/Export.h" +#include "GafferScene/TypeIds.h" + +#include "Gaffer/ComputeNode.h" +#include "Gaffer/NumericPlug.h" +#include "Gaffer/StringPlug.h" +#include "Gaffer/TypedObjectPlug.h" + +namespace GafferScene +{ + +IE_CORE_FORWARDDECLARE( ScenePlug ) +IE_CORE_FORWARDDECLARE( FilterPlug ) + +class GAFFERSCENE_API FilterQuery : public Gaffer::ComputeNode +{ + + public : + + FilterQuery( const std::string &name=defaultName() ); + ~FilterQuery() override; + + GAFFER_NODE_DECLARE_TYPE( GafferScene::FilterQuery, FilterQueryTypeId, ComputeNode ); + + ScenePlug *scenePlug(); + const ScenePlug *scenePlug() const; + + FilterPlug *filterPlug(); + const FilterPlug *filterPlug() const; + + Gaffer::StringPlug *locationPlug(); + const Gaffer::StringPlug *locationPlug() const; + + Gaffer::BoolPlug *exactMatchPlug(); + const Gaffer::BoolPlug *exactMatchPlug() const; + + Gaffer::BoolPlug *descendantMatchPlug(); + const Gaffer::BoolPlug *descendantMatchPlug() const; + + Gaffer::BoolPlug *ancestorMatchPlug(); + const Gaffer::BoolPlug *ancestorMatchPlug() const; + + Gaffer::StringPlug *closestAncestorPlug(); + const Gaffer::StringPlug *closestAncestorPlug() const; + + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; + + protected : + + Gaffer::IntPlug *matchPlug(); + const Gaffer::IntPlug *matchPlug() const; + + // Used in the computation of `ancestorMatchPlug()`. This uses + // `${scene:path}` rather than `locationPlug()` so can be used in + // recursive computes to inherit results from ancestor contexts. + Gaffer::StringPlug *closestAncestorInternalPlug(); + const Gaffer::StringPlug *closestAncestorInternalPlug() const; + + void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; + Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; + + static size_t g_firstPlugIndex; + +}; + +IE_CORE_DECLAREPTR( FilterQuery ) + +} // namespace GafferScene + +#endif // GAFFERSCENE_FILTERQUERY_H diff --git a/include/GafferScene/FilterResults.h b/include/GafferScene/FilterResults.h index dd33f624e1b..b62199b4330 100644 --- a/include/GafferScene/FilterResults.h +++ b/include/GafferScene/FilterResults.h @@ -41,6 +41,7 @@ #include "GafferScene/TypeIds.h" #include "Gaffer/ComputeNode.h" +#include "Gaffer/StringPlug.h" #include "Gaffer/TypedObjectPlug.h" namespace GafferScene @@ -65,6 +66,9 @@ class GAFFERSCENE_API FilterResults : public Gaffer::ComputeNode FilterPlug *filterPlug(); const FilterPlug *filterPlug() const; + Gaffer::StringPlug *rootPlug(); + const Gaffer::StringPlug *rootPlug() const; + Gaffer::PathMatcherDataPlug *outPlug(); const Gaffer::PathMatcherDataPlug *outPlug() const; diff --git a/include/GafferScene/Instancer.h b/include/GafferScene/Instancer.h index 3b79d4df45f..0cbdb560308 100644 --- a/include/GafferScene/Instancer.h +++ b/include/GafferScene/Instancer.h @@ -171,40 +171,40 @@ class GAFFERSCENE_API Instancer : public BranchCreator Gaffer::ValuePlug::CachePolicy hashCachePolicy( const Gaffer::ValuePlug *output ) const override; bool affectsBranchBound( const Gaffer::Plug *input ) const override; - void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchTransform( const Gaffer::Plug *input ) const override; - void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchAttributes( const Gaffer::Plug *input ) const override; - void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchObject( const Gaffer::Plug *input ) const override; // Implemented to remove the parent object, because we "convert" the points into a hierarchy bool processesRootObject() const override; - void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; void hashObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; IECore::ConstObjectPtr computeObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const override; bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; - void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; void hashChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; IECore::ConstInternedStringVectorDataPtr computeChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const override; bool affectsBranchSetNames( const Gaffer::Plug *input ) const override; - void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const override; + void hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const override; bool affectsBranchSet( const Gaffer::Plug *input ) const override; - void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; + void hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; void hashSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const override; IECore::ConstPathMatcherDataPtr computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const override; @@ -228,15 +228,19 @@ class GAFFERSCENE_API Instancer : public BranchCreator Gaffer::PathMatcherDataPlug *setCollaboratePlug(); const Gaffer::PathMatcherDataPlug *setCollaboratePlug() const; - ConstEngineDataPtr engine( const ScenePath &parentPath, const Gaffer::Context *context ) const; - void engineHash( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + ConstEngineDataPtr engine( const ScenePath &sourcePath, const Gaffer::Context *context ) const; + void engineHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; - IECore::ConstCompoundDataPtr prototypeChildNames( const ScenePath &parentPath, const Gaffer::Context *context ) const; - void prototypeChildNamesHash( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; + IECore::ConstCompoundDataPtr prototypeChildNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const; + void prototypeChildNamesHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const; struct PrototypeScope : public Gaffer::Context::EditableScope { - PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath &parentPath, const ScenePath &branchPath ); + PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath *parentPath, const ScenePath *branchPath ); + PrototypeScope( const EngineData *engine, const Gaffer::Context *context, const ScenePath *parentPath, const ScenePath *branchPath ); + private : + ScenePlug::ScenePath m_prototypePath; + void setPrototype( const EngineData *engine, const ScenePath *branchPath ); }; static size_t g_firstPlugIndex; diff --git a/include/GafferScene/InteractiveRender.h b/include/GafferScene/InteractiveRender.h index 9f730ad1c7d..68b2401bddb 100644 --- a/include/GafferScene/InteractiveRender.h +++ b/include/GafferScene/InteractiveRender.h @@ -94,7 +94,7 @@ class GAFFERSCENE_API InteractiveRender : public Gaffer::ComputeNode Gaffer::Context *getContext(); const Gaffer::Context *getContext() const; - virtual void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; protected : @@ -104,12 +104,12 @@ class GAFFERSCENE_API InteractiveRender : public Gaffer::ComputeNode // loading of the module which registers the required renderer type. InteractiveRender( const IECore::InternedString &rendererType, const std::string &name ); - virtual void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - virtual void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; + void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; - virtual bool acceptsInput( const Gaffer::Plug *plug, const Gaffer::Plug *inputPlug ) const override; + bool acceptsInput( const Gaffer::Plug *plug, const Gaffer::Plug *inputPlug ) const override; private : @@ -143,7 +143,9 @@ class GAFFERSCENE_API InteractiveRender : public Gaffer::ComputeNode IE_CORE_DECLAREPTR( InteractiveRender ); +[[deprecated("Use `InteractiveRender::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > InteractiveRenderIterator; +[[deprecated("Use `InteractiveRender::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveInteractiveRenderIterator; } // namespace GafferScene diff --git a/include/GafferScene/Parent.h b/include/GafferScene/Parent.h index a661357793e..2877dc4a18e 100644 --- a/include/GafferScene/Parent.h +++ b/include/GafferScene/Parent.h @@ -57,6 +57,9 @@ class GAFFERSCENE_API Parent : public BranchCreator Gaffer::ArrayPlug *childrenPlug(); const Gaffer::ArrayPlug *childrenPlug() const; + Gaffer::StringPlug *parentVariablePlug(); + const Gaffer::StringPlug *parentVariablePlug() const; + void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; protected : @@ -65,40 +68,45 @@ class GAFFERSCENE_API Parent : public BranchCreator void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; bool affectsBranchBound( const Gaffer::Plug *input ) const override; - void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchTransform( const Gaffer::Plug *input ) const override; - void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchAttributes( const Gaffer::Plug *input ) const override; - void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchObject( const Gaffer::Plug *input ) const override; - void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; - void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchSetNames( const Gaffer::Plug *input ) const override; - void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const override; + void hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const override; + bool constantBranchSetNames() const override; bool affectsBranchSet( const Gaffer::Plug *input ) const override; - void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; + void hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; private : + class ParentScope; + class SourceScope; + Gaffer::ObjectPlug *mappingPlug(); const Gaffer::ObjectPlug *mappingPlug() const; + bool affectsParentScope( const Gaffer::Plug *input ) const; + bool affectsSourceScope( const Gaffer::Plug *input ) const; bool isChildrenPlug( const Gaffer::Plug *input, const IECore::InternedString &scenePlugChildName ) const; - ScenePath sourcePath( const ScenePath &branchPath, const ScenePlug **source ) const; static size_t g_firstPlugIndex; diff --git a/include/GafferScene/Private/IECoreScenePreview/CapturingRenderer.h b/include/GafferScene/Private/IECoreScenePreview/CapturingRenderer.h index 06d55fd7615..18a1f9bfeae 100644 --- a/include/GafferScene/Private/IECoreScenePreview/CapturingRenderer.h +++ b/include/GafferScene/Private/IECoreScenePreview/CapturingRenderer.h @@ -107,6 +107,9 @@ class IECORESCENE_API CapturingRenderer : public Renderer const std::vector &capturedSamples() const; const std::vector &capturedSampleTimes() const; + const std::vector &capturedTransforms() const; + const std::vector &capturedTransformTimes() const; + const CapturedAttributes *capturedAttributes() const; const ObjectSet *capturedLinks( const IECore::InternedString &type ) const; @@ -131,6 +134,8 @@ class IECORESCENE_API CapturingRenderer : public Renderer const std::string m_name; const std::vector m_capturedSamples; const std::vector m_capturedSampleTimes; + std::vector m_capturedTransforms; + std::vector m_capturedTransformTimes; ConstCapturedAttributesPtr m_capturedAttributes; int m_numAttributeEdits; std::unordered_map> m_capturedLinks; diff --git a/include/GafferScene/RendererAlgo.h b/include/GafferScene/Private/RendererAlgo.h similarity index 84% rename from include/GafferScene/RendererAlgo.h rename to include/GafferScene/Private/RendererAlgo.h index 3e9a7b8fca9..72c83268e6f 100644 --- a/include/GafferScene/RendererAlgo.h +++ b/include/GafferScene/Private/RendererAlgo.h @@ -41,7 +41,6 @@ #include "GafferScene/Private/IECoreScenePreview/Renderer.h" -#include "IECoreScene/Camera.h" #include "IECoreScene/VisibleRenderable.h" #include "IECore/CompoundObject.h" @@ -59,36 +58,33 @@ namespace GafferScene IE_CORE_FORWARDDECLARE( SceneProcessor ) +namespace Private +{ + namespace RendererAlgo { /// Creates the directories necessary to receive the outputs defined in globals. GAFFERSCENE_API void createOutputDirectories( const IECore::CompoundObject *globals ); +/// Set the "times" to a list of times to sample the transform or deformation of a location at, based on the +/// "motionBlur" enable coming from the options, a shutter, and location attributes. Returns a boolean for +/// whether times has been altered ( returns false if times was already set correctly ). +GAFFERSCENE_API bool transformMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × ); +GAFFERSCENE_API bool deformationMotionTimes( bool motionBlur, const Imath::V2f &shutter, const IECore::CompoundObject *attributes, std::vector × ); + /// Samples the local transform from the current location in preparation for output to the renderer. -/// If segments is 0, the transform is sampled at the time from the current context. If it is non-zero then -/// the sampling is performed evenly across the shutter interval, which should have been obtained via -/// SceneAlgo::shutter(). If all samples turn out to be identical, they will be collapsed automatically -/// into a single sample. The sampleTimes container is only filled if there is more than one sample. -GAFFERSCENE_API void transformSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &shutter, std::vector &samples, std::vector &sampleTimes ); - -/// Samples the object from the current location in preparation for output to the renderer. Sampling parameters -/// are as for the transformSamples() method. Multiple samples will only be generated for Primitives and Cameras, -/// since other object types cannot be interpolated anyway. -GAFFERSCENE_API void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &shutter, std::vector &samples, std::vector &sampleTimes ); -/// \deprecated -GAFFERSCENE_API void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &shutter, std::vector &samples, std::vector &sampleTimes ); - -/// Function to return a SceneProcessor used to adapt the -/// scene for rendering. -typedef std::function Adaptor; -/// Registers an adaptor. -GAFFERSCENE_API void registerAdaptor( const std::string &name, Adaptor adaptor ); -/// Removes a previously registered adaptor. -GAFFERSCENE_API void deregisterAdaptor( const std::string &name ); -/// Returns a SceneProcessor that will apply all the currently -/// registered adaptors. -GAFFERSCENE_API SceneProcessorPtr createAdaptors(); +/// "samples" will be set to contain one sample for each sampleTime, unless the samples are all identical, +/// in which case just one sample is output. +/// If "hash" is passed in, then the hash will be set a value characterizing the samples. If "hash" is +/// already at this value, this function will do nothing and return false. Returns true if hash is not +/// passed in or the hash does not match. +GAFFERSCENE_API bool transformSamples( const Gaffer::M44fPlug *transformPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash = nullptr ); + +/// Samples the object from the current location in preparation for output to the renderer. Sample times and +/// hash behave the same as for the transformSamples() method. Multiple samples will only be generated for +/// Primitives and Cameras, since other object types cannot be interpolated anyway. +GAFFERSCENE_API bool objectSamples( const Gaffer::ObjectPlug *objectPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash = nullptr ); GAFFERSCENE_API void outputOptions( const IECore::CompoundObject *globals, IECoreScenePreview::Renderer *renderer ); GAFFERSCENE_API void outputOptions( const IECore::CompoundObject *globals, const IECore::CompoundObject *previousGlobals, IECoreScenePreview::Renderer *renderer ); @@ -265,11 +261,10 @@ GAFFERSCENE_API void outputLightFilters( const ScenePlug *scene, const IECore::C GAFFERSCENE_API void outputLights( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer ); GAFFERSCENE_API void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *globals, const RenderSets &renderSets, const LightLinks *lightLinks, IECoreScenePreview::Renderer *renderer, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); -/// Applies the resolution, aspect ratio etc from the globals to the camera. -GAFFERSCENE_API void applyCameraGlobals( IECoreScene::Camera *camera, const IECore::CompoundObject *globals, const ScenePlug *scene ); - } // namespace RendererAlgo +} // namespace Private + } // namespace GafferScene #endif // GAFFERSCENE_RENDERERALGO_H diff --git a/include/GafferScene/Render.h b/include/GafferScene/Render.h index b0f586a6e4d..0780bf39bc3 100644 --- a/include/GafferScene/Render.h +++ b/include/GafferScene/Render.h @@ -93,9 +93,12 @@ class GAFFERSCENE_API Render : public GafferDispatch::TaskNode void postTasks( const Gaffer::Context *context, Tasks &tasks ) const override; IECore::MurmurHash hash( const Gaffer::Context *context ) const override; void execute() const override; + void executeSequence( const std::vector &frames ) const override; private : + void executeInternal( bool flushCaches ) const; + ScenePlug *adaptedInPlug(); const ScenePlug *adaptedInPlug() const; diff --git a/include/GafferScene/RenderController.h b/include/GafferScene/RenderController.h index b8d4dfe14df..09df0c643f1 100644 --- a/include/GafferScene/RenderController.h +++ b/include/GafferScene/RenderController.h @@ -38,7 +38,7 @@ #define GAFFERSCENE_RENDERCONTROLLER_H #include "GafferScene/Private/IECoreScenePreview/Renderer.h" -#include "GafferScene/RendererAlgo.h" +#include "GafferScene/Private/RendererAlgo.h" #include "Gaffer/BackgroundTask.h" @@ -96,7 +96,17 @@ class GAFFERSCENE_API RenderController : public boost::signals::trackable SetsGlobalComponent = 2, RenderSetsGlobalComponent = 4, CameraOptionsGlobalComponent = 8, - AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent + TransformBlurGlobalComponent = 16, + DeformationBlurGlobalComponent = 32, + CameraShutterGlobalComponent = 64, + AllGlobalComponents = GlobalsGlobalComponent | SetsGlobalComponent | RenderSetsGlobalComponent | CameraOptionsGlobalComponent | TransformBlurGlobalComponent | DeformationBlurGlobalComponent + }; + + struct MotionBlurOptions + { + bool transformBlur = false; + bool deformationBlur = false; + Imath::V2f shutter = Imath::V2f( 0 ); }; void plugDirtied( const Gaffer::Plug *plug ); @@ -131,8 +141,9 @@ class GAFFERSCENE_API RenderController : public boost::signals::trackable unsigned m_dirtyGlobalComponents; unsigned m_changedGlobalComponents; IECore::ConstCompoundObjectPtr m_globals; - RendererAlgo::RenderSets m_renderSets; - std::unique_ptr m_lightLinks; + MotionBlurOptions m_motionBlurOptions; + Private::RendererAlgo::RenderSets m_renderSets; + std::unique_ptr m_lightLinks; IECoreScenePreview::Renderer::ObjectInterfacePtr m_defaultCamera; IECoreScenePreview::Renderer::AttributesInterfacePtr m_boundAttributes; diff --git a/include/GafferScene/SceneAlgo.h b/include/GafferScene/SceneAlgo.h index 03afd6de6b4..88b724e5335 100644 --- a/include/GafferScene/SceneAlgo.h +++ b/include/GafferScene/SceneAlgo.h @@ -44,6 +44,8 @@ #include "Gaffer/NumericPlug.h" +#include "IECoreScene/Camera.h" + #include "IECore/Export.h" IECORE_PUSH_DEFAULT_VISIBILITY @@ -62,6 +64,8 @@ IE_CORE_FORWARDDECLARE( CompoundData ) namespace GafferScene { +IE_CORE_FORWARDDECLARE( SceneProcessor ) + class SceneProcessor; class FilteredSceneProcessor; class ShaderTweaks; @@ -76,17 +80,21 @@ namespace SceneAlgo /// whether directly or indirectly via an intermediate filter. GAFFERSCENE_API std::unordered_set filteredNodes( Filter *filter ); -/// Finds all the paths in the scene that are matched by the filter, and adds them into the PathMatcher. +/// \deprecated GAFFERSCENE_API void matchingPaths( const Filter *filter, const ScenePlug *scene, IECore::PathMatcher &paths ); -/// As above, but specifying the filter as a plug - typically Filter::outPlug() or -/// FilteredSceneProcessor::filterPlug() would be passed. -GAFFERSCENE_API void matchingPaths( const Gaffer::IntPlug *filterPlug, const ScenePlug *scene, IECore::PathMatcher &paths ); +/// Finds all the paths in the scene that are matched by the filter, and adds them into the PathMatcher. +GAFFERSCENE_API void matchingPaths( const FilterPlug *filterPlug, const ScenePlug *scene, IECore::PathMatcher &paths ); +/// \todo Add default value for `root` and remove overload above. +GAFFERSCENE_API void matchingPaths( const FilterPlug *filterPlug, const ScenePlug *scene, const ScenePlug::ScenePath &root, IECore::PathMatcher &paths ); /// As above, but specifying the filter as a PathMatcher. GAFFERSCENE_API void matchingPaths( const IECore::PathMatcher &filter, const ScenePlug *scene, IECore::PathMatcher &paths ); -/// Matching above, but doing a fast hash of the matching paths instead of storing all paths +/// \deprecated GAFFERSCENE_API IECore::MurmurHash matchingPathsHash( const Filter *filter, const ScenePlug *scene ); +/// As for `matchingPaths()`, but doing a fast hash of the matching paths instead of storing them. GAFFERSCENE_API IECore::MurmurHash matchingPathsHash( const GafferScene::FilterPlug *filterPlug, const ScenePlug *scene ); +/// \todo Add default value for `root` and remove overload above. +GAFFERSCENE_API IECore::MurmurHash matchingPathsHash( const GafferScene::FilterPlug *filterPlug, const ScenePlug *scene, const ScenePlug::ScenePath &root ); GAFFERSCENE_API IECore::MurmurHash matchingPathsHash( const IECore::PathMatcher &filter, const ScenePlug *scene ); /// Parallel scene traversal @@ -117,27 +125,20 @@ GAFFERSCENE_API IECore::MurmurHash matchingPathsHash( const IECore::PathMatcher /// }; /// ``` template -void parallelProcessLocations( const GafferScene::ScenePlug *scene, ThreadableFunctor &f ); -/// As above, but starting the traversal at the specified root. -template -void parallelProcessLocations( const GafferScene::ScenePlug *scene, ThreadableFunctor &f, const ScenePlug::ScenePath &root ); +void parallelProcessLocations( const GafferScene::ScenePlug *scene, ThreadableFunctor &f, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); -/// Calls a functor on all paths in the scene -/// The functor must take ( const ScenePlug*, const ScenePlug::ScenePath& ), and can return false to prune traversal +/// Calls a functor on all locations in the scene. This differs from `parallelProcessLocations()` in that a single instance +/// of the functor is used for all locations. +/// The functor must take `( const ScenePlug *, const ScenePlug::ScenePath & )`, and can return false to prune traversal. template -void parallelTraverse( const ScenePlug *scene, ThreadableFunctor &f ); +void parallelTraverse( const ScenePlug *scene, ThreadableFunctor &f, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); -/// Calls a functor on all paths in the scene that are matched by the filter. -/// The functor must take ( const ScenePlug*, const ScenePlug::ScenePath& ), and can return false to prune traversal +/// As for `parallelTraverse()`, but only calling the functor for locations matched by the filter. template -void filteredParallelTraverse( const ScenePlug *scene, const GafferScene::Filter *filter, ThreadableFunctor &f ); -/// As above, but specifying the filter as a plug - typically Filter::outPlug() or -/// FilteredSceneProcessor::filterPlug() would be passed. -template -void filteredParallelTraverse( const ScenePlug *scene, const Gaffer::IntPlug *filterPlug, ThreadableFunctor &f ); +void filteredParallelTraverse( const ScenePlug *scene, const FilterPlug *filterPlug, ThreadableFunctor &f, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); /// As above, but using a PathMatcher as a filter. template -void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher &filter, ThreadableFunctor &f ); +void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher &filter, ThreadableFunctor &f, const ScenePlug::ScenePath &root = ScenePlug::ScenePath() ); /// Globals /// ======= @@ -270,6 +271,26 @@ GAFFERSCENE_API bool visible( const ScenePlug *scene, const ScenePlug::ScenePath /// for other object types we must return a synthetic bound. GAFFERSCENE_API Imath::Box3f bound( const IECore::Object *object ); +/// Render Adaptors +/// =============== + +/// Function to return a SceneProcessor used to adapt the +/// scene for rendering. +typedef std::function RenderAdaptor; +/// Registers an adaptor. +GAFFERSCENE_API void registerRenderAdaptor( const std::string &name, RenderAdaptor adaptor ); +/// Removes a previously registered adaptor. +GAFFERSCENE_API void deregisterRenderAdaptor( const std::string &name ); +/// Returns a SceneProcessor that will apply all the currently +/// registered adaptors. +GAFFERSCENE_API SceneProcessorPtr createRenderAdaptors(); + +/// Apply Camera Globals +/// ==================== + +/// Applies the resolution, aspect ratio etc from the globals to the camera. +GAFFERSCENE_API void applyCameraGlobals( IECoreScene::Camera *camera, const IECore::CompoundObject *globals, const ScenePlug *scene ); + } // namespace SceneAlgo } // namespace GafferScene diff --git a/include/GafferScene/SceneAlgo.inl b/include/GafferScene/SceneAlgo.inl index 025ea5976c4..d442fd5982f 100644 --- a/include/GafferScene/SceneAlgo.inl +++ b/include/GafferScene/SceneAlgo.inl @@ -36,7 +36,7 @@ #include "Gaffer/Context.h" -#include "tbb/task.h" +#include "tbb/parallel_for.h" namespace GafferScene { @@ -44,143 +44,56 @@ namespace GafferScene namespace Detail { -template -class TraverseTask : public tbb::task -{ - - public : - - TraverseTask( - const GafferScene::ScenePlug *scene, - const Gaffer::ThreadState &threadState, - ThreadableFunctor &f - ) - : m_scene( scene ), m_threadState( threadState ), m_f( f ) - { - } - - ~TraverseTask() override - { - } - - task *execute() override - { - ScenePlug::PathScope pathScope( m_threadState, m_path ); - - if( m_f( m_scene, m_path ) ) - { - IECore::ConstInternedStringVectorDataPtr childNamesData = m_scene->childNamesPlug()->getValue(); - const std::vector &childNames = childNamesData->readable(); - - set_ref_count( 1 + childNames.size() ); - - ScenePlug::ScenePath childPath = m_path; - childPath.push_back( IECore::InternedString() ); // space for the child name - for( std::vector::const_iterator it = childNames.begin(), eIt = childNames.end(); it != eIt; it++ ) - { - childPath[m_path.size()] = *it; - TraverseTask *t = new( allocate_child() ) TraverseTask( *this, childPath ); - spawn( *t ); - } - wait_for_all(); - } - - return nullptr; - } - - protected : - - TraverseTask( const TraverseTask &other, const ScenePlug::ScenePath &path ) - : m_scene( other.m_scene ), - m_threadState( other.m_threadState ), - m_f( other.m_f ), - m_path( path ) - { - } - - private : - - const GafferScene::ScenePlug *m_scene; - const Gaffer::ThreadState &m_threadState; - ThreadableFunctor &m_f; - GafferScene::ScenePlug::ScenePath m_path; - -}; - template -class LocationTask : public tbb::task +void parallelProcessLocationsWalk( const GafferScene::ScenePlug *scene, const Gaffer::ThreadState &threadState, const ScenePlug::ScenePath &path, ThreadableFunctor &f, tbb::task_group_context &taskGroupContext ) { + ScenePlug::PathScope pathScope( threadState, &path ); - public : + if( !f( scene, path ) ) + { + return; + } - LocationTask( - const GafferScene::ScenePlug *scene, - const Gaffer::ThreadState &threadState, - const ScenePlug::ScenePath &path, - ThreadableFunctor &f - ) - : m_scene( scene ), m_threadState( threadState ), m_path( path ), m_f( f ) - { - } + IECore::ConstInternedStringVectorDataPtr childNamesData = scene->childNamesPlug()->getValue(); + const std::vector &childNames = childNamesData->readable(); + if( childNames.empty() ) + { + return; + } - ~LocationTask() override - { - } + using ChildNameRange = tbb::blocked_range::const_iterator>; + const ChildNameRange loopRange( childNames.begin(), childNames.end() ); - task *execute() override + auto loopBody = [&] ( const ChildNameRange &range ) { + ScenePlug::ScenePath childPath = path; + childPath.push_back( IECore::InternedString() ); // Space for the child name + for( auto &childName : range ) { - ScenePlug::PathScope pathScope( m_threadState, m_path ); - - if( !m_f( m_scene, m_path ) ) - { - return nullptr; - } - - IECore::ConstInternedStringVectorDataPtr childNamesData = m_scene->childNamesPlug()->getValue(); - const std::vector &childNames = childNamesData->readable(); - if( childNames.empty() ) - { - return nullptr; - } - - std::vector childFunctors( childNames.size(), m_f ); - - set_ref_count( 1 + childNames.size() ); - - ScenePlug::ScenePath childPath = m_path; - childPath.push_back( IECore::InternedString() ); // space for the child name - for( size_t i = 0, e = childNames.size(); i < e; ++i ) - { - childPath.back() = childNames[i]; - LocationTask *t = new( allocate_child() ) LocationTask( m_scene, m_threadState, childPath, childFunctors[i] ); - spawn( *t ); - } - wait_for_all(); - - return nullptr; + ThreadableFunctor childFunctor( f ); + childPath.back() = childName; + parallelProcessLocationsWalk( scene, threadState, childPath, childFunctor, taskGroupContext ); } + }; - private : - - const GafferScene::ScenePlug *m_scene; - const Gaffer::ThreadState &m_threadState; - const GafferScene::ScenePlug::ScenePath m_path; - ThreadableFunctor &m_f; - -}; + if( childNames.size() > 1 ) + { + tbb::parallel_for( loopRange, loopBody, taskGroupContext ); + } + else + { + // Serial execution + loopBody( loopRange ); + } +} template struct ThreadableFilteredFunctor { - ThreadableFilteredFunctor( ThreadableFunctor &f, const Gaffer::IntPlug *filter ): m_f( f ), m_filter( filter ){} + ThreadableFilteredFunctor( ThreadableFunctor &f, const GafferScene::FilterPlug *filter ): m_f( f ), m_filter( filter ){} bool operator()( const GafferScene::ScenePlug *scene, const GafferScene::ScenePlug::ScenePath &path ) { - IECore::PathMatcher::Result match; - { - FilterPlug::SceneScope sceneScope( Gaffer::Context::current(), scene ); - match = (IECore::PathMatcher::Result)m_filter->getValue(); - } + IECore::PathMatcher::Result match = (IECore::PathMatcher::Result)m_filter->match( scene ); if( match & IECore::PathMatcher::ExactMatch ) { @@ -194,7 +107,7 @@ struct ThreadableFilteredFunctor } ThreadableFunctor &m_f; - const Gaffer::IntPlug *m_filter; + const FilterPlug *m_filter; }; @@ -233,46 +146,37 @@ struct PathMatcherFunctor namespace SceneAlgo { -template -void parallelProcessLocations( const GafferScene::ScenePlug *scene, ThreadableFunctor &f ) -{ - return parallelProcessLocations( scene, f, ScenePlug::ScenePath() ); -} - template void parallelProcessLocations( const GafferScene::ScenePlug *scene, ThreadableFunctor &f, const ScenePlug::ScenePath &root ) { tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); // Prevents outer tasks silently cancelling our tasks - Detail::LocationTask *task = new( tbb::task::allocate_root( taskGroupContext ) ) Detail::LocationTask( scene, Gaffer::ThreadState::current(), root, f ); - tbb::task::spawn_root_and_wait( *task ); -} - -template -void parallelTraverse( const GafferScene::ScenePlug *scene, ThreadableFunctor &f ) -{ - tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); // Prevents outer tasks silently cancelling our tasks - Detail::TraverseTask *task = new( tbb::task::allocate_root( taskGroupContext ) ) Detail::TraverseTask( scene, Gaffer::ThreadState::current(), f ); - tbb::task::spawn_root_and_wait( *task ); + Detail::parallelProcessLocationsWalk( scene, Gaffer::ThreadState::current(), root, f, taskGroupContext ); } template -void filteredParallelTraverse( const GafferScene::ScenePlug *scene, const GafferScene::Filter *filter, ThreadableFunctor &f ) +void parallelTraverse( const ScenePlug *scene, ThreadableFunctor &f, const ScenePlug::ScenePath &root ) { - filteredParallelTraverse( scene, filter->outPlug(), f ); + // `parallelProcessLocations()` takes a copy of the functor at each location, whereas + // `parallelTraverse()` is intended to use the same functor for all locations. Wrap the + // functor in a cheap-to-copy lambda, so that the functor itself won't be copied. + auto reference = [&f] ( const ScenePlug *scene, const ScenePlug::ScenePath &path ) { + return f( scene, path ); + }; + parallelProcessLocations( scene, reference, root ); } template -void filteredParallelTraverse( const GafferScene::ScenePlug *scene, const Gaffer::IntPlug *filterPlug, ThreadableFunctor &f ) +void filteredParallelTraverse( const ScenePlug *scene, const GafferScene::FilterPlug *filterPlug, ThreadableFunctor &f, const ScenePlug::ScenePath &root ) { Detail::ThreadableFilteredFunctor ff( f, filterPlug ); - parallelTraverse( scene, ff ); + parallelTraverse( scene, ff, root ); } template -void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher &filter, ThreadableFunctor &f ) +void filteredParallelTraverse( const ScenePlug *scene, const IECore::PathMatcher &filter, ThreadableFunctor &f, const ScenePlug::ScenePath &root ) { Detail::PathMatcherFunctor ff( f, filter ); - parallelTraverse( scene, ff ); + parallelTraverse( scene, ff, root ); } } // namespace SceneAlgo diff --git a/include/GafferScene/SceneElementProcessor.h b/include/GafferScene/SceneElementProcessor.h index a832f027749..60c4c54463e 100644 --- a/include/GafferScene/SceneElementProcessor.h +++ b/include/GafferScene/SceneElementProcessor.h @@ -43,12 +43,9 @@ namespace GafferScene { -/// The SceneElementProcessor class provides a base class for modifying elements of an input -/// scene while leaving the scene hierarchy unchanged. -/// \todo This "all in one" base class for modifying bounds/transforms/attributes/objects -/// is feeling a bit unwieldy, and it seems that typical derived classes only ever modify -/// one thing anyway. Perhaps we'd be better off with individual TransformProcessor, -/// AttributeProcessor, ObjectProcessor and Deformer base classes. +/// \todo Replace with a range of more specific base classes, deprecate and remove. +/// We already have AttributeProcessor, ObjectProcessor and Deformer, and it looks +/// like a TransformProcessor would get us most of the rest of the way. class GAFFERSCENE_API SceneElementProcessor : public FilteredSceneProcessor { diff --git a/include/GafferScene/SceneNode.h b/include/GafferScene/SceneNode.h index 6d960e7a3fd..1d4e4cb4ff0 100644 --- a/include/GafferScene/SceneNode.h +++ b/include/GafferScene/SceneNode.h @@ -127,6 +127,11 @@ class GAFFERSCENE_API SceneNode : public Gaffer::ComputeNode Gaffer::ValuePlug::CachePolicy hashCachePolicy( const Gaffer::ValuePlug *output ) const override; Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; + /// Returns `enabledPlug()->getValue()` evaluated in a global context. + /// Disabling is handled automatically by the SceneNode and SceneProcessor + /// base classes, so there should be little need to call this. + bool enabled( const Gaffer::Context *context ) const; + private : void plugInputChanged( Gaffer::Plug *plug ); diff --git a/include/GafferScene/ScenePlug.h b/include/GafferScene/ScenePlug.h index 98c9e9e1484..762a879b046 100644 --- a/include/GafferScene/ScenePlug.h +++ b/include/GafferScene/ScenePlug.h @@ -155,32 +155,50 @@ class GAFFERSCENE_API ScenePlug : public Gaffer::ValuePlug /// specifying the scene path. struct PathScope : public Gaffer::Context::EditableScope { + /// NOTE : Any of these calls which take a pointer are fast versions which require + /// the caller to keep the source memory valid for the lifetime of the PathScope. + /// Standard constructors, for modifying context on the current thread. PathScope( const Gaffer::Context *context ); + [[deprecated("Use faster pointer version")]] PathScope( const Gaffer::Context *context, const ScenePath &scenePath ); + PathScope( const Gaffer::Context *context, const ScenePath *scenePath ); /// Specialised constructors used to transfer state to TBB tasks. See /// ThreadState documentation for more details. PathScope( const Gaffer::ThreadState &threadState ); + [[deprecated("Use faster pointer version")]] PathScope( const Gaffer::ThreadState &threadState, const ScenePath &scenePath ); + PathScope( const Gaffer::ThreadState &threadState, const ScenePath *scenePath ); + [[deprecated("Use faster pointer version")]] void setPath( const ScenePath &scenePath ); + void setPath( const ScenePath *scenePath ); }; /// Utility class to scope a temporary copy of a context, /// specifying the set name. struct SetScope : public Gaffer::Context::EditableScope { + /// NOTE : Any of these calls which take a pointer are fast versions which require + /// the caller to keep the source memory valid for the lifetime of the SetScope. + /// Standard constructors, for modifying context on the current thread. SetScope( const Gaffer::Context *context ); + [[deprecated("Use faster pointer version")]] SetScope( const Gaffer::Context *context, const IECore::InternedString &setName ); + SetScope( const Gaffer::Context *context, const IECore::InternedString *setName ); /// Specialised constructors used to transfer state to TBB tasks. See /// ThreadState documentation for more details. SetScope( const Gaffer::ThreadState &threadState ); + [[deprecated("Use faster pointer version")]] SetScope( const Gaffer::ThreadState &threadState, const IECore::InternedString &setName ); + SetScope( const Gaffer::ThreadState &threadState, const IECore::InternedString *setName ); + [[deprecated("Use faster pointer version")]] void setSetName( const IECore::InternedString &setName ); + void setSetName( const IECore::InternedString *setName ); }; /// Utility class to scope a temporary copy of a context, @@ -261,7 +279,9 @@ class GAFFERSCENE_API ScenePlug : public Gaffer::ValuePlug /// \todo Many of the places we use this, it would be preferable if the source data was already /// a path. Perhaps a ScenePathPlug could take care of this for us? static void stringToPath( const std::string &s, ScenePlug::ScenePath &path ); + static ScenePath stringToPath( const std::string &s ); static void pathToString( const ScenePlug::ScenePath &path, std::string &s ); + static std::string pathToString( const ScenePlug::ScenePath &path ); /// Deprecated methods /// ================== @@ -280,12 +300,18 @@ class GAFFERSCENE_API ScenePlug : public Gaffer::ValuePlug IE_CORE_DECLAREPTR( ScenePlug ); +[[deprecated("Use `ScenePlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ScenePlugIterator; +[[deprecated("Use `ScenePlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputScenePlugIterator; +[[deprecated("Use `ScenePlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputScenePlugIterator; +[[deprecated("Use `ScenePlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveScenePlugIterator; +[[deprecated("Use `ScenePlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputScenePlugIterator; +[[deprecated("Use `ScenePlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputScenePlugIterator; } // namespace GafferScene diff --git a/include/GafferScene/Seeds.h b/include/GafferScene/Seeds.h index d3bfd9e31b9..ddc51fb2e41 100644 --- a/include/GafferScene/Seeds.h +++ b/include/GafferScene/Seeds.h @@ -68,24 +68,24 @@ class GAFFERSCENE_API Seeds : public BranchCreator protected : bool affectsBranchBound( const Gaffer::Plug *input ) const override; - void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchTransform( const Gaffer::Plug *input ) const override; - void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchAttributes( const Gaffer::Plug *input ) const override; - void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchObject( const Gaffer::Plug *input ) const override; - void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; - void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; private : diff --git a/include/GafferScene/Set.h b/include/GafferScene/Set.h index af09c0abae6..3cd0dd4a230 100644 --- a/include/GafferScene/Set.h +++ b/include/GafferScene/Set.h @@ -76,6 +76,9 @@ class GAFFERSCENE_API Set : public FilteredSceneProcessor Gaffer::StringPlug *namePlug(); const Gaffer::StringPlug *namePlug() const; + Gaffer::StringPlug *setVariablePlug(); + const Gaffer::StringPlug *setVariablePlug() const; + /// \deprecated Gaffer::StringVectorDataPlug *pathsPlug(); const Gaffer::StringVectorDataPlug *pathsPlug() const; diff --git a/include/GafferScene/SetVisualiser.h b/include/GafferScene/SetVisualiser.h index 12b55cec3ed..fb0fa42ec87 100644 --- a/include/GafferScene/SetVisualiser.h +++ b/include/GafferScene/SetVisualiser.h @@ -86,8 +86,8 @@ class GAFFERSCENE_API SetVisualiser : public AttributeProcessor protected : - virtual void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - virtual void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; + void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; bool affectsProcessedAttributes( const Gaffer::Plug *input ) const override; void hashProcessedAttributes( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; diff --git a/include/GafferScene/Shader.h b/include/GafferScene/Shader.h index dd9d771d1e9..7f8e071b946 100644 --- a/include/GafferScene/Shader.h +++ b/include/GafferScene/Shader.h @@ -132,8 +132,8 @@ class GAFFERSCENE_API Shader : public Gaffer::ComputeNode protected : - virtual void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - virtual void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; + void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; /// Attributes computation /// ---------------------- diff --git a/include/GafferScene/ShaderPlug.h b/include/GafferScene/ShaderPlug.h index 10399af9ad1..842d69bc3a4 100644 --- a/include/GafferScene/ShaderPlug.h +++ b/include/GafferScene/ShaderPlug.h @@ -78,12 +78,18 @@ class GAFFERSCENE_API ShaderPlug : public Gaffer::Plug IE_CORE_DECLAREPTR( ShaderPlug ); +[[deprecated("Use `ShaderPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ShaderPlugIterator; +[[deprecated("Use `ShaderPlug::InputIterator` instead")]] typedef Gaffer::FilteredChildIterator > InputShaderPlugIterator; +[[deprecated("Use `ShaderPlug::OutputIterator` instead")]] typedef Gaffer::FilteredChildIterator > OutputShaderPlugIterator; +[[deprecated("Use `ShaderPlug::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveShaderPlugIterator; +[[deprecated("Use `ShaderPlug::RecursiveInputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveInputShaderPlugIterator; +[[deprecated("Use `ShaderPlug::RecursiveOutputIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator, Gaffer::PlugPredicate<> > RecursiveOutputShaderPlugIterator; } // namespace GafferScene diff --git a/include/GafferScene/TransformQuery.h b/include/GafferScene/TransformQuery.h new file mode 100644 index 00000000000..b7320ad4864 --- /dev/null +++ b/include/GafferScene/TransformQuery.h @@ -0,0 +1,101 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef GAFFERSCENE_TRANSFORMQUERY_H +#define GAFFERSCENE_TRANSFORMQUERY_H + +#include "GafferScene/Export.h" +#include "GafferScene/ScenePlug.h" +#include "GafferScene/TypeIds.h" + +#include "Gaffer/ComputeNode.h" +#include "Gaffer/StringPlug.h" +#include "Gaffer/CompoundNumericPlug.h" +#include "Gaffer/TypedPlug.h" + +#include + +namespace GafferScene +{ + +struct GAFFERSCENE_API TransformQuery : Gaffer::ComputeNode +{ + enum class Space + { + Local = 0x00, + World = 0x01, + Relative = 0x02 + }; + + TransformQuery( std::string const& name = defaultName< TransformQuery >() ); + ~TransformQuery() override; + + GAFFER_NODE_DECLARE_TYPE( GafferScene::TransformQuery, TransformQueryTypeId, Gaffer::ComputeNode ); + + ScenePlug* scenePlug(); + ScenePlug const* scenePlug() const; + Gaffer::StringPlug* locationPlug(); + Gaffer::StringPlug const* locationPlug() const; + Gaffer::IntPlug* spacePlug(); + Gaffer::IntPlug const* spacePlug() const; + Gaffer::StringPlug* relativeLocationPlug(); + Gaffer::StringPlug const* relativeLocationPlug() const; + Gaffer::BoolPlug* invertPlug(); + Gaffer::BoolPlug const* invertPlug() const; + Gaffer::M44fPlug* matrixPlug(); + Gaffer::M44fPlug const* matrixPlug() const; + Gaffer::V3fPlug* translatePlug(); + Gaffer::V3fPlug const* translatePlug() const; + Gaffer::V3fPlug* rotatePlug(); + Gaffer::V3fPlug const* rotatePlug() const; + Gaffer::V3fPlug* scalePlug(); + Gaffer::V3fPlug const* scalePlug() const; + + void affects( Gaffer::Plug const* input, AffectedPlugsContainer& outputs ) const override; + +protected: + + void hash( Gaffer::ValuePlug const* output, Gaffer::Context const* context, IECore::MurmurHash& hash ) const override; + void compute( Gaffer::ValuePlug* output, Gaffer::Context const* context ) const override; + + static size_t g_firstPlugIndex; +}; + +IE_CORE_DECLAREPTR( TransformQuery ) + +} // GafferScene + +#endif // GAFFERSCENE_TRANSFORMQUERY_H diff --git a/include/GafferScene/TweakPlug.h b/include/GafferScene/TweakPlug.h index a6dcb226d76..48a66211e1d 100644 --- a/include/GafferScene/TweakPlug.h +++ b/include/GafferScene/TweakPlug.h @@ -119,6 +119,7 @@ class GAFFERSCENE_API TweakPlug : public Gaffer::ValuePlug }; +[[deprecated("Use `TweakPlug::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > TweakPlugIterator; IE_CORE_DECLAREPTR( TweakPlug ) diff --git a/include/GafferScene/TypeIds.h b/include/GafferScene/TypeIds.h index de0929f6fd3..e0741e14f04 100644 --- a/include/GafferScene/TypeIds.h +++ b/include/GafferScene/TypeIds.h @@ -159,6 +159,10 @@ enum TypeId UnencapsulateTypeId = 110614, MotionPathTypeId = 110615, InstancerContextVariablePlugTypeId = 110616, + FilterQueryTypeId = 110617, + TransformQueryTypeId = 110618, + BoundQueryTypeId = 110619, + ExistenceQueryTypeId = 110620, PreviewGeometryTypeId = 110648, PreviewProceduralTypeId = 110649, diff --git a/include/GafferScene/UDIMQuery.h b/include/GafferScene/UDIMQuery.h index c3f2761afb0..231e2d660a6 100644 --- a/include/GafferScene/UDIMQuery.h +++ b/include/GafferScene/UDIMQuery.h @@ -81,6 +81,9 @@ class GAFFERSCENE_API UDIMQuery : public Gaffer::ComputeNode void hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; void compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const override; + Gaffer::ValuePlug::CachePolicy computeCachePolicy( const Gaffer::ValuePlug *output ) const override; + Gaffer::ValuePlug::CachePolicy hashCachePolicy( const Gaffer::ValuePlug *output ) const override; + private : static size_t g_firstPlugIndex; diff --git a/include/GafferScene/Unencapsulate.h b/include/GafferScene/Unencapsulate.h index 0aebd1db763..8f55f7f5666 100644 --- a/include/GafferScene/Unencapsulate.h +++ b/include/GafferScene/Unencapsulate.h @@ -57,33 +57,33 @@ class GAFFERSCENE_API Unencapsulate : public BranchCreator protected : bool affectsBranchBound( const Gaffer::Plug *input ) const override; - void hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchTransform( const Gaffer::Plug *input ) const override; - void hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::M44f computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::M44f computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchAttributes( const Gaffer::Plug *input ) const override; - void hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstCompoundObjectPtr computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool processesRootObject() const override; bool affectsBranchObject( const Gaffer::Plug *input ) const override; - void hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstObjectPtr computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstObjectPtr computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchChildNames( const Gaffer::Plug *input ) const override; - void hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; + void hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const override; bool affectsBranchSetNames( const Gaffer::Plug *input ) const override; - void hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const override; + void hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstInternedStringVectorDataPtr computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const override; bool affectsBranchSet( const Gaffer::Plug *input ) const override; - void hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; + void hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + IECore::ConstPathMatcherDataPtr computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const override; private : diff --git a/include/GafferSceneUI/SceneGadget.h b/include/GafferSceneUI/SceneGadget.h index d557136fcd3..1c741c09dd6 100644 --- a/include/GafferSceneUI/SceneGadget.h +++ b/include/GafferSceneUI/SceneGadget.h @@ -196,7 +196,9 @@ class GAFFERSCENEUI_API SceneGadget : public GafferUI::Gadget }; +[[deprecated("Use `SceneGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > SceneGadgetIterator; +[[deprecated("Use `SceneGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveSceneGadgetIterator; } // namespace GafferUI diff --git a/include/GafferSceneUI/SourceSet.h b/include/GafferSceneUI/SourceSet.h deleted file mode 100755 index 9d69152e95d..00000000000 --- a/include/GafferSceneUI/SourceSet.h +++ /dev/null @@ -1,114 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// Copyright (c) 2019, 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. -// -////////////////////////////////////////////////////////////////////////// - -#ifndef GAFFERSCENEUI_SOURCESET_H -#define GAFFERSCENEUI_SOURCESET_H - -#include "GafferSceneUI/TypeIds.h" - -#include "GafferScene/ScenePlug.h" -#include "GafferScene/SceneNode.h" - -#include "Gaffer/Context.h" -#include "Gaffer/Set.h" -#include "Gaffer/StandardSet.h" - -#include "IECore/PathMatcher.h" - -namespace GafferSceneUI -{ - -/// The SourceSet provides a Set implementation that adjusts its membership such that -/// it contains the source node for the current scene selection, given a context and -/// a target node from which to observe the scene. -/// -/// When there is no scene selection, or the supplied node set contains no SceneNodes, -/// it falls back to the node in the node set. -/// -/// When multiple locations are selected or there are multiple nodes in the node set, -/// the last of each will be used to determine which source node is presented by the set. -/// -/// The SourceSet requires a valid context and node set in order to determine source nodes -/// other than the direct selection of non-SceneNodes. -class GAFFER_API SourceSet : public Gaffer::Set -{ - - public : - - SourceSet( Gaffer::ContextPtr context, Gaffer::SetPtr nodeSet ); - ~SourceSet() override; - - IE_CORE_DECLARERUNTIMETYPEDEXTENSION( GafferSceneUI::SourceSet, SourceSetTypeId, Gaffer::Set ); - - void setContext( Gaffer::ContextPtr context ); - Gaffer::Context *getContext() const; - - void setNodeSet( Gaffer::SetPtr nodeSet ); - Gaffer::Set *getNodeSet() const; - - /// @name Set interface - //////////////////////////////////////////////////////////////////// - //@{ - bool contains( const Member *object ) const override; - Member *member( size_t index ) override; - const Member *member( size_t index ) const override; - size_t size() const override; - //@} - - private : - - Gaffer::ContextPtr m_context; - Gaffer::SetPtr m_nodes; - - GafferScene::ScenePlugPtr m_scenePlug; - void updateScenePlug(); - - Gaffer::NodePtr m_sourceNode; - void updateSourceNode(); - - void contextChanged( const IECore::InternedString &name ); - void plugDirtied( const Gaffer::Plug *plug ); - boost::signals::scoped_connection m_contextChangedConnection; - boost::signals::scoped_connection m_plugDirtiedConnection; - boost::signals::scoped_connection m_nodeAddedConnection; - boost::signals::scoped_connection m_nodeRemovedConnection; -}; - -IE_CORE_DECLAREPTR( SourceSet ); - -} // namespace GafferSceneUI - -#endif // GAFFERSCENEUI_SOURCESET_H diff --git a/include/GafferSceneUI/TypeIds.h b/include/GafferSceneUI/TypeIds.h index 668fbd1e755..356ae423f9f 100644 --- a/include/GafferSceneUI/TypeIds.h +++ b/include/GafferSceneUI/TypeIds.h @@ -55,7 +55,6 @@ enum TypeId CameraToolTypeId = 110661, UVViewTypeId = 110662, UVSceneTypeId = 110663, - SourceSetTypeId = 110664, LastTypeId = 110700 }; diff --git a/include/GafferTest/ContextTest.h b/include/GafferTest/ContextTest.h index 28ade8a05c7..4d267edadcb 100644 --- a/include/GafferTest/ContextTest.h +++ b/include/GafferTest/ContextTest.h @@ -37,16 +37,160 @@ #ifndef GAFFERTEST_CONTEXTTEST_H #define GAFFERTEST_CONTEXTTEST_H +#include "GafferTest/Assert.h" #include "GafferTest/Export.h" +#include "Gaffer/Context.h" + +#include + namespace GafferTest { +template < typename T > +void testEditableScopeTyped( const typename T::ValueType &aVal, const typename T::ValueType &bVal ) +{ + using V = typename T::ValueType; + + Gaffer::ContextPtr baseContext = new Gaffer::Context(); + baseContext->set( "a", aVal ); + baseContext->set( "b", bVal ); + + // Test basic context functionality + GAFFERTEST_ASSERT( baseContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( *baseContext->getIfExists( "a" ) == aVal ); + GAFFERTEST_ASSERT( baseContext->get( "b" ) == bVal ); + GAFFERTEST_ASSERT( *baseContext->getIfExists( "b" ) == bVal ); + GAFFERTEST_ASSERT( baseContext->getIfExists( "doesntExist" ) == nullptr ); + + const typename T::Ptr aData = new T( aVal ); + const typename T::Ptr bData = new T( bVal ); + + // Test setting with a TypedData + baseContext->set( "a", bData.get() ); + baseContext->set( "b", bData.get() ); + GAFFERTEST_ASSERT( baseContext->get( "a" ) == bVal ); + GAFFERTEST_ASSERT( baseContext->get( "b" ) == bVal ); + + // And set back again with a direct value + baseContext->set( "a", aVal ); + GAFFERTEST_ASSERT( baseContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( baseContext->get( "b" ) == bVal ); + + // Test getting as a generic Data - this should work where set as Data, or directly from a value + GAFFERTEST_ASSERT( baseContext->getAsData( "a" )->isEqualTo( aData.get() ) ); + GAFFERTEST_ASSERT( baseContext->getAsData( "b" )->isEqualTo( bData.get() ) ); + + const V *aPointer = baseContext->getIfExists( "a" ); + const V *bPointer = baseContext->getIfExists( "b" ); + + { + // Scope an editable copy of the context + Gaffer::Context::EditableScope scope( baseContext.get() ); + + const Gaffer::Context *currentContext = Gaffer::Context::current(); + GAFFERTEST_ASSERT( currentContext != baseContext ); + + // The editable copy should be identical to the original, + // and the original should be unchanged. + GAFFERTEST_ASSERT( baseContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( baseContext->get( "b" ) == bVal ); + GAFFERTEST_ASSERT( currentContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->get( "b" ) == bVal ); + GAFFERTEST_ASSERT( currentContext->hash() == baseContext->hash() ); + + // The copy should even be referencing the exact same data + // as the original. + GAFFERTEST_ASSERT( baseContext->getIfExists( "a" ) == aPointer ); + GAFFERTEST_ASSERT( baseContext->getIfExists( "b" ) == bPointer ); + GAFFERTEST_ASSERT( currentContext->getIfExists( "a" ) == aPointer ); + GAFFERTEST_ASSERT( currentContext->getIfExists( "b" ) == bPointer ); + + // Editing the copy shouldn't affect the original + scope.set( "c", &aVal ); + GAFFERTEST_ASSERT( baseContext->getIfExists( "c" ) == nullptr ); + GAFFERTEST_ASSERT( currentContext->get( "c" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->hash() != baseContext->hash() ); + + // Even if we're editing a variable that exists in + // the original. + scope.set( "a", &bVal ); + GAFFERTEST_ASSERT( baseContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->get( "a" ) == bVal ); + + // And we should be able to remove a variable from the + // copy without affecting the original too. + scope.remove( "b" ); + GAFFERTEST_ASSERT( baseContext->get( "b" ) == bVal ); + GAFFERTEST_ASSERT( currentContext->getIfExists( "b" ) == nullptr ); + + // And none of the edits should have affected the original + // data at all. + GAFFERTEST_ASSERT( baseContext->getIfExists( "a" ) == aPointer ); + GAFFERTEST_ASSERT( baseContext->getIfExists( "b" ) == bPointer ); + + // Test setAllocated with Data + scope.setAllocated( "a", aData.get() ); + scope.setAllocated( "b", aData.get() ); + GAFFERTEST_ASSERT( currentContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->get( "b" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->getAsData( "a" )->isEqualTo( aData.get() ) ); + GAFFERTEST_ASSERT( currentContext->getAsData( "b" )->isEqualTo( aData.get() ) ); + + // And setAllocated with a direct data + scope.setAllocated( "b", bVal ); + GAFFERTEST_ASSERT( currentContext->get( "a" ) == aVal ); + GAFFERTEST_ASSERT( currentContext->get( "b" ) == bVal ); + + // Test getting as a generic Data - this should work where set as Data, or directly from a value + GAFFERTEST_ASSERT( currentContext->getAsData( "a" )->isEqualTo( aData.get() ) ); + GAFFERTEST_ASSERT( currentContext->getAsData( "b" )->isEqualTo( bData.get() ) ); + } + + // Check that setting with a pointer, or a value, or Data, has the same effect + { + Gaffer::Context::EditableScope x( baseContext.get() ); + Gaffer::Context::EditableScope y( baseContext.get() ); + Gaffer::Context::EditableScope z( baseContext.get() ); + + x.set( "c", &aVal ); + y.setAllocated( "c", aVal ); + z.setAllocated( "c", aData.get() ); + + GAFFERTEST_ASSERT( x.context()->get( "c" ) == aVal ); + GAFFERTEST_ASSERT( y.context()->get( "c" ) == aVal ); + GAFFERTEST_ASSERT( z.context()->get( "c" ) == aVal ); + + GAFFERTEST_ASSERT( x.context()->hash() == y.context()->hash() ); + GAFFERTEST_ASSERT( x.context()->hash() == z.context()->hash() ); + GAFFERTEST_ASSERT( x.context()->variableHash( "c" ) == y.context()->variableHash( "c" ) ); + GAFFERTEST_ASSERT( x.context()->variableHash( "c" ) == z.context()->variableHash( "c" ) ); + + x.set( "c", &bVal ); + y.setAllocated( "c", bVal ); + z.setAllocated( "c", bData.get() ); + + GAFFERTEST_ASSERT( x.context()->get( "c" ) == bVal ); + GAFFERTEST_ASSERT( y.context()->get( "c" ) == bVal ); + GAFFERTEST_ASSERT( z.context()->get( "c" ) == bVal ); + + GAFFERTEST_ASSERT( x.context()->hash() == y.context()->hash() ); + GAFFERTEST_ASSERT( x.context()->hash() == z.context()->hash() ); + GAFFERTEST_ASSERT( x.context()->variableHash( "c" ) == y.context()->variableHash( "c" ) ); + GAFFERTEST_ASSERT( x.context()->variableHash( "c" ) == z.context()->variableHash( "c" ) ); + } +} + GAFFERTEST_API void testManyContexts(); GAFFERTEST_API void testManySubstitutions(); GAFFERTEST_API void testManyEnvironmentSubstitutions(); GAFFERTEST_API void testScopingNullContext(); GAFFERTEST_API void testEditableScope(); +GAFFERTEST_API std::tuple countContextHash32Collisions( int contexts, int mode, int seed ); +GAFFERTEST_API void testContextHashPerformance( int numEntries, int entrySize, bool startInitialized ); +GAFFERTEST_API void testContextCopyPerformance( int numEntries, int entrySize ); +GAFFERTEST_API void testCopyEditableScope(); +GAFFERTEST_API void testContextHashValidation(); } // namespace GafferTest diff --git a/src/GafferSceneUIModule/SourceSetBinding.h b/include/GafferTest/RandomTest.h similarity index 85% rename from src/GafferSceneUIModule/SourceSetBinding.h rename to include/GafferTest/RandomTest.h index 16c9ebe6401..8a959f87b62 100644 --- a/src/GafferSceneUIModule/SourceSetBinding.h +++ b/include/GafferTest/RandomTest.h @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2019, Cinesite VFX Ltd. All rights reserved. +// Copyright (c) 2020, 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 @@ -34,14 +34,16 @@ // ////////////////////////////////////////////////////////////////////////// -#ifndef GAFFERSCENEUIMODULE_SOURCESETBINDING_H -#define GAFFERSCENEUIMODULE_SOURCESETBINDING_H +#ifndef GAFFERTEST_RANDOMTEST_H +#define GAFFERTEST_RANDOMTEST_H -namespace GafferSceneUIModule +#include "GafferTest/Export.h" + +namespace GafferTest { -void bindSourceSet(); +GAFFERTEST_API void testRandomPerf(); -} // namespace GafferSceneUIModule +} // namespace GafferTest -#endif // GAFFERSCENEUIMODULE_SOURCESETBINDING_H +#endif // GAFFERTEST_RANDOMTEST_H diff --git a/include/GafferUI/AnnotationsGadget.h b/include/GafferUI/AnnotationsGadget.h index 5439f19065e..bfa522bdcfc 100644 --- a/include/GafferUI/AnnotationsGadget.h +++ b/include/GafferUI/AnnotationsGadget.h @@ -37,9 +37,11 @@ #ifndef GAFFERUI_ANNOTATIONSGADGET_H #define GAFFERUI_ANNOTATIONSGADGET_H +#include "Gaffer/MetadataAlgo.h" + #include "GafferUI/Gadget.h" -#include "IECore/SimpleTypedData.h" +#include "IECore/StringAlgo.h" #include @@ -66,6 +68,13 @@ class GAFFERUI_API AnnotationsGadget : public Gadget GAFFER_GRAPHCOMPONENT_DECLARE_TYPE( GafferUI::AnnotationsGadget, AnnotationsGadgetTypeId, Gadget ); + /// Special value that may be used with `setVisibleAnnotations()`, to match + /// all annotations not registered with `MetadataAlgo::registerAnnotationTemplate()`. + static const std::string untemplatedAnnotations; + + void setVisibleAnnotations( const IECore::StringAlgo::MatchPattern &patterns ); + const IECore::StringAlgo::MatchPattern &getVisibleAnnotations() const; + bool acceptsParent( const GraphComponent *potentialParent ) const override; protected : @@ -86,17 +95,12 @@ class GAFFERUI_API AnnotationsGadget : public Gadget void graphGadgetChildAdded( GraphComponent *child ); void graphGadgetChildRemoved( const GraphComponent *child ); void nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore::InternedString key, Gaffer::Node *node ); - - struct StandardAnnotation - { - IECore::ConstStringDataPtr text; - IECore::ConstColor3fDataPtr color; - }; + void update() const; struct Annotations { bool dirty = true; - std::vector standardAnnotations; + std::vector standardAnnotations; bool bookmarked = false; IECore::InternedString numericBookmark; bool renderable = false; @@ -107,6 +111,9 @@ class GAFFERUI_API AnnotationsGadget : public Gadget using AnnotationsContainer = std::unordered_map; mutable AnnotationsContainer m_annotations; + mutable bool m_dirty; + + IECore::StringAlgo::MatchPattern m_visibleAnnotations; }; diff --git a/include/GafferUI/AuxiliaryNodeGadget.h b/include/GafferUI/AuxiliaryNodeGadget.h index a0c4b2a6d8a..9472b684efe 100644 --- a/include/GafferUI/AuxiliaryNodeGadget.h +++ b/include/GafferUI/AuxiliaryNodeGadget.h @@ -74,7 +74,9 @@ class GAFFERUI_API AuxiliaryNodeGadget : public NodeGadget IE_CORE_DECLAREPTR( AuxiliaryNodeGadget ) +[[deprecated("Use `AuxiliaryNodeGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > AuxiliaryNodeGadgetIterator; +[[deprecated("Use `AuxiliaryNodeGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveAuxiliaryNodeGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/BackdropNodeGadget.h b/include/GafferUI/BackdropNodeGadget.h index 3bcf62fbf15..0837fa898b9 100644 --- a/include/GafferUI/BackdropNodeGadget.h +++ b/include/GafferUI/BackdropNodeGadget.h @@ -110,7 +110,9 @@ class GAFFERUI_API BackdropNodeGadget : public NodeGadget IE_CORE_DECLAREPTR( BackdropNodeGadget ); +[[deprecated("Use `BackdropNodeGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > BackdropNodeGadgetIterator; +[[deprecated("Use `BackdropNodeGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveBackdropNodeGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/CompoundNodule.h b/include/GafferUI/CompoundNodule.h index 7acae544873..f16e4e6fdb8 100644 --- a/include/GafferUI/CompoundNodule.h +++ b/include/GafferUI/CompoundNodule.h @@ -77,7 +77,9 @@ class GAFFERUI_API CompoundNodule : public Nodule IE_CORE_DECLAREPTR( CompoundNodule ); +[[deprecated("Use `CompoundNodule::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > CompoundNoduleIterator; +[[deprecated("Use `CompoundNodule::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveCompoundNoduleIterator; } // namespace GafferUI diff --git a/include/GafferUI/CompoundNumericNodule.h b/include/GafferUI/CompoundNumericNodule.h index 2b7059ca5bf..8e91656724f 100644 --- a/include/GafferUI/CompoundNumericNodule.h +++ b/include/GafferUI/CompoundNumericNodule.h @@ -83,7 +83,9 @@ class GAFFERUI_API CompoundNumericNodule : public StandardNodule IE_CORE_DECLAREPTR( CompoundNumericNodule ); +[[deprecated("Use `CompoundNumericNodule::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > CompoundNumericNoduleIterator; +[[deprecated("Use `CompoundNumericNodule::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveCompoundNumericNoduleIterator; } // namespace GafferUI diff --git a/include/GafferUI/ConnectionCreator.h b/include/GafferUI/ConnectionCreator.h index a56fed74192..516c7e375a3 100644 --- a/include/GafferUI/ConnectionCreator.h +++ b/include/GafferUI/ConnectionCreator.h @@ -76,7 +76,9 @@ class GAFFERUI_API ConnectionCreator : public Gadget IE_CORE_DECLAREPTR( ConnectionCreator ) +[[deprecated("Use `ConnectionCreator::Iterator` instead")]] typedef Gaffer::FilteredChildIterator> ConnectionCreatorIterator; +[[deprecated("Use `ConnectionCreator::Recursive` instead")]] typedef Gaffer::FilteredRecursiveChildIterator> RecursiveConnectionCreatorIterator; } // namespace GafferUI diff --git a/include/GafferUI/ConnectionGadget.h b/include/GafferUI/ConnectionGadget.h index 14345ddcd7b..1dc9f2fd973 100644 --- a/include/GafferUI/ConnectionGadget.h +++ b/include/GafferUI/ConnectionGadget.h @@ -139,7 +139,9 @@ class GAFFERUI_API ConnectionGadget : public ConnectionCreator }; +[[deprecated("Use `ConnectionGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ConnectionGadgetIterator; +[[deprecated("Use `ConnectionGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveConnectionGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/ContainerGadget.h b/include/GafferUI/ContainerGadget.h index 726b874f6bf..e42d12f95c2 100644 --- a/include/GafferUI/ContainerGadget.h +++ b/include/GafferUI/ContainerGadget.h @@ -79,7 +79,9 @@ class GAFFERUI_API ContainerGadget : public Gadget IE_CORE_DECLAREPTR( ContainerGadget ); +[[deprecated("Use `ContainerGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ContainerGadgetIterator; +[[deprecated("Use `ContainerGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveContainerGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/DotNodeGadget.h b/include/GafferUI/DotNodeGadget.h index be50ca9f67e..3bc147f41df 100644 --- a/include/GafferUI/DotNodeGadget.h +++ b/include/GafferUI/DotNodeGadget.h @@ -90,7 +90,9 @@ class GAFFERUI_API DotNodeGadget : public StandardNodeGadget IE_CORE_DECLAREPTR( DotNodeGadget ) +[[deprecated("Use `DotNodeGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > DotNodeGadgetIterator; +[[deprecated("Use `DotNodeGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveDotNodeGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/Frame.h b/include/GafferUI/Frame.h index cb00e573320..13eeafb5b1b 100644 --- a/include/GafferUI/Frame.h +++ b/include/GafferUI/Frame.h @@ -68,7 +68,9 @@ class GAFFERUI_API Frame : public IndividualContainer IE_CORE_DECLAREPTR( Frame ); +[[deprecated("Use `Frame::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > FrameIterator; +[[deprecated("Use `Frame::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveFrameIterator; } // namespace GafferUI diff --git a/include/GafferUI/Gadget.h b/include/GafferUI/Gadget.h index b274e7cd9ec..a181149dccf 100644 --- a/include/GafferUI/Gadget.h +++ b/include/GafferUI/Gadget.h @@ -346,7 +346,9 @@ class GAFFERUI_API Gadget : public Gaffer::GraphComponent }; +[[deprecated("Use `Gadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > GadgetIterator; +[[deprecated("Use `Gadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/GraphGadget.h b/include/GafferUI/GraphGadget.h index b13188d93ee..a14e56c1ec7 100644 --- a/include/GafferUI/GraphGadget.h +++ b/include/GafferUI/GraphGadget.h @@ -297,7 +297,9 @@ class GAFFERUI_API GraphGadget : public ContainerGadget IE_CORE_DECLAREPTR( GraphGadget ); +[[deprecated("Use `GraphGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > GraphGadgetIterator; +[[deprecated("Use `GraphGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveGraphGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/Handle.h b/include/GafferUI/Handle.h index cf618838daf..43d253b6dcd 100644 --- a/include/GafferUI/Handle.h +++ b/include/GafferUI/Handle.h @@ -218,7 +218,9 @@ class GAFFERUI_API Handle : public Gadget IE_CORE_DECLAREPTR( Handle ) +[[deprecated("Use `Handle::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > HandleIterator; +[[deprecated("Use `Handle::Recursive` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveHandleIterator; } // namespace GafferUI diff --git a/include/GafferUI/ImageGadget.h b/include/GafferUI/ImageGadget.h index 0360207db0f..26e8886d5ce 100644 --- a/include/GafferUI/ImageGadget.h +++ b/include/GafferUI/ImageGadget.h @@ -97,7 +97,9 @@ class GAFFERUI_API ImageGadget : public Gadget IE_CORE_DECLAREPTR( ImageGadget ) +[[deprecated("Use `ImageGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ImageGadgetIterator; +[[deprecated("Use `ImageGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveImageGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/IndividualContainer.h b/include/GafferUI/IndividualContainer.h index 3480f3d2bab..16285946871 100644 --- a/include/GafferUI/IndividualContainer.h +++ b/include/GafferUI/IndividualContainer.h @@ -72,7 +72,9 @@ class GAFFERUI_API IndividualContainer : public ContainerGadget IE_CORE_DECLAREPTR( IndividualContainer ); +[[deprecated("Use `IndividualContainer::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > IndividualContainerIterator; +[[deprecated("Use `IndividualContainer::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveIndividualContainerIterator; } // namespace GafferUI diff --git a/include/GafferUI/LinearContainer.h b/include/GafferUI/LinearContainer.h index 4a516548d2f..334ec2f9f95 100644 --- a/include/GafferUI/LinearContainer.h +++ b/include/GafferUI/LinearContainer.h @@ -103,7 +103,9 @@ class GAFFERUI_API LinearContainer : public ContainerGadget IE_CORE_DECLAREPTR( LinearContainer ); +[[deprecated("Use `LinearContainer::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > LinearContainerIterator; +[[deprecated("Use `LinearContainer::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveLinearContainerIterator; } // namespace GafferUI diff --git a/include/GafferUI/NameGadget.h b/include/GafferUI/NameGadget.h index c85377ce499..69feed31046 100644 --- a/include/GafferUI/NameGadget.h +++ b/include/GafferUI/NameGadget.h @@ -62,7 +62,9 @@ class GAFFERUI_API NameGadget : public TextGadget IE_CORE_DECLAREPTR( NameGadget ); +[[deprecated("Use `NameGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > NameGadgetIterator; +[[deprecated("Use `NameGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveNameGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/NodeGadget.h b/include/GafferUI/NodeGadget.h index 6ee50db2d0c..c98e8a7d82b 100644 --- a/include/GafferUI/NodeGadget.h +++ b/include/GafferUI/NodeGadget.h @@ -124,7 +124,9 @@ class GAFFERUI_API NodeGadget : public Gadget IE_CORE_DECLAREPTR( NodeGadget ); +[[deprecated("Use `NodeGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > NodeGadgetIterator; +[[deprecated("Use `NodeGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveNodeGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/Nodule.h b/include/GafferUI/Nodule.h index ce4c2b4607e..9a7f6efeb9c 100644 --- a/include/GafferUI/Nodule.h +++ b/include/GafferUI/Nodule.h @@ -110,7 +110,9 @@ class GAFFERUI_API Nodule : public ConnectionCreator }; +[[deprecated("Use `Nodule::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > NoduleIterator; +[[deprecated("Use `Nodule::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveNoduleIterator; IE_CORE_DECLAREPTR( Nodule ); diff --git a/include/GafferUI/NoduleLayout.h b/include/GafferUI/NoduleLayout.h index 94f3da0e393..274841bd3c6 100644 --- a/include/GafferUI/NoduleLayout.h +++ b/include/GafferUI/NoduleLayout.h @@ -143,7 +143,9 @@ class GAFFERUI_API NoduleLayout : public Gadget IE_CORE_DECLAREPTR( NoduleLayout ) +[[deprecated("Use `NoduleLayout::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > NoduleLayoutIterator; +[[deprecated("Use `NoduleLayout::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveNoduleLayoutIterator; } // namespace GafferUI diff --git a/include/GafferUI/PlugAdder.h b/include/GafferUI/PlugAdder.h index 463f75dcf48..f45b51238e0 100644 --- a/include/GafferUI/PlugAdder.h +++ b/include/GafferUI/PlugAdder.h @@ -94,7 +94,9 @@ class GAFFERUI_API PlugAdder : public ConnectionCreator IE_CORE_DECLAREPTR( PlugAdder ) +[[deprecated("Use `PlugAdder::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > PlugAdderIterator; +[[deprecated("Use `PlugAdder::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursivePlugAdderIterator; } // namespace GafferUI diff --git a/include/GafferUI/PlugGadget.h b/include/GafferUI/PlugGadget.h index f10e6df6377..2ca8670defa 100644 --- a/include/GafferUI/PlugGadget.h +++ b/include/GafferUI/PlugGadget.h @@ -94,7 +94,9 @@ class GAFFERUI_API PlugGadget : public ContainerGadget IE_CORE_DECLAREPTR( PlugGadget ) +[[deprecated("Use `PlugGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > PlugGadgetIterator; +[[deprecated("Use `PlugGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursivePlugGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/RotateHandle.h b/include/GafferUI/RotateHandle.h index cb9c8777938..3ca4efa0f58 100644 --- a/include/GafferUI/RotateHandle.h +++ b/include/GafferUI/RotateHandle.h @@ -97,7 +97,9 @@ class GAFFERUI_API RotateHandle : public Handle IE_CORE_DECLAREPTR( RotateHandle ) +[[deprecated("Use `RotateHandle::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > RotateHandleIterator; +[[deprecated("Use `RotateHandle::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveRotateHandleIterator; } // namespace GafferUI diff --git a/include/GafferUI/ScaleHandle.h b/include/GafferUI/ScaleHandle.h index 21327862ee0..870e2a8d3b9 100644 --- a/include/GafferUI/ScaleHandle.h +++ b/include/GafferUI/ScaleHandle.h @@ -77,7 +77,9 @@ class GAFFERUI_API ScaleHandle : public Handle IE_CORE_DECLAREPTR( ScaleHandle ) +[[deprecated("Use `ScaleHandle::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > ScaleHandleIterator; +[[deprecated("Use `ScaleHandle::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveScaleHandleIterator; } // namespace GafferUI diff --git a/include/GafferUI/SpacerGadget.h b/include/GafferUI/SpacerGadget.h index 9998ab527a1..f4cac864d40 100644 --- a/include/GafferUI/SpacerGadget.h +++ b/include/GafferUI/SpacerGadget.h @@ -72,7 +72,9 @@ class GAFFERUI_API SpacerGadget : public Gadget IE_CORE_DECLAREPTR( SpacerGadget ) +[[deprecated("Use `SpacerGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > SpacerGadgetIterator; +[[deprecated("Use `SpacerGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveSpacerGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/StandardConnectionGadget.h b/include/GafferUI/StandardConnectionGadget.h index 3fd855b7050..b24310d5d58 100644 --- a/include/GafferUI/StandardConnectionGadget.h +++ b/include/GafferUI/StandardConnectionGadget.h @@ -147,7 +147,9 @@ class GAFFERUI_API StandardConnectionGadget : public ConnectionGadget boost::signals::scoped_connection m_keyReleaseConnection; }; +[[deprecated("Use `StandardConnectionGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > StandardConnectionGadgetIterator; +[[deprecated("Use `StandardConnectionGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveStandardConnectionGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/StandardNodeGadget.h b/include/GafferUI/StandardNodeGadget.h index c6bee68d400..4982a52876e 100644 --- a/include/GafferUI/StandardNodeGadget.h +++ b/include/GafferUI/StandardNodeGadget.h @@ -166,7 +166,9 @@ class GAFFERUI_API StandardNodeGadget : public NodeGadget IE_CORE_DECLAREPTR( StandardNodeGadget ) +[[deprecated("Use `StandardNodeGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > StandardNodeGadgetIterator; +[[deprecated("Use `StandardNodeGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveStandardNodeGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/StandardNodule.h b/include/GafferUI/StandardNodule.h index ff0bc58be67..8a09b382281 100644 --- a/include/GafferUI/StandardNodule.h +++ b/include/GafferUI/StandardNodule.h @@ -109,7 +109,9 @@ class GAFFERUI_API StandardNodule : public Nodule IE_CORE_DECLAREPTR( StandardNodule ); +[[deprecated("Use `StandardNodule::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > StandardNoduleIterator; +[[deprecated("Use `StandardNodule::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveStandardNoduleIterator; } // namespace GafferUI diff --git a/include/GafferUI/StandardStyle.h b/include/GafferUI/StandardStyle.h index 32b3112da3e..da31a8f794d 100644 --- a/include/GafferUI/StandardStyle.h +++ b/include/GafferUI/StandardStyle.h @@ -92,8 +92,8 @@ class GAFFERUI_API StandardStyle : public Style Imath::V3f closestPointOnConnection( const Imath::V3f &p, const Imath::V3f &srcPosition, const Imath::V3f &srcTangent, const Imath::V3f &dstPosition, const Imath::V3f &dstTangent ) const override; void renderAuxiliaryConnection( const Imath::Box2f &srcNodeFrame, const Imath::Box2f &dstNodeFrame, State state ) const override; void renderAuxiliaryConnection( const Imath::V2f &srcPosition, const Imath::V2f &srcTangent, const Imath::V2f &dstPosition, const Imath::V2f &dstTangent, State state ) const override; - void renderBackdrop( const Imath::Box2f &box, State state = NormalState, const Imath::Color3f *userColor = nullptr ) const override; + Imath::V2f renderAnnotation( const Imath::V2f &origin, const std::string &text, State state = NormalState, const Imath::Color3f *userColor = nullptr ) const override; void renderTranslateHandle( Axes axes, State state = NormalState ) const override; void renderRotateHandle( Axes axes, State state = NormalState, const Imath::V3f &highlightVector = Imath::V3f( 0 ) ) const override; @@ -131,6 +131,8 @@ class GAFFERUI_API StandardStyle : public Style void renderConnectionInternal( const Imath::V3f &srcPosition, const Imath::V3f &srcTangent, const Imath::V3f &dstPosition, const Imath::V3f &dstTangent ) const; static unsigned int connectionDisplayList(); + void renderFrameInternal( const Imath::Box2f &contents, float padding, float borderWidth, const Imath::Color3f &userColor ) const; + static IECoreGL::Shader *shader(); static int g_borderParameter; static int g_borderRadiusParameter; diff --git a/include/GafferUI/Style.h b/include/GafferUI/Style.h index e3df1139f8e..66eaf1db054 100644 --- a/include/GafferUI/Style.h +++ b/include/GafferUI/Style.h @@ -133,8 +133,9 @@ class GAFFERUI_API Style : public IECore::RunTimeTyped virtual Imath::V3f closestPointOnConnection( const Imath::V3f &p, const Imath::V3f &srcPosition, const Imath::V3f &srcTangent, const Imath::V3f &dstPosition, const Imath::V3f &dstTangent ) const = 0; virtual void renderAuxiliaryConnection( const Imath::Box2f &srcNodeFrame, const Imath::Box2f &dstNodeFrame, State state ) const = 0; virtual void renderAuxiliaryConnection( const Imath::V2f &srcPosition, const Imath::V2f &srcTangent, const Imath::V2f &dstPosition, const Imath::V2f &dstTangent, State state ) const = 0; - virtual void renderBackdrop( const Imath::Box2f &box, State state = NormalState, const Imath::Color3f *userColor = nullptr ) const = 0; + /// Renders an annotation for a node, returning an origin suitable for rendering the next annotation. + virtual Imath::V2f renderAnnotation( const Imath::V2f &origin, const std::string &text, State state = NormalState, const Imath::Color3f *userColor = nullptr ) const = 0; //@} /// @name 3D UI elements diff --git a/include/GafferUI/TextGadget.h b/include/GafferUI/TextGadget.h index 17c16d6e783..e98c8bead89 100644 --- a/include/GafferUI/TextGadget.h +++ b/include/GafferUI/TextGadget.h @@ -74,7 +74,9 @@ class GAFFERUI_API TextGadget : public Gadget IE_CORE_DECLAREPTR( TextGadget ); +[[deprecated("Use `TextGadget::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > TextGadgetIterator; +[[deprecated("Use `TextGadget::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveTextGadgetIterator; } // namespace GafferUI diff --git a/include/GafferUI/TranslateHandle.h b/include/GafferUI/TranslateHandle.h index 145ca42f8c8..79400a9dee6 100644 --- a/include/GafferUI/TranslateHandle.h +++ b/include/GafferUI/TranslateHandle.h @@ -84,7 +84,9 @@ class GAFFERUI_API TranslateHandle : public Handle IE_CORE_DECLAREPTR( TranslateHandle ) +[[deprecated("Use `TranslateHandle::Iterator` instead")]] typedef Gaffer::FilteredChildIterator > TranslateHandleIterator; +[[deprecated("Use `TranslateHandle::RecursiveIterator` instead")]] typedef Gaffer::FilteredRecursiveChildIterator > RecursiveTranslateHandleIterator; } // namespace GafferUI diff --git a/include/GafferVDB/Interrupter.h b/include/GafferVDB/Interrupter.h index 7095e6643df..795761a8b30 100644 --- a/include/GafferVDB/Interrupter.h +++ b/include/GafferVDB/Interrupter.h @@ -47,8 +47,7 @@ class Interrupter { public: Interrupter(const IECore::Canceller *canceller) - : m_canceller(canceller), - m_interrupted(false) + : m_canceller( canceller ) { } @@ -62,31 +61,15 @@ class Interrupter { bool wasInterrupted( int percent = -1 ) { - if ( m_interrupted ) - { - return true; - } - - // todo this a a problem installing a exception handler - // per a call to this function. - try - { - IECore::Canceller::check( m_canceller ); - } - catch( const IECore::Cancelled& ) - { - m_interrupted = true; - } - - return m_interrupted; + return m_canceller && m_canceller->cancelled(); } + private: + const IECore::Canceller* m_canceller; - bool m_interrupted; }; } // namespace GafferVDB #endif // GAFFERVDB_INTERRUPTER_H - diff --git a/include/GafferVDB/LevelSetOffset.h b/include/GafferVDB/LevelSetOffset.h index df4430ba702..61ba43593cf 100644 --- a/include/GafferVDB/LevelSetOffset.h +++ b/include/GafferVDB/LevelSetOffset.h @@ -40,7 +40,7 @@ #include "GafferVDB/Export.h" #include "GafferVDB/TypeIds.h" -#include "GafferScene/SceneElementProcessor.h" +#include "GafferScene/Deformer.h" #include "Gaffer/NumericPlug.h" #include "Gaffer/StringPlug.h" @@ -48,7 +48,7 @@ namespace GafferVDB { -class GAFFERVDB_API LevelSetOffset : public GafferScene::SceneElementProcessor +class GAFFERVDB_API LevelSetOffset : public GafferScene::Deformer { public : @@ -56,7 +56,7 @@ class GAFFERVDB_API LevelSetOffset : public GafferScene::SceneElementProcessor LevelSetOffset(const std::string &name = defaultName() ); ~LevelSetOffset() override; - GAFFER_NODE_DECLARE_TYPE( GafferVDB::LevelSetOffset, LevelSetOffsetTypeId, GafferScene::SceneElementProcessor ); + GAFFER_NODE_DECLARE_TYPE( GafferVDB::LevelSetOffset, LevelSetOffsetTypeId, GafferScene::Deformer ); Gaffer::StringPlug *gridPlug(); const Gaffer::StringPlug *gridPlug() const; @@ -64,17 +64,15 @@ class GAFFERVDB_API LevelSetOffset : public GafferScene::SceneElementProcessor Gaffer::FloatPlug *offsetPlug(); const Gaffer::FloatPlug *offsetPlug() const; - void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; - protected : - bool processesObject() const override; + 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, IECore::ConstObjectPtr inputObject ) const override; + IECore::ConstObjectPtr computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const override; - bool processesBound() const override; - void hashProcessedBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeProcessedBound( const ScenePath &path, const Gaffer::Context *context, const Imath::Box3f &inputBound ) const override; + bool affectsProcessedObjectBound( const Gaffer::Plug *input ) const override; + void hashProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; + Imath::Box3f computeProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context ) const override; private: diff --git a/include/GafferVDB/LevelSetToMesh.h b/include/GafferVDB/LevelSetToMesh.h index cf13b53b7b7..afc7603ccab 100644 --- a/include/GafferVDB/LevelSetToMesh.h +++ b/include/GafferVDB/LevelSetToMesh.h @@ -40,7 +40,7 @@ #include "GafferVDB/Export.h" #include "GafferVDB/TypeIds.h" -#include "GafferScene/SceneElementProcessor.h" +#include "GafferScene/Deformer.h" #include "Gaffer/NumericPlug.h" #include "Gaffer/StringPlug.h" @@ -48,7 +48,7 @@ namespace GafferVDB { -class GAFFERVDB_API LevelSetToMesh : public GafferScene::SceneElementProcessor +class GAFFERVDB_API LevelSetToMesh : public GafferScene::Deformer { public : @@ -56,7 +56,7 @@ class GAFFERVDB_API LevelSetToMesh : public GafferScene::SceneElementProcessor LevelSetToMesh( const std::string &name=defaultName() ); ~LevelSetToMesh() override; - GAFFER_NODE_DECLARE_TYPE( GafferVDB::LevelSetToMesh, LevelSetToMeshTypeId, GafferScene::SceneElementProcessor ); + GAFFER_NODE_DECLARE_TYPE( GafferVDB::LevelSetToMesh, LevelSetToMeshTypeId, GafferScene::Deformer ); Gaffer::StringPlug *gridPlug(); const Gaffer::StringPlug *gridPlug() const; @@ -67,17 +67,11 @@ class GAFFERVDB_API LevelSetToMesh : public GafferScene::SceneElementProcessor Gaffer::FloatPlug *adaptivityPlug(); const Gaffer::FloatPlug *adaptivityPlug() const; - void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; - protected : - bool processesObject() const override; + 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, IECore::ConstObjectPtr inputObject ) const override; - - bool processesBound() const override; - void hashProcessedBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const override; - Imath::Box3f computeProcessedBound( const ScenePath &path, const Gaffer::Context *context, const Imath::Box3f &inputBound ) const override; + IECore::ConstObjectPtr computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const override; private : diff --git a/include/GafferVDB/MeshToLevelSet.h b/include/GafferVDB/MeshToLevelSet.h index 192ef0712ce..5f896a4b7a6 100644 --- a/include/GafferVDB/MeshToLevelSet.h +++ b/include/GafferVDB/MeshToLevelSet.h @@ -40,7 +40,7 @@ #include "GafferVDB/Export.h" #include "GafferVDB/TypeIds.h" -#include "GafferScene/SceneElementProcessor.h" +#include "GafferScene/ObjectProcessor.h" #include "Gaffer/NumericPlug.h" @@ -52,7 +52,7 @@ class StringPlug; namespace GafferVDB { -class GAFFERVDB_API MeshToLevelSet : public GafferScene::SceneElementProcessor +class GAFFERVDB_API MeshToLevelSet : public GafferScene::ObjectProcessor { public : @@ -60,7 +60,7 @@ class GAFFERVDB_API MeshToLevelSet : public GafferScene::SceneElementProcessor MeshToLevelSet( const std::string &name=defaultName() ); ~MeshToLevelSet() override; - GAFFER_NODE_DECLARE_TYPE( GafferVDB::MeshToLevelSet, MeshToLevelSetTypeId, GafferScene::SceneElementProcessor ); + GAFFER_NODE_DECLARE_TYPE( GafferVDB::MeshToLevelSet, MeshToLevelSetTypeId, GafferScene::ObjectProcessor ); Gaffer::StringPlug *gridPlug(); const Gaffer::StringPlug *gridPlug() const; @@ -74,13 +74,11 @@ class GAFFERVDB_API MeshToLevelSet : public GafferScene::SceneElementProcessor Gaffer::FloatPlug *interiorBandwidthPlug(); const Gaffer::FloatPlug *interiorBandwidthPlug() const; - void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; - protected : - bool processesObject() const override; + bool affectsProcessedObject( const Gaffer::Plug *plug ) 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, IECore::ConstObjectPtr inputObject ) const override; + IECore::ConstObjectPtr computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const override; private : diff --git a/include/GafferVDB/PointsGridToPoints.h b/include/GafferVDB/PointsGridToPoints.h index b3e16ecc36f..b84195280f9 100644 --- a/include/GafferVDB/PointsGridToPoints.h +++ b/include/GafferVDB/PointsGridToPoints.h @@ -40,7 +40,7 @@ #include "GafferVDB/Export.h" #include "GafferVDB/TypeIds.h" -#include "GafferScene/SceneElementProcessor.h" +#include "GafferScene/ObjectProcessor.h" #include "Gaffer/NumericPlug.h" @@ -52,7 +52,7 @@ class StringPlug; namespace GafferVDB { -class GAFFERVDB_API PointsGridToPoints : public GafferScene::SceneElementProcessor +class GAFFERVDB_API PointsGridToPoints : public GafferScene::ObjectProcessor { public : @@ -60,7 +60,7 @@ class GAFFERVDB_API PointsGridToPoints : public GafferScene::SceneElementProcess PointsGridToPoints( const std::string &name=defaultName() ); ~PointsGridToPoints() override; - GAFFER_NODE_DECLARE_TYPE( GafferVDB::PointsGridToPoints, PointsGridToPointsId, GafferScene::SceneElementProcessor ); + GAFFER_NODE_DECLARE_TYPE( GafferVDB::PointsGridToPoints, PointsGridToPointsId, GafferScene::ObjectProcessor ); Gaffer::StringPlug *gridPlug(); const Gaffer::StringPlug *gridPlug() const; @@ -71,13 +71,11 @@ class GAFFERVDB_API PointsGridToPoints : public GafferScene::SceneElementProcess Gaffer::BoolPlug *invertNamesPlug(); const Gaffer::BoolPlug *invertNamesPlug() const; - void affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const override; - protected : - bool processesObject() const override; + 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, IECore::ConstObjectPtr inputObject ) const override; + IECore::ConstObjectPtr computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const override; private : diff --git a/python/Gaffer/ExtensionAlgo.py b/python/Gaffer/ExtensionAlgo.py index 5f5cbb60f9c..e9c18a2af1c 100644 --- a/python/Gaffer/ExtensionAlgo.py +++ b/python/Gaffer/ExtensionAlgo.py @@ -237,4 +237,3 @@ def walkPlugs( graphComponent ) : return "plugs = {\n\n" + __indent( "\n".join( items ), 1 ) + "\n}\n" else : return "" - diff --git a/python/Gaffer/PythonExpressionEngine.py b/python/Gaffer/PythonExpressionEngine.py index 48060bf0ef1..24844ed7566 100644 --- a/python/Gaffer/PythonExpressionEngine.py +++ b/python/Gaffer/PythonExpressionEngine.py @@ -93,7 +93,11 @@ def execute( self, context, inputs ) : plugPathSplit = plugPath.split( "." ) for p in plugPathSplit[:-1] : parentDict = parentDict[p] - result.append( parentDict.get( plugPathSplit[-1], IECore.NullObject.defaultNullObject() ) ) + r = parentDict.get( plugPathSplit[-1], IECore.NullObject.defaultNullObject() ) + try: + result.append( r ) + except: + raise TypeError( "Unsupported type for result \"%s\" for expression output \"%s\"" % ( str( r ), plugPath ) ) return result @@ -434,4 +438,3 @@ def __getattr__( self, name ) : return getattr( self.__context, name ) else : raise AttributeError( name ) - diff --git a/python/Gaffer/_Range.py b/python/Gaffer/_Range.py index 50e272086e7..f26eac5127a 100644 --- a/python/Gaffer/_Range.py +++ b/python/Gaffer/_Range.py @@ -94,4 +94,3 @@ def __recursivePlugRange( cls, parent, direction = None ) : Gaffer.Plug.RecursiveRange = classmethod( __recursivePlugRange ) Gaffer.Plug.RecursiveInputRange = classmethod( functools.partial( __recursivePlugRange, direction = Gaffer.Plug.Direction.In ) ) Gaffer.Plug.RecursiveOutputRange = classmethod( functools.partial( __recursivePlugRange, direction = Gaffer.Plug.Direction.Out ) ) - diff --git a/python/GafferAppleseedUI/AppleseedAttributesUI.py b/python/GafferAppleseedUI/AppleseedAttributesUI.py index df408a89bf1..27ac0114ef5 100644 --- a/python/GafferAppleseedUI/AppleseedAttributesUI.py +++ b/python/GafferAppleseedUI/AppleseedAttributesUI.py @@ -67,7 +67,7 @@ def __shadingSummary( plug ) : info.append( "Shading Samples %d" % plug["shadingSamples"]["value"].getValue() ) if plug["mediumPriority"]["enabled"].getValue() : - info.append( "Medium Priority %d" % plug["mediumPriority"]["value"].getValue() ) + info.append( "Medium Priority %d" % plug["mediumPriority"]["value"].getValue() ) if plug["doubleSided"]["enabled"].getValue() : info.append( "Double Sided %d" % plug["doubleSided"]["value"].getValue() ) diff --git a/python/GafferArnold/ArnoldTextureBake.py b/python/GafferArnold/ArnoldTextureBake.py index 52a9850be39..3f67103616c 100644 --- a/python/GafferArnold/ArnoldTextureBake.py +++ b/python/GafferArnold/ArnoldTextureBake.py @@ -361,7 +361,6 @@ def __init__( self, name = "ArnoldTextureBake" ) : self["__OptionOverrides"]["options"]["resolutionMultiplier"]["enabled"].setValue( True ) self["__OptionOverrides"]["options"]["overscan"]["enabled"].setValue( True ) self["__OptionOverrides"]["options"]["renderCropWindow"]["enabled"].setValue( True ) - self["__OptionOverrides"]["options"]["cameraBlur"]["enabled"].setValue( True ) self["__OptionOverrides"]["options"]["transformBlur"]["enabled"].setValue( True ) self["__OptionOverrides"]["options"]["deformationBlur"]["enabled"].setValue( True ) @@ -608,4 +607,3 @@ def __init__( self, name = "ArnoldTextureBake" ) : IECore.registerRunTimeTyped( ArnoldTextureBake, typeName = "GafferArnold::ArnoldTextureBake" ) - diff --git a/python/GafferArnoldTest/ArnoldRenderTest.py b/python/GafferArnoldTest/ArnoldRenderTest.py index c85c9a6ade0..bdde158f54e 100644 --- a/python/GafferArnoldTest/ArnoldRenderTest.py +++ b/python/GafferArnoldTest/ArnoldRenderTest.py @@ -72,7 +72,7 @@ def tearDown( self ) : GafferSceneTest.SceneTestCase.tearDown( self ) - GafferScene.RendererAlgo.deregisterAdaptor( "Test" ) + GafferScene.SceneAlgo.deregisterRenderAdaptor( "Test" ) def testExecute( self ) : @@ -822,7 +822,7 @@ def a() : return result - GafferScene.RendererAlgo.registerAdaptor( "Test", a ) + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", a ) sphere = GafferScene.Sphere() @@ -1395,7 +1395,7 @@ def testEncapsulateDeformationBlur( self ) : # The `maxDifference` is huge to account for noise and watermarks, but is still low enough to check what # we want, since if the Encapsulate was sampled at shutter open and not the frame, the difference would be # 0.5. - self.assertImagesEqual( s["deformationOff"]["out"], s["deformationOn"]["out"], maxDifference = 0.25, ignoreMetadata = True ) + self.assertImagesEqual( s["deformationOff"]["out"], s["deformationOn"]["out"], maxDifference = 0.27, ignoreMetadata = True ) if __name__ == "__main__": unittest.main() diff --git a/python/GafferArnoldTest/IECoreArnoldPreviewTest/RendererTest.py b/python/GafferArnoldTest/IECoreArnoldPreviewTest/RendererTest.py index 599d5291291..e6b8cbd7e2b 100644 --- a/python/GafferArnoldTest/IECoreArnoldPreviewTest/RendererTest.py +++ b/python/GafferArnoldTest/IECoreArnoldPreviewTest/RendererTest.py @@ -2591,6 +2591,78 @@ def render( self, renderer ) : self.assertIsNone( arnold.AiNodeLookUpUserParameter( sphere, "user:a" ) ) self.assertIsNone( arnold.AiNodeLookUpUserParameter( sphere, "user:b" ) ) + def testProceduralWithCurves( self ) : + + class CurvesProcedural( GafferScene.Private.IECoreScenePreview.Procedural ) : + + def __init__( self, minPixelWidth ) : + + GafferScene.Private.IECoreScenePreview.Procedural.__init__( self ) + + self.__minPixelWidth = minPixelWidth + + def render( self, renderer ) : + + curves = IECoreScene.CurvesPrimitive( + IECore.IntVectorData( [ 4 ] ), + IECore.CubicBasisf.catmullRom(), + False, + IECore.V3fVectorData( [ + imath.V3f( 0, -1, 0 ), + imath.V3f( 0, -1, 0 ), + imath.V3f( 0, 1, 0 ), + imath.V3f( 0, 1, 0 ), + ] ) + ) + + renderer.object( + "/curves", + curves, + renderer.attributes( IECore.CompoundObject( { + "ai:curves:min_pixel_width" : IECore.FloatData( self.__minPixelWidth ) + } ) ), + ) + + IECore.registerRunTimeTyped( CurvesProcedural ) + + # We repeat the test with and without a min pixel width. + # This exercises the instanced and non-instanced code paths + # in the procedural renderer backend. + for minPixelWidth in ( 0.0, 1.0 ) : + + renderer = GafferScene.Private.IECoreScenePreview.Renderer.create( + "Arnold", + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch, + ) + + renderer.object( + "/procedural", + CurvesProcedural( minPixelWidth ), + renderer.attributes( IECore.CompoundObject() ) + ).transform( imath.M44f().translate( imath.V3f( 0, 0, -1 ) ) ) + + outputFileName = os.path.join( self.temporaryDirectory(), "beauty.exr" ) + renderer.output( + "test", + IECoreScene.Output( + outputFileName, + "exr", + "rgba", + { + } + ) + ) + + renderer.render() + del renderer + + # We should find the curves visible in the centre pixel. + image = IECoreImage.ImageReader( outputFileName ).read() + dimensions = image.dataWindow.size() + imath.V2i( 1 ) + centreIndex = dimensions.x * int( dimensions.y * 0.5 ) + int( dimensions.x * 0.5 ) + color = imath.Color3f( image["R"][centreIndex], image["G"][centreIndex], image["B"][centreIndex] ) + self.assertEqual( color, imath.Color3f( 1 ) ) + @staticmethod def __aovShaders() : diff --git a/python/GafferArnoldUI/ArnoldMeshLightUI.py b/python/GafferArnoldUI/ArnoldMeshLightUI.py index 8b31ce7010a..b12f6e94c1a 100644 --- a/python/GafferArnoldUI/ArnoldMeshLightUI.py +++ b/python/GafferArnoldUI/ArnoldMeshLightUI.py @@ -64,6 +64,8 @@ def __shaderMetadata( plug, name ) : rays. """, + "nameValuePlugPlugValueWidget:ignoreNamePlug", True, + ], "parameters" : [ diff --git a/python/GafferArnoldUI/ArnoldOptionsUI.py b/python/GafferArnoldUI/ArnoldOptionsUI.py index 2efc22979c2..9b11b6ff828 100644 --- a/python/GafferArnoldUI/ArnoldOptionsUI.py +++ b/python/GafferArnoldUI/ArnoldOptionsUI.py @@ -1135,4 +1135,3 @@ def __gpuSummary( plug ) : } ) - diff --git a/python/GafferArnoldUI/ArnoldTextureBakeUI.py b/python/GafferArnoldUI/ArnoldTextureBakeUI.py index aa4b857e6ec..7375b2af519 100644 --- a/python/GafferArnoldUI/ArnoldTextureBakeUI.py +++ b/python/GafferArnoldUI/ArnoldTextureBakeUI.py @@ -167,7 +167,6 @@ "divider", True, ], - "applyMedianFilter" : [ "description", """ @@ -183,7 +182,6 @@ "layout:activator", "medianActivator", ], - } ) diff --git a/python/GafferArnoldUI/CacheMenu.py b/python/GafferArnoldUI/CacheMenu.py index 9b7cabf3300..876cc326f1a 100644 --- a/python/GafferArnoldUI/CacheMenu.py +++ b/python/GafferArnoldUI/CacheMenu.py @@ -56,4 +56,3 @@ def appendDefinitions( menuDefinition, prefix="" ) : "command" : functools.partial( GafferArnold.InteractiveArnoldRender.flushCaches, flags ), } ) - diff --git a/python/GafferArnoldUITest/ArnoldShaderUITest.py b/python/GafferArnoldUITest/ArnoldShaderUITest.py index 5b079951a18..6e84583d682 100644 --- a/python/GafferArnoldUITest/ArnoldShaderUITest.py +++ b/python/GafferArnoldUITest/ArnoldShaderUITest.py @@ -184,7 +184,7 @@ def testUserDefaultMetadata( self ) : # SolidAngle does not appear to have wrapped AiMetaDataGetRGBA in Python, so we don't # support the RGBA case - #self.assertEqual( parms["missing_texture_color"].value, imath.Color4f( 12, 13, 14, 15 ) ) + #self.assertEqual( parms["missing_texture_color"].value, imath.Color4f( 12, 13, 14, 15 ) ) self.assertEqual( parms["uvcoords"].value, imath.V2f( 12, 13 ) ) self.assertEqual( parms["filename"].value, "overrideUserDefault" ) diff --git a/python/GafferCortexUI/CompoundParameterValueWidget.py b/python/GafferCortexUI/CompoundParameterValueWidget.py index 24fd8e2aa4f..e2158f347d2 100644 --- a/python/GafferCortexUI/CompoundParameterValueWidget.py +++ b/python/GafferCortexUI/CompoundParameterValueWidget.py @@ -125,7 +125,7 @@ def _parameterHandler( self ) : def _parameterLabelText( self, parameterHandler ) : - return IECore.CamelCase.toSpaced( parameterHandler.plug().getName() ) + return IECore.CamelCase.toSpaced( parameterHandler.plug().getName() ) def _parameterToolTip( self, parameterHandler ) : diff --git a/python/GafferDispatchTest/DispatcherTest.py b/python/GafferDispatchTest/DispatcherTest.py index 2a398a7f81c..e411c3d707f 100644 --- a/python/GafferDispatchTest/DispatcherTest.py +++ b/python/GafferDispatchTest/DispatcherTest.py @@ -1830,5 +1830,56 @@ def testTaskPlugsWithoutTaskNodes( self ) : with six.assertRaisesRegex( self, RuntimeError, "TaskPlug \"ScriptNode.badNode.task\" has no TaskNode" ) : dispatcher.dispatch( [ s["taskList"] ] ) + def testContextDrivingDispatcherPlugs( self ) : + + # a per-frame task with driven dispatcher plugs + # | + # v creates context variables that drive dispatcher plugs on `a` + # | + # b per-frame task with normal dispatcher plugs + + s = Gaffer.ScriptNode() + + log = [] + s["a"] = GafferDispatchTest.LoggingTaskNode( log = log ) + s["a"]["f"] = Gaffer.StringPlug( defaultValue = "####" ) + + s["cv"] = Gaffer.ContextVariables() + s["cv"].setup( s["a"]["task"] ) + s["cv"]["in"].setInput( s["a"]["task"] ) + s["cv"]["variables"].addChild( Gaffer.NameValuePlug( "test", 2 ) ) + + s["b"] = GafferDispatchTest.LoggingTaskNode( log = log ) + s["b"]["preTasks"][0].setInput( s["cv"]["out"] ) + s["b"]["f"] = Gaffer.StringPlug( defaultValue = "####" ) + + dispatcher = GafferDispatch.Dispatcher.create( "testDispatcher" ) + dispatcher["framesMode"].setValue( GafferDispatch.Dispatcher.FramesMode.CustomRange ) + dispatcher["frameRange"].setValue( "1-4" ) + + # batchSize + s["e"] = Gaffer.Expression() + s["e"].setExpression( "parent['a']['dispatcher']['batchSize'] = context.get( 'test', 1 )", "python" ) + dispatcher.dispatch( [ s["b"] ] ) + self.assertEqual( [ l.node.getName() for l in log ], [ "a", "a", "b", "b", "a", "a", "b", "b" ] ) + self.assertEqual( [ l.context.getFrame() for l in log ], [ 1, 2, 1, 2, 3, 4, 3, 4 ] ) + + # immediate + del log[:] + s["cv"]["variables"].addChild( Gaffer.NameValuePlug( "testBool", True ) ) + s["e"].setExpression( "parent['a']['dispatcher']['immediate'] = context.get( 'testBool', False )", "python" ) + dispatcher.dispatch( [ s["b"] ] ) + self.assertEqual( [ l.node.getName() for l in log ], [ "a", "a", "a", "a", "b", "b", "b", "b" ] ) + self.assertEqual( [ l.context.getFrame() for l in log ], [ 1, 2, 3, 4, 1, 2, 3, 4 ] ) + + # requiresSequenceExecution + del log[:] + s["cv"]["variables"].addChild( Gaffer.NameValuePlug( "testBool", True ) ) + s["e"].setExpression( "parent['a']['requiresSequenceExecution'] = context.get( 'testBool', False )", "python" ) + dispatcher.dispatch( [ s["b"] ] ) + self.assertEqual( [ l.node.getName() for l in log ], [ "a", "b", "b", "b", "b" ] ) + self.assertEqual( log[0].frames, [ 1, 2, 3, 4 ] ) + self.assertEqual( [ l.context.getFrame() for l in log[1:] ], [ 1, 2, 3, 4 ] ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferDispatchTest/PythonCommandTest.py b/python/GafferDispatchTest/PythonCommandTest.py index 136a801abf5..b5d262668d6 100644 --- a/python/GafferDispatchTest/PythonCommandTest.py +++ b/python/GafferDispatchTest/PythonCommandTest.py @@ -278,7 +278,7 @@ def testSequenceModeVariable( self ) : s["n"]["command"].setValue( "\n".join( commandLines ) ) d = self.__dispatcher( frameRange = "1-5" ) - six.assertRaisesRegex( self, Exception, "Context has no entry named \"frame\"", d.dispatch, [ s[ "n" ] ] ) + six.assertRaisesRegex( self, Exception, "Context has no variable named \"frame\"", d.dispatch, [ s[ "n" ] ] ) commandLines = inspect.cleandoc( """ diff --git a/python/GafferDispatchUI/DispatcherUI.py b/python/GafferDispatchUI/DispatcherUI.py index 3454a5febfd..94ec0e44bbb 100644 --- a/python/GafferDispatchUI/DispatcherUI.py +++ b/python/GafferDispatchUI/DispatcherUI.py @@ -134,13 +134,22 @@ plugs = { + "dispatcher" : ( + + "layout:activator:doesNotRequireSequenceExecution", lambda plug : not plug.node()["task"].requiresSequenceExecution(), + + ), + "dispatcher.batchSize" : ( "description", """ Maximum number of frames to batch together when dispatching tasks. + If the node requires sequence execution `batchSize` will be ignored. """, + "layout:activator", "doesNotRequireSequenceExecution", + ), "dispatcher.immediate" : ( diff --git a/python/GafferImageTest/ContextSanitiserTest.py b/python/GafferImageTest/ContextSanitiserTest.py new file mode 100644 index 00000000000..f0829080f65 --- /dev/null +++ b/python/GafferImageTest/ContextSanitiserTest.py @@ -0,0 +1,83 @@ +########################################################################## +# +# Copyright (c) 2021, 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 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 imath +import six + +import IECore + +import Gaffer +import GafferImage +import GafferImageTest + +class ContextSanitiserTest( GafferImageTest.ImageTestCase ) : + + def test( self ) : + + constant = GafferImage.Constant() + + # A ContextSanitiser is automatically hooked up by ImageTestCase.setUp, so + # we don't need to explicitly set one up + with IECore.CapturingMessageHandler() as mh : + with Gaffer.Context() as c : + + c["image:channelName"] = IECore.StringData( "R" ) + c["image:tileOrigin"] = IECore.V2iData( imath.V2i( 0, 0 ) ) + + constant["out"]["metadata"].getValue() + constant["out"]["sampleOffsets"].getValue() + constant["out"]["channelData"].getValue() + + c["image:channelName"] = IECore.IntData( 5 ) + + with six.assertRaisesRegex( self, IECore.Exception, 'Context variable is not of type "StringData"' ) : + constant["out"]["metadata"].getValue() + + for message in mh.messages : + self.assertEqual( message.level, mh.Level.Warning ) + self.assertEqual( message.context, "ContextSanitiser" ) + + self.assertEqual( + [ m.message for m in mh.messages ], + [ + 'image:channelName in context for Constant.out.metadata computeNode:hash', + 'image:tileOrigin in context for Constant.out.metadata computeNode:hash', + 'image:channelName in context for Constant.out.sampleOffsets computeNode:compute' + ] + ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferImageTest/DeepStateTest.py b/python/GafferImageTest/DeepStateTest.py index 0cce5ee8023..bf8ffac4f8a 100644 --- a/python/GafferImageTest/DeepStateTest.py +++ b/python/GafferImageTest/DeepStateTest.py @@ -1029,4 +1029,3 @@ def testMissingChannels( self ) : if __name__ == "__main__": unittest.main() - diff --git a/python/GafferImageTest/DisplayTest.py b/python/GafferImageTest/DisplayTest.py index ac0c0b7c5fc..afe336b0a47 100644 --- a/python/GafferImageTest/DisplayTest.py +++ b/python/GafferImageTest/DisplayTest.py @@ -249,7 +249,7 @@ def testSetDriver( self ) : driversCreated = GafferTest.CapturingSlot( GafferImage.Display.driverCreatedSignal() ) server = IECoreImage.DisplayDriverServer() - dataWindow = imath.Box2i( imath.V2i( 0 ), imath.V2i( 100 ) ) + dataWindow = imath.Box2i( imath.V2i( 0 ), imath.V2i( GafferImage.ImagePlug.tileSize() ) ) driver = self.Driver( GafferImage.Format( dataWindow ), diff --git a/python/GafferImageTest/FormatDataTest.py b/python/GafferImageTest/FormatDataTest.py index 0caa223cd16..71c72163e80 100644 --- a/python/GafferImageTest/FormatDataTest.py +++ b/python/GafferImageTest/FormatDataTest.py @@ -99,5 +99,8 @@ def testStoreInContext( self ) : c["f"] = d self.assertEqual( c["f"], d ) + def testEditableScopeForFormat( self ) : + GafferImageTest.testEditableScopeForFormat() + if __name__ == "__main__": unittest.main() diff --git a/python/GafferImageTest/GradeTest.py b/python/GafferImageTest/GradeTest.py index 644a4ca131a..8dcfb878d89 100644 --- a/python/GafferImageTest/GradeTest.py +++ b/python/GafferImageTest/GradeTest.py @@ -308,4 +308,3 @@ def testUnpremultiplied( self ) : defaultGrade["gamma"].setValue( imath.Color4f( 2, 2, 2, 1.0 ) ) self.assertImagesEqual( unpremultipliedGrade["out"], defaultGrade["out"] ) - diff --git a/python/GafferImageTest/ImageReaderTest.py b/python/GafferImageTest/ImageReaderTest.py index 3f42eafbf09..6ad9975bfb4 100644 --- a/python/GafferImageTest/ImageReaderTest.py +++ b/python/GafferImageTest/ImageReaderTest.py @@ -53,6 +53,7 @@ class ImageReaderTest( GafferImageTest.ImageTestCase ) : colorSpaceFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/circles_as_cineon.exr" ) offsetDataWindowFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) jpgFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/circles.jpg" ) + largeFileName = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/colorbars_max_clamp.exr" ) def setUp( self ) : @@ -91,7 +92,7 @@ def testUnspecifiedFilename( self ) : def testChannelDataHashes( self ) : # Test that two tiles within the same image have different hashes. n = GafferImage.ImageReader() - n["fileName"].setValue( self.fileName ) + n["fileName"].setValue( self.largeFileName ) h1 = n["out"].channelData( "R", imath.V2i( 0 ) ).hash() h2 = n["out"].channelData( "R", imath.V2i( GafferImage.ImagePlug().tileSize() ) ).hash() diff --git a/python/GafferImageTest/ImageTestCase.py b/python/GafferImageTest/ImageTestCase.py index ac12a0c4b2f..c1848dc17f5 100644 --- a/python/GafferImageTest/ImageTestCase.py +++ b/python/GafferImageTest/ImageTestCase.py @@ -155,4 +155,3 @@ def assertRaisesDeepNotSupported( self, node ) : deep = GafferImage.Empty() node["in"].setInput( deep["out"] ) six.assertRaisesRegex( self, RuntimeError, 'Deep data not supported in input "in*', GafferImage.ImageAlgo.image, node["out"] ) - diff --git a/python/GafferImageTest/ImageWriterTest.py b/python/GafferImageTest/ImageWriterTest.py index 37f90e1d8f6..9c14f36b571 100644 --- a/python/GafferImageTest/ImageWriterTest.py +++ b/python/GafferImageTest/ImageWriterTest.py @@ -393,7 +393,7 @@ def testDeepWrite( self ) : self.assertEqual( reRead["out"].dataWindow(), onePixelDataWindow ) emptyPixelData = IECore.CompoundObject() - emptyPixelData["tileOrigins"] = IECore.V2iVectorData( [ imath.V2i( 0, 64 ) ] ) + emptyPixelData["tileOrigins"] = IECore.V2iVectorData( [ GafferImage.ImagePlug.tileOrigin( imath.V2i( 0, 99 ) ) ] ) emptyPixelData["sampleOffsets"] = IECore.ObjectVector( [ GafferImage.ImagePlug.emptyTileSampleOffsets() ] ) for channel in [ "R", "G","B", "A", "Z", "ZBack" ]: emptyPixelData[channel] = IECore.ObjectVector( [ IECore.FloatVectorData() ] ) diff --git a/python/GafferImageTest/ResizeTest.py b/python/GafferImageTest/ResizeTest.py index 7eef35f4f3c..387e827611a 100644 --- a/python/GafferImageTest/ResizeTest.py +++ b/python/GafferImageTest/ResizeTest.py @@ -182,11 +182,11 @@ def testDataWindowRounding( self ) : def testFilterAffectsChannelData( self ) : - r = GafferImage.Resize() - cs = GafferTest.CapturingSlot( r.plugDirtiedSignal() ) - r["filter"].setValue( "gaussian" ) + r = GafferImage.Resize() + cs = GafferTest.CapturingSlot( r.plugDirtiedSignal() ) + r["filter"].setValue( "gaussian" ) - self.assertTrue( r["out"]["channelData"] in set( c[0] for c in cs ) ) + self.assertTrue( r["out"]["channelData"] in set( c[0] for c in cs ) ) def testSamplerBoundsViolationCrash( self ) : diff --git a/python/GafferImageTest/__init__.py b/python/GafferImageTest/__init__.py index e67a6ef3658..da80c61d35e 100644 --- a/python/GafferImageTest/__init__.py +++ b/python/GafferImageTest/__init__.py @@ -102,6 +102,7 @@ from .EmptyTest import EmptyTest from .DeepHoldoutTest import DeepHoldoutTest from .DeepRecolorTest import DeepRecolorTest +from .ContextSanitiserTest import ContextSanitiserTest if __name__ == "__main__": import unittest diff --git a/python/GafferImageUI/DilateUI.py b/python/GafferImageUI/DilateUI.py index 8827b1ff1b8..8c179fcaf1d 100644 --- a/python/GafferImageUI/DilateUI.py +++ b/python/GafferImageUI/DilateUI.py @@ -55,4 +55,3 @@ def nodeMenuCreateCommand( menu ) : """, ) - diff --git a/python/GafferImageUI/ImageViewUI.py b/python/GafferImageUI/ImageViewUI.py index e1aacb73be1..709ac0f03ca 100644 --- a/python/GafferImageUI/ImageViewUI.py +++ b/python/GafferImageUI/ImageViewUI.py @@ -45,7 +45,8 @@ import GafferImage import GafferImageUI - +from Qt import QtGui +from Qt import QtWidgets ########################################################################## # Metadata registration. @@ -73,7 +74,7 @@ "toolbarLayout:customWidget:StateWidgetBalancingSpacer:section", "Top", "toolbarLayout:customWidget:StateWidgetBalancingSpacer:index", -1, - "toolbarLayout:customWidget:BottomRightSpacer:widgetType", "GafferImageUI.ImageViewUI._Spacer", + "toolbarLayout:customWidget:BottomRightSpacer:widgetType", "GafferImageUI.ImageViewUI._ExpandingSpacer", "toolbarLayout:customWidget:BottomRightSpacer:section", "Bottom", "toolbarLayout:customWidget:BottomRightSpacer:index", 2, @@ -156,12 +157,29 @@ "colorInspector" : [ - - "plugValueWidget:type", "GafferImageUI.ImageViewUI._ColorInspectorPlugValueWidget", - "label", "", + "plugValueWidget:type", "GafferUI.LayoutPlugValueWidget", "toolbarLayout:section", "Bottom", "toolbarLayout:index", 1, + ], + "colorInspector.evaluator" : [ + "plugValueWidget:type", "", + ], + + "colorInspector.inspectors" : [ + "plugValueWidget:type", "GafferImageUI.ImageViewUI._ColorInspectorsPlugValueWidget", + "label", "", + ], + + "colorInspector.inspectors.*" : [ + "description", + """ + Display the value of the pixel under the cursor. Ctrl-click to add an inspector to a pixel, or + Ctrl-drag to create an area inspector. Display shows value of each channel, hue/saturation/value, and Exposure Value which is measured in stops relative to 18% grey. + """, + "label", "", + "plugValueWidget:type", "GafferImageUI.ImageViewUI._ColorInspectorPlugValueWidget", + "layout:index", lambda plug : 1024-int( "".join( ['0'] + [ i for i in plug.getName() if i.isdigit() ] ) ) ], "channels" : [ @@ -264,6 +282,12 @@ def __clicked( self, button ) : # _ColorInspectorPlugValueWidget ########################################################################## +def _inspectFormat( f ) : + r = "%.3f" % f + if '.' in r and len( r ) > 5: + r = r[ 0 : max( 5, r.find('.') ) ] + return r.rstrip('.') + def _hsvString( color ) : if any( math.isinf( x ) or math.isnan( x ) for x in color ) : @@ -275,14 +299,13 @@ def _hsvString( color ) : return "- - -" else : hsv = color.rgb2hsv() - return "%.3f %.3f %.3f" % ( hsv.r, hsv.g, hsv.b ) + return "%s %s %s" % ( _inspectFormat( hsv.r ), _inspectFormat( hsv.g ), _inspectFormat( hsv.b ) ) -class _ColorInspectorPlugValueWidget( GafferUI.PlugValueWidget ) : +class _ColorInspectorsPlugValueWidget( GafferUI.PlugValueWidget ) : def __init__( self, plug, **kw ) : frame = GafferUI.Frame( borderWidth = 4 ) - GafferUI.PlugValueWidget.__init__( self, frame, plug, **kw ) # Style selector specificity rules seem to preclude us styling this @@ -291,64 +314,177 @@ def __init__( self, plug, **kw ) : with frame : - with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.LayoutPlugValueWidget( plug ) - GafferUI.Spacer( imath.V2i( 10 ), imath.V2i( 10 ) ) - self.__positionLabel = GafferUI.Label() - self.__positionLabel._qtWidget().setFixedWidth( 90 ) +class _ColorInspectorPlugValueWidget( GafferUI.PlugValueWidget ) : - self.__swatch = GafferUI.ColorSwatch() - self.__swatch._qtWidget().setFixedWidth( 12 ) - self.__swatch._qtWidget().setFixedHeight( 12 ) + def __init__( self, plug, **kw ) : - self.__busyWidget = GafferUI.BusyWidget( size = 12 ) + l = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) + GafferUI.PlugValueWidget.__init__( self, l, plug, **kw ) - self.__rgbLabel = GafferUI.Label() + mode = plug["mode"].getValue() + with l: + self.__indexLabel = GafferUI.Label() + labelFont = QtGui.QFont( self.__indexLabel._qtWidget().font() ) + labelFont.setBold( True ) + labelFont.setPixelSize( 10 ) + labelFontMetrics = QtGui.QFontMetrics( labelFont ) + self.__indexLabel._qtWidget().setMinimumWidth( labelFontMetrics.width( "99" ) ) - GafferUI.Spacer( imath.V2i( 20, 10 ), imath.V2i( 20, 10 ) ) + self.__modeImage = GafferUI.Image( "sourceCursor.png" ) - self.__hsvLabel = GafferUI.Label() + self.__positionLabel = GafferUI.Label() + self.__positionLabel._qtWidget().setMinimumWidth( labelFontMetrics.width( "9999 9999 -> 9999 9999" ) ) - GafferUI.Spacer( imath.V2i( 10 ), imath.V2i( 10 ) ) + self.__swatch = GafferUI.ColorSwatch() + self.__swatch._qtWidget().setFixedWidth( 12 ) + self.__swatch._qtWidget().setFixedHeight( 12 ) - self.__pixel = imath.V2f( 0 ) + self.__busyWidget = GafferUI.BusyWidget( size = 12 ) - viewportGadget = plug.parent().viewportGadget() - viewportGadget.mouseMoveSignal().connect( Gaffer.WeakMethod( self.__mouseMove ), scoped = False ) + self.__rgbLabel = GafferUI.Label() + self.__rgbLabel._qtWidget().setMinimumWidth( labelFontMetrics.width( "RGBA : 99999 99999 99999 99999" ) ) - imageGadget = viewportGadget.getPrimaryChild() - imageGadget.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False ) - imageGadget.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False ) - imageGadget.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) + self.__hsvLabel = GafferUI.Label() + self.__hsvLabel._qtWidget().setMinimumWidth( labelFontMetrics.width( "HSV : 99999 99999 99999" ) ) + + self.__exposureLabel = GafferUI.Label() + self.__exposureLabel._qtWidget().setMinimumWidth( labelFontMetrics.width( "EV : 19.9" ) ) + + l.addChild( GafferUI.Spacer( size = imath.V2i( 0 ) ) ) + + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Cursor: + m = IECore.MenuDefinition() + m.append( "/Pixel Inspector", + { "command" : functools.partial( Gaffer.WeakMethod( self.__addClick ), GafferImageUI.ImageView.ColorInspectorPlug.Mode.Pixel ) } + ) + m.append( "/Area Inspector", + { "command" : functools.partial( Gaffer.WeakMethod( self.__addClick ), GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area ) } + ) + button = GafferUI.MenuButton( "", "plus.png", hasFrame=False, menu = GafferUI.Menu( m, title = "Add Color Inspector" ) ) + else: + button = GafferUI.Button( "", "delete.png", hasFrame=False ) + button.clickedSignal().connect( Gaffer.WeakMethod( self.__deleteClick ), scoped = False ) + + + self.__pixel = imath.V2i( 0 ) + self.__createInspectorStartPosition = None + + if plug.getName() == "ColorInspectorPlug": + viewportGadget = plug.node().viewportGadget() + + imageGadget = viewportGadget.getPrimaryChild() + imageGadget.mouseMoveSignal().connect( Gaffer.WeakMethod( self.__mouseMove ), scoped = False ) + imageGadget.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False ) + imageGadget.buttonReleaseSignal().connect( Gaffer.WeakMethod( self.__buttonRelease ), scoped = False ) + imageGadget.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False ) + imageGadget.dragEnterSignal().connect( Gaffer.WeakMethod( self.__dragEnter ), scoped = False ) + imageGadget.dragMoveSignal().connect( Gaffer.WeakMethod( self.__dragMove ), scoped = False ) + imageGadget.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) + + self.__swatch.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False ) + self.__swatch.dragBeginSignal().connect( Gaffer.WeakMethod( self.__dragBegin ), scoped = False ) + self.__swatch.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) + + plug.node()["colorInspector"]["evaluator"]["pixelColor"].getInput().node().plugDirtiedSignal().connect( Gaffer.WeakMethod( self.__updateFromImageNode ), scoped = False ) + + plug.node().plugDirtiedSignal().connect( Gaffer.WeakMethod( self._plugDirtied ), scoped = False ) + plug.node()["in"].getInput().node().scriptNode().context().changedSignal().connect( Gaffer.WeakMethod( self.__updateFromContext ), scoped = False ) + Gaffer.Metadata.plugValueChangedSignal( self.getPlug().node() ).connect( Gaffer.WeakMethod( self.__plugMetadataChanged ), scoped = False ) self.__updateLabels( imath.V2i( 0 ), imath.Color4f( 0, 0, 0, 1 ) ) + # Set initial state of mode icon + self._plugDirtied( plug["mode"] ) + + def __addInspector( self ): + parent = self.getPlug().parent() + suffix = 1 + while "c" + str( suffix ) in parent: + suffix += 1 + + parent.addChild( GafferImageUI.ImageView.ColorInspectorPlug( "c" + str( suffix ) ) ) + + def __addClick( self, mode ): + self.__addInspector() + ci = self.getPlug().parent().children()[-1] + ci["mode"].setValue( mode ) + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area: + ci["area"].setValue( + self.getPlug().node()["colorInspector"]["evaluator"]["areaColor"].getInput().node()["in"].format().getDisplayWindow() + ) + + def __deleteClick( self, button ): + self.getPlug().parent().removeChild( self.getPlug() ) + + def __updateFromImageNode( self, unused ): + + self.__updateLazily() + + def _plugDirtied( self, childPlug ): + if childPlug == self.getPlug()["mode"]: + mode = self.getPlug()["mode"].getValue() + + # TODO - should GafferUI.Image have a setImage? + self.__modeImage._qtWidget().setPixmap( GafferUI.Image._qtPixmapFromFile( [ "sourceCursor.png", "sourcePixel.png", "sourceArea.png" ][ mode ] ) ) + self.__updateLazily() + + def __plugMetadataChanged( self, plug, key, reason ): + if key == "__hovered" and ( plug == self.getPlug()["area"] or plug == self.getPlug()["pixel"] ): + # We could avoid the extra compute of the color at the cost of a little extra complexity if + # we stored the last evaluated color so we could directly call _updateLabels + self.__updateLazily() + def _updateFromPlug( self ) : self.__updateLazily() + def __updateFromContext( self, context, name ) : + + self.__updateLazily() + @GafferUI.LazyMethod() def __updateLazily( self ) : + mode = self.getPlug()["mode"].getValue() + inputImagePlug = self.getPlug().node()["in"].getInput() + if not inputImagePlug: + # This can happen when the source is deleted - can't get pixel values if there's no input image + self.__updateLabels( self.__pixel, imath.Color4f( 0 ) ) + return - with self.getContext() : - self.__updateInBackground( self.__pixel ) + with inputImagePlug.node().scriptNode().context() : + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Cursor: + self.__updateInBackground( self.__pixel ) + elif mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area: + self.__updateInBackground( self.getPlug()["area"].getValue() ) + elif mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Pixel: + self.__updateInBackground( self.getPlug()["pixel"].getValue() ) @GafferUI.BackgroundMethod() - def __updateInBackground( self, pixel ) : - - image = self.getPlug().node().viewportGadget().getPrimaryChild().getImage() + def __updateInBackground( self, source ) : with Gaffer.Context( Gaffer.Context.current() ) as c : - c["colorInspector:pixel"] = pixel - samplerChannels = self.getPlug()["color"].getInput().node()["channels"].getValue() - channelNames = image["channelNames"].getValue() - color = self.getPlug()["color"].getValue() + if type( source ) == imath.V2i: + c["colorInspector:source"] = imath.V2f( source ) + imath.V2f( 0.5 ) # Center of pixel + color = self.getPlug().node()["colorInspector"]["evaluator"]["pixelColor"].getValue() + elif type( source ) == imath.Box2i: + areaEval = self.getPlug().node()["colorInspector"]["evaluator"]["areaColor"] + c["colorInspector:source"] = GafferImage.BufferAlgo.intersection( source, areaEval.getInput().node()["in"].format().getDisplayWindow() ) + color = areaEval.getValue() + else: + raise Exception( "ColorInspector source must be V2i or Box2i, not " + str( type( source ) ) ) + + # TODO : This is a pretty ugly way to find the input node connected to the colorInspector? + samplerChannels = self.getPlug().node()["colorInspector"]["evaluator"]["pixelColor"].getInput().node()["channels"].getValue() + image = self.getPlug().node().viewportGadget().getPrimaryChild().getImage() + channelNames = image["channelNames"].getValue() if samplerChannels[3] not in channelNames : color = imath.Color3f( color[0], color[1], color[2] ) - return pixel, color + return source, color @__updateInBackground.preCall def __updateInBackgroundPreCall( self ) : @@ -384,32 +520,62 @@ def __updateInBackgroundPostCall( self, backgroundResult ) : self.__busyWidget.setBusy( False ) def __updateLabels( self, pixel, color ) : + mode = self.getPlug()["mode"].getValue() + + hovered = False + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area: + hovered = Gaffer.Metadata.value( self.getPlug()["area"], "__hovered" ) + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Pixel: + hovered = Gaffer.Metadata.value( self.getPlug()["pixel"], "__hovered" ) + prefix = "" + postfix = "" + if hovered: + # Chosen to match brightColor in python/GafferUI/_Stylesheet.py + prefix = '' + postfix = '' + self.__indexLabel.setText( prefix + ( "" if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Cursor else "" + self.getPlug().getName()[1:] + "" ) + postfix ) + if mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area: + r = self.getPlug()["area"].getValue() + self.__positionLabel.setText( prefix + "%i %i -> %i %i" % ( r.min().x, r.min().y, r.max().x, r.max().y ) + postfix ) + elif mode == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Cursor: + self.__positionLabel.setText( prefix + "XY : %i %i" % ( pixel.x, pixel.y ) + postfix ) + else: + p = self.getPlug()["pixel"].getValue() + self.__positionLabel.setText( prefix + "XY : %i %i" % ( p.x, p.y ) + postfix ) - self.__positionLabel.setText( "XY : %d %d" % ( pixel.x, pixel.y ) ) self.__swatch.setColor( color ) if isinstance( color, imath.Color4f ) : - self.__rgbLabel.setText( "RGBA : %.3f %.3f %.3f %.3f" % ( color.r, color.g, color.b, color.a ) ) + self.__rgbLabel.setText( "RGBA : %s %s %s %s" % ( _inspectFormat(color.r), _inspectFormat(color.g), _inspectFormat(color.b), _inspectFormat(color.a) ) ) else : - self.__rgbLabel.setText( "RGB : %.3f %.3f %.3f" % ( color.r, color.g, color.b ) ) + self.__rgbLabel.setText( "RGB : %s %s %s" % ( _inspectFormat(color.r), _inspectFormat(color.g), _inspectFormat(color.b) ) ) self.__hsvLabel.setText( "HSV : %s" % _hsvString( color ) ) - def __mouseMove( self, viewportGadget, event ) : - - imageGadget = viewportGadget.getPrimaryChild() - l = viewportGadget.rasterToGadgetSpace( imath.V2f( event.line.p0.x, event.line.p0.y ), imageGadget ) + luminance = color.r * 0.2126 + color.g * 0.7152 + color.b * 0.0722 + if luminance == 0: + exposure = "-inf" + elif luminance < 0: + exposure = "NaN" + else: + exposure = "%.1f" % ( math.log( luminance / 0.18 ) / math.log( 2 ) ) + if exposure == "-0.0": + exposure = "0.0" + self.__exposureLabel.setText( "EV : %s" % exposure ) + def __eventPosition( self, imageGadget, event ): try : - pixel = imageGadget.pixelAt( l ) + pixel = imageGadget.pixelAt( event.line ) except : # `pixelAt()` can throw if there is an error # computing the image being viewed. We leave # the error reporting to other UI components. - return False + return imath.V2i( 0 ) + return imath.V2i( math.floor( pixel.x ), math.floor( pixel.y ) ) + + def __mouseMove( self, imageGadget, event ) : - pixel = imath.V2f( math.floor( pixel.x ), math.floor( pixel.y ) ) # Origin - pixel = pixel + imath.V2f( 0.5 ) # Center + pixel = self.__eventPosition( imageGadget, event ) if pixel == self.__pixel : return False @@ -422,20 +588,47 @@ def __mouseMove( self, viewportGadget, event ) : def __buttonPress( self, imageGadget, event ) : - if event.buttons != event.Buttons.Left or event.modifiers : + if event.buttons == event.Buttons.Left and not event.modifiers : + self.__createInspectorStartPosition = None + return True # accept press so we get dragBegin() for dragging color + elif event.buttons == event.Buttons.Left and event.modifiers == GafferUI.ModifiableEvent.Modifiers.Control : + self.__createInspectorStartPosition = self.__eventPosition( imageGadget, event ) + self.__addInspector() + ci = self.getPlug().parent().children()[-1] + Gaffer.Metadata.registerValue( ci["pixel"], "__hovered", True, persistent = False ) + ci["pixel"].setValue( self.__createInspectorStartPosition ) + + return True # creating inspector + else: return False - return True # accept press so we get dragBegin() + def __buttonRelease( self, imageGadget, event ) : + if self.__createInspectorStartPosition: + ci = self.getPlug().parent().children()[-1] + Gaffer.Metadata.registerValue( ci["pixel"], "__hovered", False, persistent = False ) + Gaffer.Metadata.registerValue( ci["area"], "__hovered", False, persistent = False ) def __dragBegin( self, imageGadget, event ) : - if event.buttons != event.Buttons.Left or event.modifiers : - return False + if self.__createInspectorStartPosition: + return IECore.NullObject.defaultNullObject() + + with Gaffer.Context( self.getPlug().node()["in"].getInput().node().scriptNode().context() ) as c : - with Gaffer.Context( self.getContext() ) as c : - c["colorInspector:pixel"] = self.__pixel try : - color = self.getPlug()["color"].getValue() + source = self.__pixel + if self.getPlug()["mode"].getValue() == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Pixel: + source = self.getPlug()["pixel"].getValue() + elif self.getPlug()["mode"].getValue() == GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area: + source = self.getPlug()["area"].getValue() + + if type( source ) == imath.V2i: + c["colorInspector:source"] = imath.V2f( source ) + imath.V2f( 0.5 ) # Center of pixel + color = self.getPlug().node()["colorInspector"]["evaluator"]["pixelColor"].getValue() + else: + c["colorInspector:source"] = source + color = self.getPlug().node()["colorInspector"]["evaluator"]["areaColor"].getValue() + except : # Error will be reported elsewhere in the UI return None @@ -443,7 +636,37 @@ def __dragBegin( self, imageGadget, event ) : GafferUI.Pointer.setCurrent( "rgba" ) return color + def __dragEnter( self, imageGadget, event ) : + viewportGadget = self.getPlug().node().viewportGadget() + imageGadget = viewportGadget.getPrimaryChild() + if event.sourceGadget != imageGadget: + return False + + return True + + def __dragMove( self, imageGadget, event ) : + if self.__createInspectorStartPosition: + ci = self.getPlug().parent().children()[-1] + c = imath.Box2i() + c.extendBy( self.__createInspectorStartPosition ) + c.extendBy( self.__eventPosition( imageGadget, event ) ) + + # __eventPosition is rounded down, the rectangle should also include the upper end of the + # pixel containing the cursor + c.setMax( c.max() + imath.V2i( 1 ) ) + + ci["mode"].setValue( GafferImageUI.ImageView.ColorInspectorPlug.Mode.Area ) + Gaffer.Metadata.registerValue( ci["pixel"], "__hovered", False, persistent = False ) + Gaffer.Metadata.registerValue( ci["area"], "__hovered", True, persistent = False ) + ci["area"].setValue( c ) + + return True + def __dragEnd( self, imageGadget, event ) : + if self.__createInspectorStartPosition: + ci = self.getPlug().parent().children()[-1] + Gaffer.Metadata.registerValue( ci["pixel"], "__hovered", False, persistent = False ) + Gaffer.Metadata.registerValue( ci["area"], "__hovered", False, persistent = False ) GafferUI.Pointer.setCurrent( "" ) return True @@ -591,6 +814,16 @@ def __init__( self, imageView, **kw ) : GafferUI.Spacer.__init__( self, size = imath.V2i( 0, 25 ) ) +class _ExpandingSpacer( GafferUI.Spacer ): + def __init__( self, imageView, **kw ) : + + GafferUI.Widget.__init__( self, QtWidgets.QWidget(), **kw ) + + layout = QtWidgets.QHBoxLayout() + layout.setContentsMargins( 0, 0, 0, 0 ) + self._qtWidget().setLayout( layout ) + layout.addStretch( 1 ) + class _StateWidgetBalancingSpacer( GafferUI.Spacer ) : def __init__( self, imageView, **kw ) : diff --git a/python/GafferImageUI/RGBAChannelsPlugValueWidget.py b/python/GafferImageUI/RGBAChannelsPlugValueWidget.py index 130d405c0f7..bc9eec60f3d 100644 --- a/python/GafferImageUI/RGBAChannelsPlugValueWidget.py +++ b/python/GafferImageUI/RGBAChannelsPlugValueWidget.py @@ -129,4 +129,3 @@ def __setValue( self, unused, value ) : with Gaffer.UndoScope( self.getPlug().ancestor( Gaffer.ScriptNode ) ) : self.getPlug().setValue( IECore.StringVectorData( value ) ) - diff --git a/python/GafferImageUITest/CatalogueUITest.py b/python/GafferImageUITest/CatalogueUITest.py index 84a93535a31..1aea821cd94 100644 --- a/python/GafferImageUITest/CatalogueUITest.py +++ b/python/GafferImageUITest/CatalogueUITest.py @@ -165,4 +165,3 @@ def mockNode( self ) : if __name__ == "__main__": unittest.main() - diff --git a/python/GafferImageUITest/ImageGadgetTest.py b/python/GafferImageUITest/ImageGadgetTest.py index 89353dd5432..9e347c86ced 100644 --- a/python/GafferImageUITest/ImageGadgetTest.py +++ b/python/GafferImageUITest/ImageGadgetTest.py @@ -129,4 +129,3 @@ def testStateChangedSignal( self ) : if __name__ == "__main__": unittest.main() - diff --git a/python/GafferOSLTest/OSLImageTest.py b/python/GafferOSLTest/OSLImageTest.py index af71c590b0a..47b3185fa83 100644 --- a/python/GafferOSLTest/OSLImageTest.py +++ b/python/GafferOSLTest/OSLImageTest.py @@ -36,6 +36,7 @@ import os import imath +import inspect import IECore @@ -50,110 +51,191 @@ class OSLImageTest( GafferImageTest.ImageTestCase ) : representativeDeepImagePath = os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/representativeDeepImage.exr" ) def test( self ) : + for useClosure in [ False, True ]: - getRed = GafferOSL.OSLShader() - getRed.loadShader( "ImageProcessing/InChannel" ) - getRed["parameters"]["channelName"].setValue( "R" ) + getRed = GafferOSL.OSLShader() + getRed.loadShader( "ImageProcessing/InChannel" ) + getRed["parameters"]["channelName"].setValue( "R" ) - getGreen = GafferOSL.OSLShader() - getGreen.loadShader( "ImageProcessing/InChannel" ) - getGreen["parameters"]["channelName"].setValue( "G" ) + getGreen = GafferOSL.OSLShader() + getGreen.loadShader( "ImageProcessing/InChannel" ) + getGreen["parameters"]["channelName"].setValue( "G" ) - getBlue = GafferOSL.OSLShader() - getBlue.loadShader( "ImageProcessing/InChannel" ) - getBlue["parameters"]["channelName"].setValue( "B" ) + getBlue = GafferOSL.OSLShader() + getBlue.loadShader( "ImageProcessing/InChannel" ) + getBlue["parameters"]["channelName"].setValue( "B" ) - floatToColor = GafferOSL.OSLShader() - floatToColor.loadShader( "Conversion/FloatToColor" ) - floatToColor["parameters"]["r"].setInput( getBlue["out"]["channelValue"] ) - floatToColor["parameters"]["g"].setInput( getGreen["out"]["channelValue"] ) - floatToColor["parameters"]["b"].setInput( getRed["out"]["channelValue"] ) - - outRGB = GafferOSL.OSLShader() - outRGB.loadShader( "ImageProcessing/OutLayer" ) - outRGB["parameters"]["layerColor"].setInput( floatToColor["out"]["c"] ) - - imageShader = GafferOSL.OSLShader() - imageShader.loadShader( "ImageProcessing/OutImage" ) - imageShader["parameters"]["in0"].setInput( outRGB["out"]["layer"] ) - - reader = GafferImage.ImageReader() - reader["fileName"].setValue( os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) ) - - image = GafferOSL.OSLImage() - image["in"].setInput( reader["out"] ) - - # we haven't connected the shader yet, so the node should act as a pass through + floatToColor = GafferOSL.OSLShader() + floatToColor.loadShader( "Conversion/FloatToColor" ) + floatToColor["parameters"]["r"].setInput( getBlue["out"]["channelValue"] ) + floatToColor["parameters"]["g"].setInput( getGreen["out"]["channelValue"] ) + floatToColor["parameters"]["b"].setInput( getRed["out"]["channelValue"] ) - self.assertEqual( GafferImage.ImageAlgo.image( image["out"] ), GafferImage.ImageAlgo.image( reader["out"] ) ) + reader = GafferImage.ImageReader() + reader["fileName"].setValue( os.path.expandvars( "$GAFFER_ROOT/python/GafferImageTest/images/rgb.100x100.exr" ) ) - # that should all change when we hook up a shader + shuffle = GafferImage.Shuffle() + shuffle["in"].setInput( reader["out"] ) + shuffle["channels"].addChild( GafferImage.Shuffle.ChannelPlug( "channel" ) ) + shuffle["channels"]["channel"]["out"].setValue( 'unchangedR' ) + shuffle["channels"]["channel"]["in"].setValue( 'R' ) - image["channels"].addChild( Gaffer.NameValuePlug( "", GafferOSL.ClosurePlug(), "testClosure" ) ) - cs = GafferTest.CapturingSlot( image.plugDirtiedSignal() ) + image = GafferOSL.OSLImage() + image["in"].setInput( shuffle["out"] ) - def checkDirtiness( expected): - self.assertEqual( [ i[0].fullName() for i in cs ], [ "OSLImage." + i for i in expected ] ) - del cs[:] + # we haven't connected the shader yet, so the node should act as a pass through - image["channels"]["testClosure"]["value"].setInput( imageShader["out"]["out"] ) + self.assertEqual( GafferImage.ImageAlgo.image( image["out"] ), GafferImage.ImageAlgo.image( shuffle["out"] ) ) - checkDirtiness( [ - "channels.testClosure.value", "channels.testClosure", "channels", "__shader", "__shading", - "out.channelNames", "out.channelData", "out" - ] ) + # that should all change when we hook up a shader - inputImage = GafferImage.ImageAlgo.image( reader["out"] ) - outputImage = GafferImage.ImageAlgo.image( image["out"] ) + if useClosure: - self.assertNotEqual( inputImage, outputImage ) - self.assertEqual( outputImage["R"], inputImage["B"] ) - self.assertEqual( outputImage["G"], inputImage["G"] ) - self.assertEqual( outputImage["B"], inputImage["R"] ) + outRGB = GafferOSL.OSLShader() + outRGB.loadShader( "ImageProcessing/OutLayer" ) + outRGB["parameters"]["layerColor"].setInput( floatToColor["out"]["c"] ) - # changes in the shader network should signal more dirtiness + imageShader = GafferOSL.OSLShader() + imageShader.loadShader( "ImageProcessing/OutImage" ) + imageShader["parameters"]["in0"].setInput( outRGB["out"]["layer"] ) - getGreen["parameters"]["channelName"].setValue( "R" ) - checkDirtiness( [ - "channels.testClosure.value", "channels.testClosure", "channels", "__shader", "__shading", - "out.channelNames", "out.channelData", "out" - ] ) + image["channels"].addChild( Gaffer.NameValuePlug( "", GafferOSL.ClosurePlug(), "testClosure" ) ) - floatToColor["parameters"]["r"].setInput( getRed["out"]["channelValue"] ) - checkDirtiness( [ - "channels.testClosure.value", "channels.testClosure", "channels", "__shader", "__shading", - "out.channelNames", "out.channelData", "out" - ] ) + else: + + image["channels"].addChild( Gaffer.NameValuePlug( "", imath.Color3f(), "testColor" ) ) - inputImage = GafferImage.ImageAlgo.image( reader["out"] ) - outputImage = GafferImage.ImageAlgo.image( image["out"] ) - - self.assertEqual( outputImage["R"], inputImage["R"] ) - self.assertEqual( outputImage["G"], inputImage["R"] ) - self.assertEqual( outputImage["B"], inputImage["R"] ) + cs = GafferTest.CapturingSlot( image.plugDirtiedSignal() ) + + def checkDirtiness( expected): + self.assertEqual( [ i[0].fullName() for i in cs ], [ "OSLImage." + i for i in expected ] ) + del cs[:] + + if useClosure: + image["channels"]["testClosure"]["value"].setInput( imageShader["out"]["out"] ) + channelsDirtied = ["channels.testClosure.value", "channels.testClosure"] + else: + image["channels"]["testColor"]["value"].setInput( floatToColor["out"]["c"] ) + channelsDirtied = [ + "channels.testColor.value.r", "channels.testColor.value.g", "channels.testColor.value.b", + "channels.testColor.value", "channels.testColor" + ] + + checkDirtiness( channelsDirtied + [ + "channels", "__shader", "__shading", + "__affectedChannels", "out.channelNames", "out.channelData", "out" + ] ) + + inputImage = GafferImage.ImageAlgo.image( shuffle["out"] ) + + with Gaffer.ContextMonitor( image["__shading"] ) as monitor : + self.assertEqual( image["out"].channelNames(), IECore.StringVectorData( [ "A", "B", "G", "R", "unchangedR" ] ) ) + # Evaluating channel names only requires evaluating the shading plug if we have a closure + self.assertEqual( monitor.combinedStatistics().numUniqueContexts(), 1 if useClosure else 0 ) + + # Channels we don't touch should be passed through unaltered + for channel, changed in [('B',True), ('G',True), ('R',True), ('A',False), ('unchangedR',False) ]: + self.assertEqual( + image["out"].channelDataHash( channel, imath.V2i( 0, 0 ) ) == + shuffle["out"].channelDataHash( channel, imath.V2i( 0, 0 ) ), + not changed + ) + image["out"].channelData( channel, imath.V2i( 0, 0 ) ) + + # Should only need one shading evaluate for all channels + self.assertEqual( monitor.combinedStatistics().numUniqueContexts(), 1 ) + + outputImage = GafferImage.ImageAlgo.image( image["out"] ) + + self.assertNotEqual( inputImage, outputImage ) + self.assertEqual( outputImage["R"], inputImage["B"] ) + self.assertEqual( outputImage["G"], inputImage["G"] ) + self.assertEqual( outputImage["B"], inputImage["R"] ) + + # changes in the shader network should signal more dirtiness + + getGreen["parameters"]["channelName"].setValue( "R" ) + checkDirtiness( channelsDirtied + [ + "channels", "__shader", "__shading", + "__affectedChannels", "out.channelNames", "out.channelData", "out" + ] ) + + floatToColor["parameters"]["r"].setInput( getRed["out"]["channelValue"] ) + checkDirtiness( channelsDirtied + [ + "channels", "__shader", "__shading", + "__affectedChannels", "out.channelNames", "out.channelData", "out" + ] ) + + + inputImage = GafferImage.ImageAlgo.image( shuffle["out"] ) + outputImage = GafferImage.ImageAlgo.image( image["out"] ) + + self.assertEqual( outputImage["R"], inputImage["R"] ) + self.assertEqual( outputImage["G"], inputImage["R"] ) + self.assertEqual( outputImage["B"], inputImage["R"] ) + self.assertEqual( outputImage["A"], inputImage["A"] ) + self.assertEqual( outputImage["unchangedR"], inputImage["unchangedR"] ) + + image["in"].setInput( None ) + checkDirtiness( [ + 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', + '__shading', '__affectedChannels', + 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' + ] ) + + image["defaultFormat"]["displayWindow"]["max"]["x"].setValue( 200 ) + checkDirtiness( [ + 'defaultFormat.displayWindow.max.x', 'defaultFormat.displayWindow.max', 'defaultFormat.displayWindow', 'defaultFormat', + '__defaultIn.format', '__defaultIn.dataWindow', '__defaultIn', '__shading', '__affectedChannels', + 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out' + ] ) + + constant = GafferImage.Constant() + image["in"].setInput( constant["out"] ) + + checkDirtiness( [ + 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', + '__shading', '__affectedChannels', + 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' + ] ) + + image["in"].setInput( shuffle["out"] ) + if useClosure: + outRGB["parameters"]["layerName"].setValue( "newLayer" ) + else: + image["channels"][0]["name"].setValue( "newLayer" ) + + self.assertEqual( image["out"].channelNames(), IECore.StringVectorData( + [ "A", "B", "G", "R", "newLayer.B", "newLayer.G", "newLayer.R", "unchangedR" ] + ) ) + + for channel in ['B', 'G', 'R', 'A', 'unchangedR' ]: + self.assertEqual( + image["out"].channelDataHash( channel, imath.V2i( 0, 0 ) ), + shuffle["out"].channelDataHash( channel, imath.V2i( 0, 0 ) ) + ) + self.assertEqual( + image["out"].channelData( channel, imath.V2i( 0, 0 ) ), + shuffle["out"].channelData( channel, imath.V2i( 0, 0 ) ) + ) + + crop = GafferImage.Crop() + crop["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 0, 0 ) ) ) + crop["in"].setInput( shuffle["out"] ) + + image["in"].setInput( crop["out"] ) + + if useClosure: + # When using closures, we can't find out about the new channels being added if the datawindow is + # empty + self.assertEqual( image["out"].channelNames(), IECore.StringVectorData( + [ "A", "B", "G", "R", "unchangedR" ] + ) ) + else: + self.assertEqual( image["out"].channelNames(), IECore.StringVectorData( + [ "A", "B", "G", "R", "newLayer.B", "newLayer.G", "newLayer.R", "unchangedR" ] + ) ) - image["in"].setInput( None ) - checkDirtiness( [ - 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', - '__shading', - 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' - ] ) - - image["defaultFormat"]["displayWindow"]["max"]["x"].setValue( 200 ) - checkDirtiness( [ - 'defaultFormat.displayWindow.max.x', 'defaultFormat.displayWindow.max', 'defaultFormat.displayWindow', 'defaultFormat', - '__defaultIn.format', '__defaultIn.dataWindow', '__defaultIn', '__shading', - 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out' - ] ) - - constant = GafferImage.Constant() - image["in"].setInput( constant["out"] ) - - checkDirtiness( [ - 'in.format', 'in.dataWindow', 'in.metadata', 'in.deep', 'in.sampleOffsets', 'in.channelNames', 'in.channelData', 'in', - '__shading', - 'out.channelNames', 'out.channelData', 'out.format', 'out.dataWindow', 'out.metadata', 'out.deep', 'out.sampleOffsets', 'out' - ] ) def testAcceptsShaderSwitch( self ) : @@ -593,5 +675,185 @@ def testDefaultFormat( self ): self.assertEqual( oslImage["out"]["format"].getValue().getDisplayWindow(), imath.Box2i( imath.V2i( 0 ), imath.V2i( 4, 4 ) ) ) self.assertEqual( GafferImage.ImageAlgo.image( oslImage["out"] )["G"], IECore.FloatVectorData( [0.6] * 16 ) ) + # Extreme example of doing something very expensive in OSLImage + def mandelbrotNode( self ): + mandelbrotCode = GafferOSL.OSLCode() + mandelbrotCode["parameters"].addChild( Gaffer.IntPlug( "iterations", defaultValue = 0, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) ) + mandelbrotCode["out"].addChild( Gaffer.FloatPlug( "outFloat", direction = Gaffer.Plug.Direction.Out ) ) + mandelbrotCode["code"].setValue( inspect.cleandoc( + """ + // Basic mandelbrot adapted from surface shader here: + // https://github.com/AcademySoftwareFoundation/OpenShadingLanguage/blob/master/src/shaders/mandelbrot.osl + point center = point (0,0,0); + float scale = 2; + point cent = center; + point c = scale * point(2*(u-0.5), 2*((1-v)-0.5), 0) + cent; + point z = c; + int i; + for (i = 1; i < iterations && dot(z,z) < 4.0; ++i) { + float x = z[0], y = z[1]; + z = point (x*x - y*y, 2*x*y, 0) + c; + } + if (i < iterations) { + float f = pow(float(i)/iterations, 1/log10(float(iterations))); + outFloat = f; + } else { + outFloat = 0; + } + """ + ) ) + return mandelbrotCode + + def testBadCachePolicyHang( self ): + + # Using the legacy cache policy for OSLImage.shadingPlug creates a hang due to tbb task stealing, + # though it's a bit hard to actually demonstrate + + constant = GafferImage.Constant() + constant["format"].setValue( GafferImage.Format( 128, 128, 1.000 ) ) + + # Need a slow to compute OSL code in order to trigger hang + mandelbrotCode = self.mandelbrotNode() + + # In order to trigger the hang, we need to mix threads which are stuck waiting for an expression which + # uses the Standard policy with threads that are actually finishing, so that tbb tries to start up new + # threads while we're waiting for the expression result. To do this, we use the "var" context variable + # to create two versions of this OSLCode + mandelbrotCode["varExpression"] = Gaffer.Expression() + mandelbrotCode["varExpression"].setExpression( 'parent.parameters.iterations = 100000 + context( "var", 0 );', "OSL" ) + + + oslImage = GafferOSL.OSLImage() + oslImage["channels"].addChild( Gaffer.NameValuePlug( "", Gaffer.Color3fPlug( "value", defaultValue = imath.Color3f( 1, 1, 1 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ), True, "channel", Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) + oslImage["in"].setInput( constant["out"] ) + oslImage["channels"]["channel"]["value"][0].setInput( mandelbrotCode["out"]["outFloat"] ) + oslImage["channels"]["channel"]["value"][1].setInput( mandelbrotCode["out"]["outFloat"] ) + oslImage["channels"]["channel"]["value"][2].setInput( mandelbrotCode["out"]["outFloat"] ) + + # This imageStats is use to create non-blocking slow calculations + imageStats = GafferImage.ImageStats() + imageStats["in"].setInput( oslImage["out"] ) + imageStats["area"].setValue( imath.Box2i( imath.V2i( 0, 0 ), imath.V2i( 64, 64 ) ) ) + + + # This box does the non-blocking slow calculation, followed by a blocking slow calculation. + # This ensures that tasks which do just the non-block calculation will start finishing while + # the blocking slow calculation is still running, allowing tbb to try running more threads + # on the blocking calcluation, realizing they can't run, and stealing tasks onto those threads + # which can hit the Standard policy lock on the expression upstream and deadlock, unless the + # OSLImage isolates its threads correctly + expressionBox = Gaffer.Box() + expressionBox.addChild( Gaffer.FloatVectorDataPlug( "inChannelData", defaultValue = IECore.FloatVectorData( [ ] ) ) ) + expressionBox.addChild( Gaffer.FloatPlug( "inStat" ) ) + expressionBox.addChild( Gaffer.FloatPlug( "out", direction = Gaffer.Plug.Direction.Out ) ) + expressionBox["inChannelData"].setInput( oslImage["out"]["channelData"] ) + expressionBox["inStat"].setInput( imageStats["average"]["r"] ) + + expressionBox["contextVariables"] = Gaffer.ContextVariables() + expressionBox["contextVariables"].setup( Gaffer.FloatVectorDataPlug( "in", defaultValue = IECore.FloatVectorData( [ ] ) ) ) + expressionBox["contextVariables"]["variables"].addChild( Gaffer.NameValuePlug( "image:tileOrigin", Gaffer.V2iPlug( "value" ), True, "member1" ) ) + expressionBox["contextVariables"]["variables"].addChild( Gaffer.NameValuePlug( "image:channelName", Gaffer.StringPlug( "value", defaultValue = 'R' ), True, "member2" ) ) + expressionBox["contextVariables"]["variables"].addChild( Gaffer.NameValuePlug( "var", Gaffer.IntPlug( "value", defaultValue = 1 ), True, "member3" ) ) + expressionBox["contextVariables"]["in"].setInput( expressionBox["inChannelData"] ) + + expressionBox["expression"] = Gaffer.Expression() + expressionBox["expression"].setExpression( inspect.cleandoc( + """ + d = parent["contextVariables"]["out"] + parent["out"] = d[0] + parent["inStat"] + """ + ) ) + + # Create a switch to mix which tasks perform the non-blocking or blocking calculation - we need a mixture + # to trigger the hang + switch = Gaffer.Switch() + switch.setup( Gaffer.IntPlug( "in", defaultValue = 0, ) ) + switch["in"][0].setInput( expressionBox["out"] ) + switch["in"][1].setInput( imageStats["average"]["r"] ) + + switch["switchExpression"] = Gaffer.Expression() + switch["switchExpression"].setExpression( 'parent.index = ( stoi( context( "testContext", "0" ) ) % 10 ) > 5;', "OSL" ) + + # In order to evaluate this expression a bunch of times at once with different values of "testContext", + # we set up a simple scene that can be evaluated with GafferSceneTest.traversScene. + # In theory, we could use a simple function that used a parallel_for to evaluate switch["out"], but for + # some reason we don't entirely understand, this does not trigger the hang + import GafferSceneTest + import GafferScene + + sphere = GafferScene.Sphere() + + pathFilter = GafferScene.PathFilter() + pathFilter["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) + + customAttributes = GafferScene.CustomAttributes() + customAttributes["attributes"].addChild( Gaffer.NameValuePlug( "foo", Gaffer.FloatPlug( "value" ), True, "member1" ) ) + customAttributes["attributes"]["member1"]["value"].setInput( switch["out"] ) + customAttributes["in"].setInput( sphere["out"] ) + customAttributes["filter"].setInput( pathFilter["out"] ) + + collectScenes = GafferScene.CollectScenes() + collectScenes["in"].setInput( customAttributes["out"] ) + collectScenes["rootNames"].setValue( IECore.StringVectorData( [ str(i) for i in range(1000) ] ) ) + collectScenes["rootNameVariable"].setValue( 'testContext' ) + + # When OSLImage.shadingPlug is not correctly isolated, and grain size on ShadingEngine is smaller than the + # image tile size, this fails about 50% of the time. Running it 5 times makes the failure pretty consistent. + for i in range( 5 ): + Gaffer.ValuePlug.clearCache() + Gaffer.ValuePlug.clearHashCache() + GafferSceneTest.traverseScene( collectScenes["out"] ) + + @GafferTest.TestRunner.PerformanceTestMethod() + def testMinimalPerf( self ) : + + constant = GafferImage.Constant() + constant["format"].setValue( GafferImage.Format( 4096, 4096 ) ) + + floatToColor = GafferOSL.OSLShader() + floatToColor.loadShader( "Conversion/FloatToColor" ) + + oslImage = GafferOSL.OSLImage() + oslImage["in"].setInput( constant["out"] ) + oslImage["channels"].addChild( Gaffer.NameValuePlug( "", Gaffer.Color3fPlug( "value" ), True, "channel" ) ) + oslImage["channels"]["channel"]["value"].setInput( floatToColor["out"]["c"] ) + + GafferImage.ImageAlgo.image( constant["out"] ) + + # Run the fastest possible OSLImage on lots of tiles, to highlight any constant overhead + with GafferTest.TestRunner.PerformanceScope() : + GafferImage.ImageAlgo.image( oslImage["out"] ) + + @GafferTest.TestRunner.PerformanceTestMethod( repeat = 1) + def testCollaboratePerf( self ) : + # Test an expensive OSLImage, with many output tiles depending on the same input tiles, + # which should give TaskCollaborate a chance to show some benefit + + constant = GafferImage.Constant() + constant["format"].setValue( GafferImage.Format( 128, 128 ) ) + + deleteChannels = GafferImage.DeleteChannels( "DeleteChannels" ) + deleteChannels["in"].setInput( constant["out"] ) + deleteChannels["mode"].setValue( GafferImage.DeleteChannels.Mode.Keep ) + deleteChannels["channels"].setValue( 'R' ) + + mandelbrotCode = self.mandelbrotNode() + mandelbrotCode["parameters"]["iterations"].setValue( 500000 ) + + oslImage = GafferOSL.OSLImage() + oslImage["in"].setInput( deleteChannels["out"] ) + oslImage["channels"].addChild( Gaffer.NameValuePlug( "R", Gaffer.FloatPlug( "value" ), True, "channel" ) ) + oslImage["channels"]["channel"]["value"].setInput( mandelbrotCode["out"]["outFloat"] ) + + resize = GafferImage.Resize() + resize["in"].setInput( oslImage["out"] ) + resize["format"].setValue( GafferImage.Format( imath.Box2i( imath.V2i( 0 ), imath.V2i( 2048 ) ), 1 ) ) + # We use a resize because it pulls the input tiles repeatedly, we don't want to spend time on resizing + # pixels, so use a fast filter + resize["filter"].setValue( 'box' ) + + with GafferTest.TestRunner.PerformanceScope() : + GafferImage.ImageAlgo.image( resize["out"] ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferOSLTest/OSLObjectTest.py b/python/GafferOSLTest/OSLObjectTest.py index 92d456280da..71d42ba14c5 100644 --- a/python/GafferOSLTest/OSLObjectTest.py +++ b/python/GafferOSLTest/OSLObjectTest.py @@ -1105,4 +1105,3 @@ def testLoadFrom0_55( self ) : if __name__ == "__main__": unittest.main() - diff --git a/python/GafferOSLTest/OSLShaderTest.py b/python/GafferOSLTest/OSLShaderTest.py index a884949f1a2..ef415d59e77 100644 --- a/python/GafferOSLTest/OSLShaderTest.py +++ b/python/GafferOSLTest/OSLShaderTest.py @@ -38,6 +38,7 @@ import unittest import imath import random +import shutil import IECore import IECoreScene @@ -552,8 +553,9 @@ def testReload( self ) : n = GafferOSL.OSLShader() n.loadShader( s1 ) + s1Parameters = n["parameters"].keys() self.assertEqual( - n["parameters"].keys(), + s1Parameters, [ "commonI", "commonF", @@ -679,6 +681,13 @@ def testReload( self ) : if isinstance( plug, Gaffer.ValuePlug ) : self.assertTrue( plug.isSetToDefault() ) + shutil.copyfile( s1 + ".oso", s2 + ".oso" ) + n.reloadShader() + self.assertEqual( + n["parameters"].keys(), + s1Parameters + ) + def testSplineParameters( self ) : s = self.compileShader( os.path.dirname( __file__ ) + "/shaders/splineParameters.osl" ) @@ -1080,5 +1089,14 @@ def testShaderTypeAssignsAsSurfaceType( self ) : self.assertEqual( shaderAssignment["out"].attributes( "/plane" ).keys(), [ "osl:surface" ] ) + def testConstantOutPlug( self ) : + + # For compatibility with Arnold, we hack an output closure + # parameter onto our Constant shader, but we don't want that + # to affect the way we represent the output plug in Gaffer. + shader = GafferOSL.OSLShader() + shader.loadShader( "Surface/Constant" ) + self.assertEqual( len( shader["out"].children() ), 0 ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferOSLTest/ShadingEngineTest.py b/python/GafferOSLTest/ShadingEngineTest.py index 4327be5bcc1..fe9b335037b 100644 --- a/python/GafferOSLTest/ShadingEngineTest.py +++ b/python/GafferOSLTest/ShadingEngineTest.py @@ -487,7 +487,7 @@ def testParameters( self ) : "n" : IECore.V3fData( imath.V3f( 13, 14, 15 ), IECore.GeometricData.Interpretation.Normal ), "noInterp" : imath.V3f( 16, 17, 18 ), - } ) + } ) }, output = "output" ) ) diff --git a/python/GafferOSLTest/shaders/uvTextureMap.osl b/python/GafferOSLTest/shaders/uvTextureMap.osl index 6d9c46d4b34..bf0fc6215aa 100644 --- a/python/GafferOSLTest/shaders/uvTextureMap.osl +++ b/python/GafferOSLTest/shaders/uvTextureMap.osl @@ -39,5 +39,5 @@ surface uvTextureMap string fileName = "catmull-rom", ) { - Ci = emission() * texture( fileName, u, v ); + Ci = emission() * (color)texture( fileName, u, v ); } diff --git a/python/GafferOSLUI/OSLImageUI.py b/python/GafferOSLUI/OSLImageUI.py index f03cd205d45..cf683c52d07 100644 --- a/python/GafferOSLUI/OSLImageUI.py +++ b/python/GafferOSLUI/OSLImageUI.py @@ -193,7 +193,7 @@ def __channelLabelFromPlug( plug ): "layout:activator:defaultFormatActive", lambda node : not node["in"].getInput(), - plugs = { + plugs = { "defaultFormat" : [ "description", """ @@ -202,8 +202,8 @@ def __channelLabelFromPlug( plug ): "layout:activator", "defaultFormatActive", ], "channels" : [ - "description", - """ + "description", + """ Define image channels to output by adding child plugs and connecting corresponding OSL shaders. You can drive RGB layers with a color, or connect individual channels to a float. @@ -228,7 +228,7 @@ def __channelLabelFromPlug( plug ): # appropriate values for each individual parameter, # for the case where they get promoted to a box # individually. - "noduleLayout:section", "left", + "noduleLayout:section", "left", "nodule:type", "GafferUI::CompoundNodule", "nameValuePlugPlugValueWidget:ignoreNamePlug", lambda plug : isinstance( plug["value"], GafferOSL.ClosurePlug ), ], @@ -249,7 +249,7 @@ def __channelLabelFromPlug( plug ): "noduleLayout:section", "left", "noduleLayout:label", __channelLabelFromPlug, "ui:visibleDimensions", lambda plug : 2 if hasattr( plug, "interpretation" ) and plug.interpretation() == IECore.GeometricData.Interpretation.UV else None, - ], + ], } ) diff --git a/python/GafferOSLUI/OSLLightUI.py b/python/GafferOSLUI/OSLLightUI.py index 18bca250ee2..701a8c2b48d 100644 --- a/python/GafferOSLUI/OSLLightUI.py +++ b/python/GafferOSLUI/OSLLightUI.py @@ -180,4 +180,3 @@ def __parameterMetadata( plug, key ) : "noduleLayout:label", ] : Gaffer.Metadata.registerValue( GafferOSL.OSLLight, "parameters.*", key, functools.partial( __parameterMetadata, key = key ) ) - diff --git a/python/GafferSceneTest/BoundQueryTest.py b/python/GafferSceneTest/BoundQueryTest.py new file mode 100644 index 00000000000..fae2bd30ec6 --- /dev/null +++ b/python/GafferSceneTest/BoundQueryTest.py @@ -0,0 +1,486 @@ +########################################################################## +# +# Copyright (c) 2021, 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 Gaffer +import GafferScene +import GafferSceneTest + +def randomName( gen, mnc, mxc ): + + from string import ascii_lowercase + + return ''.join( gen.choice( ascii_lowercase ) + for _ in range( gen.randrange( mnc, mxc ) ) ) + +class BoundQueryTest( GafferSceneTest.SceneTestCase ): + + def testDefault( self ): + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + bq = GafferScene.BoundQuery() + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + def testSpaceLocal( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + s1 = GafferScene.Sphere() + s1["name"].setValue( name1 ) + s1["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + + name2 = randomName( r, 5, 10 ) + s2 = GafferScene.Sphere() + s2["name"].setValue( name2 ) + s2["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s2["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s2["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + + g = GafferScene.Group() + g["name"].setValue( "group" ) + g["in"][0].setInput( s1["out"] ) + g["in"][1].setInput( s2["out"] ) + g["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + g["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + g["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + + bq = GafferScene.BoundQuery() + bq["space"].setValue( GafferScene.BoundQuery.Space.Local ) + bq["location"].setValue( "/" ) + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["location"].setValue( "/" + name1 ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["scene"].setInput( g["out"] ) + bq["location"].setValue( '/group/' + name1 ) + + b = bq["scene"].bound( '/group/' + name1 ) + + self.assertTrue( bq["__internalBound"].getValue() == b ) + self.assertTrue( bq["bound"]["min"].getValue() == b.min() ) + self.assertTrue( bq["bound"]["max"].getValue() == b.max() ) + self.assertTrue( bq["center"].getValue() == b.center() ) + self.assertTrue( bq["size"].getValue() == b.size() ) + + bq["location"].setValue( '/group/' + name2 ) + + b = bq["scene"].bound( '/group/' + name2 ) + + self.assertTrue( bq["__internalBound"].getValue() == b ) + self.assertTrue( bq["bound"]["min"].getValue() == b.min() ) + self.assertTrue( bq["bound"]["max"].getValue() == b.max() ) + self.assertTrue( bq["center"].getValue() == b.center() ) + self.assertTrue( bq["size"].getValue() == b.size() ) + + bq["location"].setValue( '/group' ) + + b = bq["scene"].bound( '/group' ) + + self.assertTrue( bq["__internalBound"].getValue() == b ) + self.assertTrue( bq["bound"]["min"].getValue() == b.min() ) + self.assertTrue( bq["bound"]["max"].getValue() == b.max() ) + self.assertTrue( bq["center"].getValue() == b.center() ) + self.assertTrue( bq["size"].getValue() == b.size() ) + + def testSpaceWorld( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + s1 = GafferScene.Sphere() + s1["name"].setValue( name1 ) + s1["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m1 = s1["transform"].matrix() + + name2 = randomName( r, 5, 10 ) + s2 = GafferScene.Sphere() + s2["name"].setValue( name2 ) + s2["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s2["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s2["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m2 = s2["transform"].matrix() + + g = GafferScene.Group() + g["name"].setValue( "group" ) + g["in"][0].setInput( s1["out"] ) + g["in"][1].setInput( s2["out"] ) + g["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + g["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + g["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m3 = g["transform"].matrix() + + bq = GafferScene.BoundQuery() + bq["space"].setValue( GafferScene.BoundQuery.Space.World ) + bq["location"].setValue( "/" ) + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["location"].setValue( "/" + name1 ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["scene"].setInput( g["out"] ) + bq["location"].setValue( '/group/' + name1 ) + + b = bq["scene"].bound( '/group/' + name1 ) * ( m1 * m3 ) + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( b.center(), 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( b.size(), 0.000001 ) ) + + bq["location"].setValue( '/group/' + name2 ) + + b = bq["scene"].bound( '/group/' + name2 ) * ( m2 * m3 ) + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( b.center(), 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( b.size(), 0.000001 ) ) + + def testSpaceRelative( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + s1 = GafferScene.Sphere() + s1["name"].setValue( name1 ) + s1["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m1 = s1["transform"].matrix() + + name2 = randomName( r, 5, 10 ) + s2 = GafferScene.Sphere() + s2["name"].setValue( name2 ) + s2["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s2["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s2["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m2 = s2["transform"].matrix() + + g = GafferScene.Group() + g["name"].setValue( "group" ) + g["in"][0].setInput( s1["out"] ) + g["in"][1].setInput( s2["out"] ) + g["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + g["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + g["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + m3 = g["transform"].matrix() + + bq = GafferScene.BoundQuery() + bq["space"].setValue( GafferScene.BoundQuery.Space.Relative ) + bq["location"].setValue( "/" ) + bq["relativeLocation"].setValue( "" ) + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["relativeLocation"].setValue( "/" ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["location"].setValue( "/" + name1 ) + bq["relativeLocation"].setValue( "" ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["relativeLocation"].setValue( "/" + name2 ) + + self.assertEqual( bq["__internalBound"].getValue(), b ) + self.assertEqual( bq["bound"]["min"].getValue(), v ) + self.assertEqual( bq["bound"]["max"].getValue(), v ) + self.assertEqual( bq["center"].getValue(), v ) + self.assertEqual( bq["size"].getValue(), v ) + + bq["scene"].setInput( g["out"] ) + bq["location"].setValue( '/group/' + name1 ) + bq["relativeLocation"].setValue( '/group/' + name2 ) + + m = ( m1 * m3 ) * ( m2 * m3 ).inverse() + b = bq["scene"].bound( '/group/' + name1 ) * m + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( b.center(), 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( b.size(), 0.000001 ) ) + + def testSpaceRelativeLocationSameAsLocationEmpty( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name = randomName( r, 5, 10 ) + + s = GafferScene.Sphere() + s["name"].setValue( name ) + + bq = GafferScene.BoundQuery() + bq["scene"].setInput( s["out"] ) + bq["location"].setValue( "" ) + bq["relativeLocation"].setValue( "" ) + bq["space"].setValue( GafferScene.BoundQuery.Space.Relative ) + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( v, 0.000001 ) ) + + def testSpaceRelativeLocationSameAsLocationInvalid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = 'a' + randomName( r, 4, 9 ) + name2 = 'b' + randomName( r, 4, 9 ) + + s = GafferScene.Sphere() + s["name"].setValue( name1 ) + + bq = GafferScene.BoundQuery() + bq["scene"].setInput( s["out"] ) + bq["location"].setValue( name2 ) + bq["relativeLocation"].setValue( name2 ) + bq["space"].setValue( GafferScene.BoundQuery.Space.Relative ) + + v = imath.V3f( 0.0, 0.0, 0.0 ) + b = imath.Box3f( v, v ) + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( v, 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( v, 0.000001 ) ) + + def testSpaceRelativeLocationSameAsLocationValid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name = randomName( r, 5, 10 ) + + s = GafferScene.Sphere() + s["name"].setValue( name ) + s["transform"]["scale"].setValue( imath.V3f( + r.random() * 10.0, + r.random() * 10.0, + r.random() * 10.0 ) ) + s["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0, + ( r.random() - 0.5 ) * 10.0 ) ) + s["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0, + ( r.random() - 0.5 ) * 360.0 ) ) + + bq = GafferScene.BoundQuery() + bq["scene"].setInput( s["out"] ) + bq["location"].setValue( name ) + bq["relativeLocation"].setValue( name ) + bq["space"].setValue( GafferScene.BoundQuery.Space.Relative ) + + b = bq["scene"].bound( name ) + + self.assertTrue( bq["__internalBound"].getValue().min().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["__internalBound"].getValue().max().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["bound"]["min"].getValue().equalWithAbsError( b.min(), 0.000001 ) ) + self.assertTrue( bq["bound"]["max"].getValue().equalWithAbsError( b.max(), 0.000001 ) ) + self.assertTrue( bq["center"].getValue().equalWithAbsError( b.center(), 0.000001 ) ) + self.assertTrue( bq["size"].getValue().equalWithAbsError( b.size(), 0.000001 ) ) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/python/GafferSceneTest/ContextSanitiserTest.py b/python/GafferSceneTest/ContextSanitiserTest.py index 89ecf7c7db3..7e4f2e08093 100644 --- a/python/GafferSceneTest/ContextSanitiserTest.py +++ b/python/GafferSceneTest/ContextSanitiserTest.py @@ -34,6 +34,8 @@ # ########################################################################## +import six + import IECore import Gaffer @@ -47,16 +49,22 @@ def test( self ) : plane = GafferScene.Plane() plane["sets"].setValue( "a" ) + # A ContextSanitiser is automatically hooked up by SceneTestCase.setUp, so + # we don't need to explicitly set one up with IECore.CapturingMessageHandler() as mh : - with GafferSceneTest.ContextSanitiser() : - with Gaffer.Context() as c : + with Gaffer.Context() as c : + + c["scene:path"] = IECore.InternedStringVectorData( [ "plane" ] ) + c["scene:setName"] = IECore.InternedStringData( "a" ) + plane["out"]["globals"].getValue() + + plane["out"]["set"].getValue() - c["scene:path"] = IECore.InternedStringVectorData( [ "plane" ] ) - plane["out"]["globals"].getValue() + plane["out"]["object"].getValue() - c["scene:setName"] = IECore.InternedStringData( "a" ) - plane["out"]["set"].getValue() + c["scene:setName"] = IECore.IntData( 5 ) + with six.assertRaisesRegex( self, IECore.Exception, 'Context variable is not of type "InternedStringData"' ) : plane["out"]["object"].getValue() for message in mh.messages : @@ -66,12 +74,10 @@ def test( self ) : self.assertEqual( [ m.message for m in mh.messages ], [ + "scene:setName in context for Plane.out.globals computeNode:hash", "scene:path in context for Plane.out.globals computeNode:hash", - "scene:path in context for Plane.out.globals computeNode:hash", - "scene:path in context for Plane.out.set computeNode:hash", "scene:path in context for Plane.out.set computeNode:hash", "scene:setName in context for Plane.out.object computeNode:hash", - "scene:setName in context for Plane.out.object computeNode:hash", ] ) diff --git a/python/GafferSceneTest/CustomAttributesTest.py b/python/GafferSceneTest/CustomAttributesTest.py index b65a80be77c..d22e18eff5a 100644 --- a/python/GafferSceneTest/CustomAttributesTest.py +++ b/python/GafferSceneTest/CustomAttributesTest.py @@ -481,5 +481,24 @@ def testCompoundDataExpression( self ) : IECore.CompoundObject( { "test" : IECore.IntData( 10 ) } ) ) + def testPlugReordering( self ) : + + sphere = GafferScene.Sphere() + sphereFilter = GafferScene.PathFilter() + sphereFilter["paths"].setValue( IECore.StringVectorData( [ "/sphere" ] ) ) + + attributes = GafferScene.CustomAttributes() + attributes["in"].setInput( sphere["out"] ) + attributes["attributes"].addChild( + Gaffer.NameValuePlug( "test", 10, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + ) + attributes["attributes"].addChild( + Gaffer.NameValuePlug( "test", 20, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + ) + self.assertEqual( attributes["out"].attributes( "/sphere" )["test"].value, 20 ) + + attributes["attributes"].reorderChildren( reversed( attributes["attributes"].children() ) ) + self.assertEqual( attributes["out"].attributes( "/sphere" )["test"].value, 10 ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/DuplicateTest.py b/python/GafferSceneTest/DuplicateTest.py index 7682bf7fcd7..eceaa846304 100644 --- a/python/GafferSceneTest/DuplicateTest.py +++ b/python/GafferSceneTest/DuplicateTest.py @@ -236,5 +236,83 @@ def testPruneTarget( self ) : prune["filter"].setInput( sphereFilter["out"] ) self.assertEqual( duplicate["out"].childNames( "/" ), IECore.InternedStringVectorData( [] ) ) + @GafferTest.TestRunner.PerformanceTestMethod() + def testPerformance( self ) : + + sphere = GafferScene.Sphere() + duplicate = GafferScene.Duplicate() + duplicate["in"].setInput( sphere["out"] ) + duplicate["target"].setValue( "/sphere" ) + duplicate["transform"]["translate"]["x"].setValue( 2 ) + duplicate["copies"].setValue( 100000 ) + + with GafferTest.TestRunner.PerformanceScope() : + GafferSceneTest.traverseScene( duplicate["out"] ) + + def testFilter( self ) : + + cube = GafferScene.Cube() + cube["sets"].setValue( "boxes" ) + sphere = GafferScene.Sphere() + + group = GafferScene.Group() + group["in"][0].setInput( cube["out"] ) + group["in"][1].setInput( sphere["out"] ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/group/*" ] ) ) + + duplicate = GafferScene.Duplicate() + duplicate["in"].setInput( group["out"] ) + duplicate["filter"].setInput( filter["out"] ) + + self.assertSceneValid( duplicate["out"] ) + + self.assertEqual( + duplicate["out"].childNames( "/group" ), + IECore.InternedStringVectorData( [ + "cube", "sphere", "cube1", "sphere1", + ] ) + ) + + self.assertPathsEqual( duplicate["out"], "/group/cube", duplicate["in"], "/group/cube" ) + self.assertPathsEqual( duplicate["out"], "/group/sphere", duplicate["in"], "/group/sphere" ) + self.assertPathsEqual( duplicate["out"], "/group/cube1", duplicate["in"], "/group/cube" ) + self.assertPathsEqual( duplicate["out"], "/group/sphere1", duplicate["in"], "/group/sphere" ) + + self.assertEqual( + duplicate["out"].set( "boxes" ).value, + IECore.PathMatcher( [ "/group/cube", "/group/cube1" ] ) + ) + + def testExistingTransform( self ) : + + cube = GafferScene.Cube() + cube["transform"]["translate"]["x"].setValue( 1 ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/cube" ] ) ) + + duplicate = GafferScene.Duplicate() + duplicate["in"].setInput( cube["out"] ) + duplicate["filter"].setInput( filter["out"] ) + duplicate["transform"]["translate"]["x"].setValue( 2 ) + + self.assertSceneValid( duplicate["out"] ) + + self.assertEqual( + duplicate["out"].transform( "/cube1" ), + imath.M44f().translate( imath.V3f( 3, 0, 0 ) ) + ) + + duplicate["transform"]["translate"]["x"].setValue( 4 ) + + self.assertSceneValid( duplicate["out"] ) + + self.assertEqual( + duplicate["out"].transform( "/cube1" ), + imath.M44f().translate( imath.V3f( 5, 0, 0 ) ) + ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/ExistenceQueryTest.py b/python/GafferSceneTest/ExistenceQueryTest.py new file mode 100644 index 00000000000..71f9009632c --- /dev/null +++ b/python/GafferSceneTest/ExistenceQueryTest.py @@ -0,0 +1,317 @@ +########################################################################## +# +# Copyright (c) 2021, 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 Gaffer +import GafferScene +import GafferSceneTest + +def randomName( gen, mnc, mxc ): + + from string import ascii_lowercase + + return ''.join( gen.choice( ascii_lowercase ) + for _ in range( gen.randrange( mnc, mxc ) ) ) + +class ExistenceQueryTest( GafferSceneTest.SceneTestCase ): + + def testDefault( self ): + + eq = GafferScene.ExistenceQuery() + + self.assertTrue( eq["exists"].getValue() == False ) + self.assertTrue( eq["closestAncestor"].getValue() == "" ) + + def testLocationSlashSceneInvalid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + + eq = GafferScene.ExistenceQuery() + eq["location"].setValue( "/" ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + eq["location"].setValue( "/" + name1 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + eq["location"].setValue( "/" + name1 + "/" + name2 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + def testLocationNoSlashSceneInvalid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + + eq = GafferScene.ExistenceQuery() + eq["location"].setValue( "" ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "" ) + + eq["location"].setValue( name1 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + eq["location"].setValue( name1 + "/" + name2 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + def testLocationEmptySceneValid( self ): + + s = GafferScene.Sphere() + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( s["out"] ) + eq["location"].setValue( "" ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "" ) + + def testLocationSlashSceneValid( self ): + + s = GafferScene.Sphere() + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( s["out"] ) + eq["location"].setValue( "/" ) + + self.assertTrue( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + def testLocationNoSlashValid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( name1 ) + + self.assertTrue( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 ) + + eq["location"].setValue( name1 + "/" + name2 ) + + self.assertTrue( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 + "/" + name2 ) + + def testLocationSlashValid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( "/" + name1 ) + + self.assertTrue( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 ) + + eq["location"].setValue( "/" + name1 + "/" + name2 ) + + self.assertTrue( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 + "/" + name2 ) + + def testLocationNoSlashInValid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 6 ) + name2 = randomName( r, 5, 6 ) + name3 = randomName( r, 5, 6 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( name1 + name2 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + eq["location"].setValue( name1 + name2 + "/" + name1 + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + def testLocationSlashInValid( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 6 ) + name2 = randomName( r, 5, 6 ) + name3 = randomName( r, 5, 6 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( "/" + name1 + name2 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + eq["location"].setValue( "/" + name1 + name2 + "/" + name1 + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" ) + + def testLocationNoSlashPartial( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + name3 = randomName( r, 2, 4 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( name1 + "/" + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 ) + + eq["location"].setValue( name1 + "/" + name2 + "/" + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 + "/" + name2 ) + + def testLocationSlashPartial( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + name3 = randomName( r, 2, 4 ) + + s = GafferScene.Sphere() + s["name"].setValue( name2 ) + g = GafferScene.Group() + g["name"].setValue( name1 ) + g["in"][0].setInput( s["out"] ) + + eq = GafferScene.ExistenceQuery() + eq["scene"].setInput( g["out"] ) + eq["location"].setValue( "/" + name1 + "/" + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1) + + eq["location"].setValue( "/" + name1 + "/" + name2 + "/" + name3 ) + + self.assertFalse( eq["exists"].getValue() ) + self.assertEqual( eq["closestAncestor"].getValue(), "/" + name1 + "/" + name2 ) + + def testEmptyLocationThenMissingLocation( self ) : + + p = GafferScene.Plane() + q = GafferScene.ExistenceQuery() + q["scene"].setInput( p["out"] ) + self.assertEqual( q["closestAncestor"].getValue(), "" ) + + q["location"].setValue( "/iDontExist" ) + self.assertEqual( q["closestAncestor"].getValue(), "/" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneTest/FilterQueryTest.py b/python/GafferSceneTest/FilterQueryTest.py new file mode 100644 index 00000000000..d3238b84af2 --- /dev/null +++ b/python/GafferSceneTest/FilterQueryTest.py @@ -0,0 +1,171 @@ +########################################################################## +# +# Copyright (c) 2021, 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 IECore + +import Gaffer +import GafferScene +import GafferSceneTest + +class FilterQueryTest( GafferSceneTest.SceneTestCase ) : + + def test( self ) : + + # /group + # /grid + # /gridLines + # /centerLines + # /borderLines + # /plane + # /sphere + + grid = GafferScene.Grid() + plane = GafferScene.Plane() + sphere = GafferScene.Sphere() + + group = GafferScene.Group() + group["in"][0].setInput( grid["out"] ) + group["in"][1].setInput( plane["out"] ) + group["in"][2].setInput( sphere["out"] ) + + pathFilter = GafferScene.PathFilter() + + query = GafferScene.FilterQuery() + query["scene"].setInput( group["out"] ) + query["filter"].setInput( pathFilter["out"] ) + + allPaths = IECore.PathMatcher() + GafferScene.SceneAlgo.matchingPaths( + IECore.PathMatcher( [ "/..." ] ), + group["out"], + allPaths + ) + self.assertEqual( allPaths.size(), 8 ) + + for pattern in [ + "/", + "/group", + "/group/grid", + "/group/grid/gridLines", + "/group/*", + "/group/grid/*", + "/group/...", + "/noMatch" + ] : + + pathFilter["paths"].setValue( IECore.StringVectorData( [ pattern ] ) ) + for pathString in allPaths.paths() : + + path = GafferScene.ScenePlug.stringToPath( pathString ) + with Gaffer.Context() as c : + c["scene:path"] = path + match = pathFilter["out"].match( group["out"] ) + + query["location"].setValue( pathString ) + self.assertEqual( query["exactMatch"].getValue(), bool( match & IECore.PathMatcher.Result.ExactMatch ) ) + self.assertEqual( query["descendantMatch"].getValue(), bool( match & IECore.PathMatcher.Result.DescendantMatch ) ) + self.assertEqual( query["ancestorMatch"].getValue(), bool( match & IECore.PathMatcher.Result.AncestorMatch ) ) + + ancestor = query["closestAncestor"].getValue() + if ancestor == "" : + self.assertFalse( match & IECore.PathMatcher.Result.ExactMatch ) + self.assertFalse( match & IECore.PathMatcher.Result.AncestorMatch ) + else : + ancestor = GafferScene.ScenePlug.stringToPath( ancestor ) + with Gaffer.Context() as c : + c["scene:path"] = ancestor + self.assertTrue( pathFilter["out"].match( group["out"] ) & IECore.PathMatcher.Result.ExactMatch ) + for i in range( len( ancestor ), len( path ) ) : + c["scene:path"] = path[:i+1] + self.assertFalse( pathFilter["out"].match( group["out"] ) & IECore.PathMatcher.Result.ExactMatch ) + + def testEmptyLocation( self ) : + + plane = GafferScene.Plane() + plane["sets"].setValue( "test" ) + + setFilter = GafferScene.SetFilter() + setFilter["setExpression"].setValue( "test" ) + + query = GafferScene.FilterQuery() + query["scene"].setInput( plane["out"] ) + query["filter"].setInput( setFilter["out"] ) + + self.assertEqual( query["exactMatch"].getValue(), False ) + self.assertEqual( query["descendantMatch"].getValue(), False ) + self.assertEqual( query["ancestorMatch"].getValue(), False ) + self.assertEqual( query["closestAncestor"].getValue(), "" ) + + def testNonExistentLocation( self ) : + + plane = GafferScene.Plane() + plane["sets"].setValue( "test" ) + + setFilter = GafferScene.SetFilter() + setFilter["setExpression"].setValue( "test" ) + + query = GafferScene.FilterQuery() + query["scene"].setInput( plane["out"] ) + query["filter"].setInput( setFilter["out"] ) + query["location"].setValue( "/sphere" ) + + self.assertEqual( query["exactMatch"].getValue(), False ) + self.assertEqual( query["descendantMatch"].getValue(), False ) + self.assertEqual( query["ancestorMatch"].getValue(), False ) + self.assertEqual( query["closestAncestor"].getValue(), "" ) + + def testNonExistentLocationWithAncestors( self ) : + + plane = GafferScene.Plane() + plane["sets"].setValue( "test" ) + + setFilter = GafferScene.SetFilter() + setFilter["setExpression"].setValue( "test" ) + + query = GafferScene.FilterQuery() + query["scene"].setInput( plane["out"] ) + query["filter"].setInput( setFilter["out"] ) + query["location"].setValue( "/plane/this/does/not/exist" ) + + self.assertEqual( query["exactMatch"].getValue(), False ) + self.assertEqual( query["descendantMatch"].getValue(), False ) + self.assertEqual( query["ancestorMatch"].getValue(), False ) + self.assertEqual( query["closestAncestor"].getValue(), "" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneTest/FilterResultsTest.py b/python/GafferSceneTest/FilterResultsTest.py index f46c75d1285..377f55d8cda 100644 --- a/python/GafferSceneTest/FilterResultsTest.py +++ b/python/GafferSceneTest/FilterResultsTest.py @@ -194,9 +194,68 @@ def testComputeCacheRecursion( self ) : Gaffer.ValuePlug.clearCache() script["filterResults2"]["out"].getValue( h ) + def testRoot( self ) : + + # /group + # /group + # /plane + # /plane + + plane = GafferScene.Plane() + + innerGroup = GafferScene.Group() + innerGroup["in"][0].setInput( plane["out"] ) + + outerGroup = GafferScene.Group() + outerGroup["in"][0].setInput( innerGroup["out"] ) + outerGroup["in"][1].setInput( plane["out"] ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/..." ] ) ) + + filterResults = GafferScene.FilterResults() + filterResults["scene"].setInput( outerGroup["out"] ) + filterResults["filter"].setInput( filter["out"] ) + + self.assertEqual( + filterResults["out"].getValue().value, + IECore.PathMatcher( [ + "/", + "/group", + "/group/group", + "/group/group/plane", + "/group/plane", + ] ) + ) + + hash = filterResults["out"].hash() + + filterResults["root"].setValue( "/group/group" ) + self.assertEqual( + filterResults["out"].getValue().value, + IECore.PathMatcher( [ + "/group/group", + "/group/group/plane", + ] ) + ) + self.assertNotEqual( filterResults["out"].hash(), hash ) + + def testRootMatchVsNoMatch( self ) : + + plane = GafferScene.Plane() + pathFilter = GafferScene.PathFilter() + + filterResults = GafferScene.FilterResults() + filterResults["scene"].setInput( plane["out"] ) + filterResults["filter"].setInput( pathFilter["out"] ) + self.assertEqual( filterResults["outStrings"].getValue(), IECore.StringVectorData() ) + + pathFilter["paths"].setValue( IECore.StringVectorData( [ "/" ] ) ) + self.assertEqual( filterResults["outStrings"].getValue(), IECore.StringVectorData( [ "/" ] ) ) + @unittest.skipIf( GafferTest.inCI(), "Performance not relevant on CI platform" ) @GafferTest.TestRunner.PerformanceTestMethod() - def testHashPerf( self ): + def testHashPerformance( self ): sphere = GafferScene.Sphere() @@ -218,5 +277,29 @@ def testHashPerf( self ): with GafferTest.TestRunner.PerformanceScope(): filterResults["out"].hash() + @unittest.skipIf( GafferTest.inCI(), "Performance not relevant on CI platform" ) + @GafferTest.TestRunner.PerformanceTestMethod() + def testComputePerformance( self ): + + sphere = GafferScene.Sphere() + + duplicate = GafferScene.Duplicate() + duplicate["in"].setInput( sphere["out"] ) + duplicate["target"].setValue( '/sphere' ) + duplicate["copies"].setValue( 1000000 ) + + pathFilter = GafferScene.PathFilter() + pathFilter["paths"].setValue( IECore.StringVectorData( [ '...' ] ) ) + + filterResults = GafferScene.FilterResults() + filterResults["scene"].setInput( duplicate["out"] ) + filterResults["filter"].setInput( pathFilter["out"] ) + + # Evaluate the root childNames beforehand to focus our timing on the compute + duplicate["out"].childNames( "/" ) + + with GafferTest.TestRunner.PerformanceScope(): + filterResults["out"].getValue() + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/InstancerTest.py b/python/GafferSceneTest/InstancerTest.py index 8f25d4a9743..268195a5abd 100644 --- a/python/GafferSceneTest/InstancerTest.py +++ b/python/GafferSceneTest/InstancerTest.py @@ -1588,12 +1588,12 @@ def testPrototypeAttributes( self ) : script["instancer"]["prototypeRootsList"].setValue( IECore.StringVectorData( [ "/foo", "/bar" ] ) ) self.assertEqual( script["instancer"]["out"].attributes( "/object/instances" ), IECore.CompoundObject() ) - self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo" )["gaffer:deformationBlur"].value, False ) - self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/bar" )["gaffer:deformationBlur"].value, True ) + self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo" ), IECore.CompoundObject() ) + self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/bar" ), IECore.CompoundObject() ) for i in [ "0", "3" ] : - self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo/{i}".format( i=i ) ), IECore.CompoundObject() ) + self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo/{i}".format( i=i ) )["gaffer:deformationBlur"].value, False ) self.assertEqual( script["instancer"]["out"].fullAttributes( "/object/instances/foo/{i}".format( i=i ) )["gaffer:deformationBlur"].value, False ) self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo/{i}/bar".format( i=i ) )["gaffer:deformationBlur"].value, True ) self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/foo/{i}/bar/sphere" ), IECore.CompoundObject() ) @@ -1601,7 +1601,7 @@ def testPrototypeAttributes( self ) : for i in [ "1", "2" ] : - self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/bar/{i}".format( i=i ) ), IECore.CompoundObject() ) + self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/bar/{i}".format( i=i ) )["gaffer:deformationBlur"].value, True ) self.assertEqual( script["instancer"]["out"].fullAttributes( "/object/instances/bar/{i}".format( i=i ) )["gaffer:deformationBlur"].value, True ) self.assertEqual( script["instancer"]["out"].attributes( "/object/instances/bar/{i}/baz".format( i=i ) ), IECore.CompoundObject() ) self.assertEqual( script["instancer"]["out"].fullAttributes( "/object/instances/bar/{i}/baz".format( i=i ) )["gaffer:deformationBlur"].value, True ) diff --git a/python/GafferSceneTest/InteractiveRenderTest.py b/python/GafferSceneTest/InteractiveRenderTest.py index 01c785a4e67..b08a014e0f2 100644 --- a/python/GafferSceneTest/InteractiveRenderTest.py +++ b/python/GafferSceneTest/InteractiveRenderTest.py @@ -1877,7 +1877,7 @@ def a() : return result - GafferScene.RendererAlgo.registerAdaptor( "Test", a ) + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", a ) s["o"] = GafferScene.Outputs() s["o"].addOutput( @@ -2095,7 +2095,7 @@ def tearDown( self ) : GafferSceneTest.SceneTestCase.tearDown( self ) - GafferScene.RendererAlgo.deregisterAdaptor( "Test" ) + GafferScene.SceneAlgo.deregisterRenderAdaptor( "Test" ) ## Should be used in test cases to create an InteractiveRender node # suitably configured for error reporting. If failOnError is diff --git a/python/GafferSceneTest/MergeScenesTest.py b/python/GafferSceneTest/MergeScenesTest.py index 4d3bed247f0..b711c140a3d 100644 --- a/python/GafferSceneTest/MergeScenesTest.py +++ b/python/GafferSceneTest/MergeScenesTest.py @@ -254,10 +254,10 @@ def testNoInputsPassThrough( self ) : merge = GafferScene.MergeScenes() self.assertScenesEqual( merge["in"][0], merge["out"] ) self.assertSceneHashesEqual( - merge["in"][0], merge["out"], - # Can't expect transform hash to be identical, because - # `SceneNode::hashTransform()` isn't called for the root. - checks = self.allSceneChecks - { "transform" } + merge["in"][0], merge["out"], + # Can't expect transform hash to be identical, because + # `SceneNode::hashTransform()` isn't called for the root. + checks = self.allSceneChecks - { "transform" } ) def testNullObject( self ) : diff --git a/python/GafferSceneTest/ParentTest.py b/python/GafferSceneTest/ParentTest.py index 4095cf4bfe1..8dc058f8e4f 100644 --- a/python/GafferSceneTest/ParentTest.py +++ b/python/GafferSceneTest/ParentTest.py @@ -35,6 +35,8 @@ ########################################################################## import os.path +import inspect +import six import imath @@ -155,6 +157,7 @@ def testChildLargerThanExistingChildren( self ) : def testEmptyParent( self ) : c = GafferScene.Cube() + c["sets"].setValue( "A" ) g = GafferScene.Group() g["in"][0].setInput( c["out"] ) @@ -528,5 +531,350 @@ def testContextVariableForParent( self ) : [ "/b/sphere" ] ) + def testParentContextVariable( self ) : + + # Parent a sphere at `/a` and a grid at `/b`. + + sphere = GafferScene.Sphere() + sphere["transform"]["translate"]["x"].setValue( 1 ) + sphere["sets"].setValue( "set1" ) + + grid = GafferScene.Grid() + grid["transform"]["translate"]["x"].setValue( 2 ) + + switch = Gaffer.NameSwitch() + switch.setup( sphere["out"] ) + switch["selector"].setValue( "${parent}" ) + switch["in"].resize( 3 ) + switch["in"][1]["name"].setValue( "/a" ) + switch["in"][1]["value"].setInput( sphere["out"] ) + switch["in"][2]["name"].setValue( "/b" ) + switch["in"][2]["value"].setInput( grid["out"] ) + + collect = GafferScene.CollectScenes() + collect["rootNames"].setValue( IECore.StringVectorData( [ "a", "b" ] ) ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/a", "/b" ] ) ) + + parent = GafferScene.Parent() + parent["in"].setInput( collect["out"] ) + parent["children"][0].setInput( switch["out"]["value"] ) + parent["filter"].setInput( filter["out"] ) + parent["parentVariable"].setValue( "parent" ) + + # Check the scene is as we expect + + self.assertSceneValid( parent["out"] ) + + self.assertEqual( parent["out"].childNames( "/a" ), IECore.InternedStringVectorData( [ "sphere" ] ) ) + self.assertEqual( parent["out"].childNames( "/a/sphere" ), IECore.InternedStringVectorData() ) + self.assertEqual( parent["out"].childNames( "/b" ), IECore.InternedStringVectorData( [ "grid" ] ) ) + self.assertEqual( parent["out"].childNames( "/b/grid" ), IECore.InternedStringVectorData( [ "gridLines", "centerLines", "borderLines" ] ) ) + + self.assertScenesEqual( + sphere["out"], + parent["out"], + scenePlug2PathPrefix = "/a" + ) + + self.assertPathHashesEqual( + sphere["out"], "/sphere", + parent["out"], "/a/sphere", + ) + + self.assertScenesEqual( + grid["out"], + parent["out"], + scenePlug2PathPrefix = "/b", + # Don't want to check sets, because the grid has no sets. + checks = self.allSceneChecks - { "sets" } + ) + + for path in [ "/grid", "/grid/centerLines", "/grid/gridLines", "/grid/borderLines" ] : + self.assertPathHashesEqual( + grid["out"], path, + parent["out"], "/b" + path, + ) + + # Rename the parent variable. This should dirty all the output plugs and make the NameSwitch + # output an empty scene. + + cs = GafferTest.CapturingSlot( parent.plugDirtiedSignal() ) + parent["parentVariable"].setValue( "x" ) + + self.assertLessEqual( # Equivalent to `assertTrue( a.issubset( b ) )`, but with more informative errors + { parent["out"][n] for n in [ "bound", "transform", "attributes", "object", "childNames", "set" ] }, + { x[0] for x in cs } + ) + + self.assertSceneValid( parent["out"] ) + self.assertEqual( parent["out"].childNames( "/a" ), IECore.InternedStringVectorData() ) + self.assertEqual( parent["out"].childNames( "/b" ), IECore.InternedStringVectorData() ) + + def testDifferentSetsPerParent( self ) : + + sphere = GafferScene.Sphere() + sphere["sets"].setValue( "roundThings" ) + + cube = GafferScene.Cube() + cube["sets"].setValue( "squareThings" ) + + switch = Gaffer.NameSwitch() + switch.setup( sphere["out"] ) + switch["selector"].setValue( "${parent}" ) + switch["in"].resize( 3 ) + switch["in"][1]["name"].setValue( "/a" ) + switch["in"][1]["value"].setInput( sphere["out"] ) + switch["in"][2]["name"].setValue( "/b" ) + switch["in"][2]["value"].setInput( cube["out"] ) + + collect = GafferScene.CollectScenes() + collect["rootNames"].setValue( IECore.StringVectorData( [ "a", "b" ] ) ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/a", "/b" ] ) ) + + parent = GafferScene.Parent() + parent["in"].setInput( collect["out"] ) + parent["children"][0].setInput( switch["out"]["value"] ) + parent["filter"].setInput( filter["out"] ) + parent["parentVariable"].setValue( "parent" ) + + self.assertEqual( set( str( x ) for x in parent["out"].setNames() ), { "roundThings", "squareThings" } ) + self.assertEqual( parent["out"].set( "roundThings" ).value, IECore.PathMatcher( [ "/a/sphere" ] ) ) + self.assertEqual( parent["out"].set( "squareThings" ).value, IECore.PathMatcher( [ "/b/cube" ] ) ) + + cube["name"].setValue( "box" ) + self.assertEqual( set( str( x ) for x in parent["out"].setNames() ), { "roundThings", "squareThings" } ) + self.assertEqual( parent["out"].set( "roundThings" ).value, IECore.PathMatcher( [ "/a/sphere" ] ) ) + self.assertEqual( parent["out"].set( "squareThings" ).value, IECore.PathMatcher( [ "/b/box" ] ) ) + + sphere["sets"].setValue( "balls" ) + self.assertEqual( set( str( x ) for x in parent["out"].setNames() ), { "balls", "squareThings" } ) + self.assertEqual( parent["out"].set( "balls" ).value, IECore.PathMatcher( [ "/a/sphere" ] ) ) + self.assertEqual( parent["out"].set( "squareThings" ).value, IECore.PathMatcher( [ "/b/box" ] ) ) + + def testDestination( self ) : + + # /group + # /sphere1 + # /sphere2 + + script = Gaffer.ScriptNode() + + script["sphere1"] = GafferScene.Sphere() + script["sphere1"]["name"].setValue( "sphere1" ) + script["sphere1"]["transform"]["translate"]["x"].setValue( 1 ) + + script["sphere2"] = GafferScene.Sphere() + script["sphere2"]["name"].setValue( "sphere2" ) + script["sphere2"]["transform"]["translate"]["x"].setValue( 2 ) + + script["group"] = GafferScene.Group() + script["group"]["in"][0].setInput( script["sphere1"]["out"] ) + script["group"]["in"][1].setInput( script["sphere2"]["out"] ) + + # "Parenting" a cube to each sphere, but putting the results at + # the root of the scene. Using an expression to vary the dimensions + # and sets of each cube. + + script["spheresFilter"] = GafferScene.PathFilter() + script["spheresFilter"]["paths"].setValue( IECore.StringVectorData( [ "/group/sphere*" ] ) ) + + script["cube"] = GafferScene.Cube() + + script["expression"] = Gaffer.Expression() + script["expression"].setExpression( inspect.cleandoc( + """ + first = context.get( "parent", "" ) == "/group/sphere1" + parent["cube"]["sets"] = "set1" if first else "set2" + parent["cube"]["dimensions"]["x"] = 1 if first else 2 + """ + ) ) + + script["parent"] = GafferScene.Parent() + script["parent"]["in"].setInput( script["group"]["out"] ) + script["parent"]["children"][0].setInput( script["cube"]["out"] ) + script["parent"]["filter"].setInput( script["spheresFilter"]["out"] ) + script["parent"]["parentVariable"].setValue( "parent" ) + script["parent"]["destination"].setValue( "${scene:path}/../.." ) + + self.assertSceneValid( script["parent"]["out"] ) + + # Because two cubes are being added below one location, the second will + # have a numeric suffix to keep it unique from the other. It's ambiguous + # as to which one should be the second, so we define it by sorting them + # based on their original parent. + + self.assertEqual( script["parent"]["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "group", "cube", "cube1" ] ) ) + self.assertEqual( script["parent"]["out"].object( "/cube" ).bound().size().x, 1 ) + self.assertEqual( script["parent"]["out"].object( "/cube1" ).bound().size().x, 2 ) + self.assertEqual( script["parent"]["out"].childNames( "/group" ), IECore.InternedStringVectorData( [ "sphere1", "sphere2" ] ) ) + self.assertEqual( script["parent"]["out"].childNames( "/cube" ), IECore.InternedStringVectorData() ) + self.assertEqual( script["parent"]["out"].childNames( "/cube1" ), IECore.InternedStringVectorData() ) + + # The contents of the sets should reflect the same sorting and uniquefying. + + self.assertEqual( script["parent"]["out"].setNames(), IECore.InternedStringVectorData( [ "set1", "set2" ] ) ) + + self.assertEqual( + script["parent"]["out"].set( "set1" ).value, + IECore.PathMatcher( [ "/cube" ] ) + ) + self.assertEqual( + script["parent"]["out"].set( "set2" ).value, + IECore.PathMatcher( [ "/cube1" ] ) + ) + + # We want the cubes to be positioned as if they were parented below the spheres. + + self.assertEqual( script["parent"]["out"].fullTransform( "/cube" ), script["parent"]["in"].fullTransform( "/group/sphere1" ) ) + self.assertEqual( script["parent"]["out"].fullTransform( "/cube1" ), script["parent"]["in"].fullTransform( "/group/sphere2" ) ) + + # And if we move the cubes to a different location, we want all that to apply still. + + script["parent"]["destination"].setValue( "/group" ) + + self.assertSceneValid( script["parent"]["out"] ) + + self.assertEqual( script["parent"]["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "group" ] ) ) + self.assertEqual( script["parent"]["out"].childNames( "/group" ), IECore.InternedStringVectorData( [ "sphere1", "sphere2", "cube", "cube1" ] ) ) + self.assertEqual( script["parent"]["out"].object( "/group/cube" ).bound().size().x, 1 ) + self.assertEqual( script["parent"]["out"].object( "/group/cube1" ).bound().size().x, 2 ) + self.assertEqual( script["parent"]["out"].childNames( "/group/cube" ), IECore.InternedStringVectorData() ) + self.assertEqual( script["parent"]["out"].childNames( "/group/cube1" ), IECore.InternedStringVectorData() ) + + self.assertEqual( script["parent"]["out"].setNames(), IECore.InternedStringVectorData( [ "set1", "set2" ] ) ) + self.assertEqual( + script["parent"]["out"].set( "set1" ).value, + IECore.PathMatcher( [ "/group/cube" ] ) + ) + self.assertEqual( + script["parent"]["out"].set( "set2" ).value, + IECore.PathMatcher( [ "/group/cube1" ] ) + ) + + self.assertEqual( script["parent"]["out"].fullTransform( "/group/cube" ), script["parent"]["in"].fullTransform( "/group/sphere1" ) ) + self.assertEqual( script["parent"]["out"].fullTransform( "/group/cube1" ), script["parent"]["in"].fullTransform( "/group/sphere2" ) ) + + def testCreateNewDestination( self ) : + + sphere = GafferScene.Sphere() + sphere["sets"].setValue( "A" ) + sphere["transform"]["translate"]["x"].setValue( 1 ) + + group = GafferScene.Group() + group["in"][0].setInput( sphere["out" ]) + group["transform"]["translate"]["y"].setValue( 2 ) + + cube = GafferScene.Cube() + cube["sets"].setValue( "A" ) + + parent = GafferScene.Parent() + parent["in"].setInput( group["out"] ) + parent["children"][0].setInput( cube["out"] ) + parent["parent"].setValue( "/group/sphere" ) + parent["destination"].setValue( "/group/parented/things" ) + + self.assertSceneValid( parent["out"] ) + + self.assertEqual( parent["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "group" ] ) ) + self.assertEqual( parent["out"].childNames( "/group" ), IECore.InternedStringVectorData( [ "sphere", "parented" ] ) ) + self.assertEqual( parent["out"].childNames( "/group/sphere" ), IECore.InternedStringVectorData() ) + self.assertEqual( parent["out"].childNames( "/group/parented" ), IECore.InternedStringVectorData( [ "things" ] ) ) + self.assertEqual( parent["out"].childNames( "/group/parented/things" ), IECore.InternedStringVectorData( [ "cube" ] ) ) + self.assertEqual( parent["out"].childNames( "/group/parented/things/cube" ), IECore.InternedStringVectorData() ) + + self.assertPathHashesEqual( parent["out"], "/group", parent["in"], "/group", checks = self.allPathChecks - { "bound", "childNames" } ) + + self.assertPathsEqual( + parent["out"], "/group/parented/things/cube", cube["out"], "/cube", + checks = self.allPathChecks - { "transform" } + ) + self.assertEqual( + parent["out"].fullTransform( "/group/parented/things/cube" ), + parent["out"].fullTransform( "/group/sphere" ) + ) + + self.assertEqual( parent["out"].set( "A" ).value, IECore.PathMatcher( [ "/group/sphere", "/group/parented/things/cube" ] ) ) + + def testNonPreExistingNestedDestination( self ) : + + sphere = GafferScene.Sphere() + cube = GafferScene.Cube() + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + group["in"][1].setInput( cube["out"] ) + + filter = GafferScene.PathFilter() + filter["paths"].setValue( IECore.StringVectorData( [ "/group/*" ] ) ) + + plane = GafferScene.Plane() + + parent = GafferScene.Parent() + parent["in"].setInput( group["out"] ) + parent["children"][0].setInput( plane["out"] ) + parent["filter"].setInput( filter["out"] ) + + spreadsheet = Gaffer.Spreadsheet() + spreadsheet["selector"].setValue( "${scene:path}" ) + spreadsheet["rows"].addColumn( parent["destination"] ) + parent["destination"].setInput( spreadsheet["out"]["destination" ] ) + spreadsheet["rows"].addRows( 2 ) + spreadsheet["rows"][1]["name"].setValue( "/group/sphere" ) + spreadsheet["rows"][1]["cells"]["destination"]["value"].setValue( "/newOuterGroup" ) + spreadsheet["rows"][2]["name"].setValue( "/group/cube" ) + spreadsheet["rows"][2]["cells"]["destination"]["value"].setValue( "/newOuterGroup/newInnerGroup" ) + + with six.assertRaisesRegex( self, Gaffer.ProcessException, 'Destination "/newOuterGroup" contains a nested destination' ) : + parent["out"].childNames( "/newOuterGroup" ) + + # Swap order, so they are likely visited the other way round when building the branches tree. + spreadsheet["rows"][1]["cells"]["destination"]["value"].setValue( "/newOuterGroup/newInnerGroup" ) + spreadsheet["rows"][2]["cells"]["destination"]["value"].setValue( "/newOuterGroup" ) + + with six.assertRaisesRegex( self, Gaffer.ProcessException, 'Destination "/newOuterGroup" contains a nested destination' ) : + parent["out"].childNames( "/newOuterGroup" ) + + def testMultipleNewDestinationsBelowOneParent( self ) : + + script = Gaffer.ScriptNode() + + script["cube"] = GafferScene.Cube() + script["sphere"] = GafferScene.Sphere() + script["group"] = GafferScene.Group() + script["group"]["in"][0].setInput( script["sphere"]["out"] ) + script["group"]["in"][1].setInput( script["cube"]["out"] ) + + script["filter"] = GafferScene.PathFilter() + script["filter"]["paths"].setValue( IECore.StringVectorData( [ "/group/cube", "/group/sphere" ] ) ) + + script["plane"] = GafferScene.Plane() + + script["parent"] = GafferScene.Parent() + script["parent"]["in"].setInput( script["group"]["out"] ) + script["parent"]["filter"].setInput( script["filter"]["out"] ) + script["parent"]["children"][0].setInput( script["plane"]["out"] ) + + script["expression"] = Gaffer.Expression() + script["expression"].setExpression( inspect.cleandoc( + """ + path = context["scene:path"] + if path[-1] == "sphere" : + parent["parent"]["destination"] = "/group/childrenOfRoundThings" + else : + parent["parent"]["destination"] = "/group/childrenOfSquareThings" + """ + ) ) + + self.assertSceneValid( script["parent"]["out"] ) + + # We expect alphabetical ordering for the new names. + self.assertEqual( + script["parent"]["out"].childNames( "/group" ), + IECore.InternedStringVectorData( [ "sphere", "cube", "childrenOfRoundThings", "childrenOfSquareThings" ] ) + ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/RenderControllerTest.py b/python/GafferSceneTest/RenderControllerTest.py index 8e69d1d7638..f53ebe96d83 100644 --- a/python/GafferSceneTest/RenderControllerTest.py +++ b/python/GafferSceneTest/RenderControllerTest.py @@ -527,5 +527,214 @@ def testNullObjects( self ) : controller.setMinimumExpansionDepth( 10 ) controller.update() + def testBlur( self ) : + + sphere = GafferScene.Sphere() + sphere["type"].setValue( sphere.Type.Primitive ) + sphereFilter = GafferScene.PathFilter() + sphereFilter["paths"].setValue( IECore.StringVectorData( [ "/sphere" ] ) ) + sphereAttributes = GafferScene.StandardAttributes() + sphereAttributes["in"].setInput( sphere["out"] ) + sphereAttributes["filter"].setInput( sphereFilter["out"] ) + + group = GafferScene.Group() + group["in"][0].setInput( sphereAttributes["out"] ) + groupFilter = GafferScene.PathFilter() + groupFilter["paths"].setValue( IECore.StringVectorData( [ "/group" ] ) ) + groupAttributes = GafferScene.StandardAttributes() + groupAttributes["in"].setInput( group["out"] ) + groupAttributes["filter"].setInput( groupFilter["out"] ) + + options = GafferScene.StandardOptions() + options["in"].setInput( groupAttributes["out"] ) + + # Animated source for testing + frame = GafferTest.FrameNode() + + # Source that isn't animated, but has an animated hash + dummyFrame = Gaffer.Node() + dummyFrame["output"] = Gaffer.FloatPlug() + dummyFrame["expression"] = Gaffer.Expression() + dummyFrame["expression"].setExpression( 'parent["output"] = context.getFrame() * 0 + 3' ) + + renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer() + controller = GafferScene.RenderController( options["out"], Gaffer.Context(), renderer ) + controller.setMinimumExpansionDepth( 2 ) + controller.update() + + def assertMotionSamples( expectedSamples, deform ) : + + capturedSphere = renderer.capturedObject( "/group/sphere" ) + self.assertIsNotNone( capturedSphere ) + + if deform: + samples = [ i.radius() for i in capturedSphere.capturedSamples() ] + times = capturedSphere.capturedSampleTimes() + else: + samples = [ i.translation().x for i in capturedSphere.capturedTransforms() ] + times = capturedSphere.capturedTransformTimes() + + self.assertEqual( len( samples ), len( expectedSamples ) ) + for (i,j) in zip( samples, expectedSamples ): + self.assertAlmostEqual( i, j, places = 6 ) + + if len( expectedSamples ) > 1 : + self.assertEqual( len( times ), len( expectedSamples ) ) + for (i,j) in zip( times, expectedSamples ): + self.assertAlmostEqual( i, j, places = 6 ) + else : + self.assertEqual( times, [] ) + + # INITIAL TRANSFORM TESTS + + assertMotionSamples( [ 0 ], False ) + sphere["transform"]["translate"]["x"].setValue( 2 ) + controller.update() + assertMotionSamples( [ 2 ], False ) + + # Hook up animated value, but blur not turned on yet + sphere["transform"]["translate"]["x"].setInput( frame["output"] ) + controller.update() + assertMotionSamples( [ 1 ], False ) + + # Test blur. + options['options']['transformBlur']["enabled"].setValue( True ) + options['options']['transformBlur']["value"].setValue( True ) + controller.update() + assertMotionSamples( [ 0.75, 1.25 ], False ) + + # Test blur on but no movement + sphere["transform"]["translate"]["x"].setInput( None ) + controller.update() + assertMotionSamples( [ 2 ], False ) + + # We get a single sample out even if the transform hash is changing but the transform isn't + sphere["transform"]["translate"]["x"].setInput( dummyFrame["output"] ) + controller.update() + assertMotionSamples( [ 3 ], False ) + + # INITIAL DEFORMATION TESTS + # Test non-blurred updates. + + assertMotionSamples( [ 1 ], True ) + sphere["radius"].setValue( 2 ) + controller.update() + assertMotionSamples( [ 2 ], True ) + + # Hook up animated value, but blur not turned on yet + sphere["radius"].setInput( frame["output"] ) + controller.update() + assertMotionSamples( [ 1 ], True ) + + # Test deformation blur. + options['options']['deformationBlur']["enabled"].setValue( True ) + options['options']['deformationBlur']["value"].setValue( True ) + controller.update() + assertMotionSamples( [ 0.75, 1.25 ], True ) + + # Test deformation blur on but no deformation + sphere["radius"].setInput( None ) + controller.update() + assertMotionSamples( [ 2 ], True ) + + + # Test shutter + sphere["transform"]["translate"]["x"].setInput( frame["output"] ) + sphere["radius"].setInput( frame["output"] ) + options['options']['shutter']["enabled"].setValue( True ) + options['options']['shutter']["value"].setValue( imath.V2f( -0.7, 0.4 ) ) + controller.update() + assertMotionSamples( [ 0.3, 1.4 ], False ) + assertMotionSamples( [ 0.3, 1.4 ], True ) + + # Test with camera shutter + camera = GafferScene.Camera() + group["in"][1].setInput( camera["out"] ) + controller.update() + self.assertEqual( renderer.capturedObject( "/group/camera" ).capturedSamples()[0].getShutter(), imath.V2f( 0.3, 1.4 ) ) + + options['options']['renderCamera']["enabled"].setValue( True ) + options['options']['renderCamera']["value"].setValue( "/group/camera" ) + controller.update() + assertMotionSamples( [ 0.3, 1.4 ], False ) + assertMotionSamples( [ 0.3, 1.4 ], True ) + camera['renderSettingOverrides']['shutter']["enabled"].setValue( True ) + camera['renderSettingOverrides']['shutter']["value"].setValue( imath.V2f( -0.5, 0.5 ) ) + controller.update() + assertMotionSamples( [ 0.5, 1.5 ], False ) + assertMotionSamples( [ 0.5, 1.5 ], True ) + self.assertEqual( renderer.capturedObject( "/group/camera" ).capturedSamples()[0].getShutter(), imath.V2f( 0.5, 1.5 ) ) + + # Test attribute controls + camera['renderSettingOverrides']['shutter']["enabled"].setValue( False ) + options['options']['shutter']["value"].setValue( imath.V2f( -0.4, 0.4 ) ) + controller.update() + assertMotionSamples( [ 0.6, 1.4 ], False ) + assertMotionSamples( [ 0.6, 1.4 ], True ) + + groupAttributes['attributes']['transformBlur']["enabled"].setValue( True ) + groupAttributes['attributes']['transformBlur']["value"].setValue( False ) + controller.update() + assertMotionSamples( [ 1 ], False ) + sphereAttributes['attributes']['transformBlur']["enabled"].setValue( True ) + controller.update() + assertMotionSamples( [ 0.6, 1.4 ], False ) + + groupAttributes['attributes']['deformationBlur']["enabled"].setValue( True ) + groupAttributes['attributes']['deformationBlur']["value"].setValue( False ) + controller.update() + assertMotionSamples( [ 1 ], True ) + sphereAttributes['attributes']['deformationBlur']["enabled"].setValue( True ) + controller.update() + assertMotionSamples( [ 0.6, 1.4 ], True ) + + groupAttributes['attributes']['transformBlurSegments']["enabled"].setValue( True ) + groupAttributes['attributes']['transformBlurSegments']["value"].setValue( 4 ) + groupAttributes['attributes']['deformationBlurSegments']["enabled"].setValue( True ) + groupAttributes['attributes']['deformationBlurSegments']["value"].setValue( 2 ) + controller.update() + assertMotionSamples( [ 0.6, 0.8, 1.0, 1.2, 1.4 ], False ) + assertMotionSamples( [ 0.6, 1.0, 1.4 ], True ) + + sphereAttributes['attributes']['transformBlurSegments']["enabled"].setValue( True ) + sphereAttributes['attributes']['transformBlurSegments']["value"].setValue( 2 ) + sphereAttributes['attributes']['deformationBlurSegments']["enabled"].setValue( True ) + sphereAttributes['attributes']['deformationBlurSegments']["value"].setValue( 4 ) + controller.update() + assertMotionSamples( [ 0.6, 1.0, 1.4 ], False ) + assertMotionSamples( [ 0.6, 0.8, 1.0, 1.2, 1.4 ], True ) + + groupAttributes['attributes']['transformBlur']["value"].setValue( True ) + groupAttributes['attributes']['deformationBlur']["value"].setValue( True ) + sphereAttributes['attributes']['transformBlur']["value"].setValue( False ) + sphereAttributes['attributes']['deformationBlur']["value"].setValue( False ) + controller.update() + assertMotionSamples( [ 1.0 ], False ) + assertMotionSamples( [ 1.0 ], True ) + + # Apply transformation to group instead of sphere, giving the same results + sphere["transform"]["translate"]["x"].setInput( None ) + sphere["transform"]["translate"]["x"].setValue( 0 ) + group["transform"]["translate"]["x"].setInput( frame["output"] ) + + groupAttributes['attributes']['transformBlur']["value"].setValue( True ) + sphereAttributes['attributes']['transformBlur']["value"].setValue( False ) + sphereAttributes['attributes']['transformBlurSegments']["enabled"].setValue( False ) + controller.update() + assertMotionSamples( [ 0.6, 0.8, 1.0, 1.2, 1.4 ], False ) + + # Override transform segments on sphere + sphereAttributes['attributes']['transformBlur']["value"].setValue( True ) + sphereAttributes['attributes']['transformBlurSegments']["enabled"].setValue( True ) + sphereAttributes['attributes']['transformBlurSegments']["value"].setValue( 1 ) + controller.update() + # Very counter-intuitively, this does nothing, because the sphere is not moving + assertMotionSamples( [ 0.6, 0.8, 1.0, 1.2, 1.4 ], False ) + + # But then if the sphere moves, the sample count does take affect + sphere["transform"]["translate"]["y"].setInput( frame["output"] ) + controller.update() + assertMotionSamples( [ 0.6, 1.4 ], False ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/RendererAlgoTest.py b/python/GafferSceneTest/RendererAlgoTest.py index 02d82d3994b..b016321dcf9 100644 --- a/python/GafferSceneTest/RendererAlgoTest.py +++ b/python/GafferSceneTest/RendererAlgoTest.py @@ -47,54 +47,6 @@ class RendererAlgoTest( GafferSceneTest.SceneTestCase ) : - def test( self ) : - - sphere = GafferScene.Sphere() - - defaultAdaptors = GafferScene.RendererAlgo.createAdaptors() - defaultAdaptors["in"].setInput( sphere["out"] ) - - def a() : - - r = GafferScene.StandardAttributes() - r["attributes"]["doubleSided"]["enabled"].setValue( True ) - r["attributes"]["doubleSided"]["value"].setValue( False ) - - return r - - GafferScene.RendererAlgo.registerAdaptor( "Test", a ) - - testAdaptors = GafferScene.RendererAlgo.createAdaptors() - testAdaptors["in"].setInput( sphere["out"] ) - - self.assertFalse( "doubleSided" in sphere["out"].attributes( "/sphere" ) ) - self.assertTrue( "doubleSided" in testAdaptors["out"].attributes( "/sphere" ) ) - self.assertEqual( testAdaptors["out"].attributes( "/sphere" )["doubleSided"].value, False ) - - GafferScene.RendererAlgo.deregisterAdaptor( "Test" ) - - defaultAdaptors2 = GafferScene.RendererAlgo.createAdaptors() - defaultAdaptors2["in"].setInput( sphere["out"] ) - - self.assertScenesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) - self.assertSceneHashesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) - - def testNullAdaptor( self ) : - - def a() : - - return None - - GafferScene.RendererAlgo.registerAdaptor( "Test", a ) - - with IECore.CapturingMessageHandler() as mh : - GafferScene.RendererAlgo.createAdaptors() - - self.assertEqual( len( mh.messages ), 1 ) - self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Warning ) - self.assertEqual( mh.messages[0].context, "RendererAlgo::createAdaptors" ) - self.assertEqual( mh.messages[0].message, "Adaptor \"Test\" returned null" ) - def testObjectSamples( self ) : frame = GafferTest.FrameNode() @@ -105,10 +57,9 @@ def testObjectSamples( self ) : with Gaffer.Context() as c : c["scene:path"] = IECore.InternedStringVectorData( [ "sphere" ] ) - samples, sampleTimes = GafferScene.RendererAlgo.objectSamples( sphere["out"], 1, imath.V2f( 0.75, 1.25 ) ) + samples = GafferScene.Private.RendererAlgo.objectSamples( sphere["out"]["object"], [ 0.75, 1.25 ] ) self.assertEqual( [ s.radius() for s in samples ], [ 0.75, 1.25 ] ) - self.assertEqual( sampleTimes, [ 0.75, 1.25 ] ) def testNonInterpolableObjectSamples( self ) : @@ -120,11 +71,10 @@ def testNonInterpolableObjectSamples( self ) : with Gaffer.Context() as c : c["scene:path"] = IECore.InternedStringVectorData( [ "procedural" ] ) - samples, sampleTimes = GafferScene.RendererAlgo.objectSamples( procedural["out"], 1, imath.V2f( 0.75, 1.25 ) ) + samples = GafferScene.Private.RendererAlgo.objectSamples( procedural["out"]["object"], [ 0.75, 1.25 ] ) self.assertEqual( len( samples ), 1 ) self.assertEqual( samples[0].parameters()["frame"].value, 1.0 ) - self.assertEqual( sampleTimes, [] ) def testObjectSamplesForCameras( self ) : @@ -135,9 +85,8 @@ def testObjectSamplesForCameras( self ) : with Gaffer.Context() as c : c["scene:path"] = IECore.InternedStringVectorData( [ "camera" ] ) - samples, sampleTimes = GafferScene.RendererAlgo.objectSamples( camera["out"], 1, imath.V2f( 0.75, 1.25 ) ) + samples = GafferScene.Private.RendererAlgo.objectSamples( camera["out"]["object"], [ 0.75, 1.25 ] ) - self.assertEqual( sampleTimes, [ 0.75, 1.25 ] ) self.assertEqual( [ s.parameters()["focalLength"].value for s in samples ], [ 0.75, 1.25 ] ) def testOutputCameras( self ) : @@ -150,7 +99,7 @@ def testOutputCameras( self ) : options = GafferScene.StandardOptions() options["in"].setInput( camera["out"] ) - renderSets = GafferScene.RendererAlgo.RenderSets( options["out"] ) + renderSets = GafferScene.Private.RendererAlgo.RenderSets( options["out"] ) def expectedCamera( frame ) : @@ -158,7 +107,7 @@ def expectedCamera( frame ) : c.setFrame( frame ) camera = options["out"].object( "/camera" ) - GafferScene.RendererAlgo.applyCameraGlobals( camera, sceneGlobals, options["out"] ) + GafferScene.SceneAlgo.applyCameraGlobals( camera, sceneGlobals, options["out"] ) return camera # Non-animated case @@ -167,7 +116,7 @@ def expectedCamera( frame ) : GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) sceneGlobals = options["out"].globals() - GafferScene.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) + GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) capturedCamera = renderer.capturedObject( "/camera" ) @@ -183,16 +132,11 @@ def expectedCamera( frame ) : GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch ) sceneGlobals = options["out"].globals() - GafferScene.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) + GafferScene.Private.RendererAlgo.outputCameras( options["out"], sceneGlobals, renderSets, renderer ) capturedCamera = renderer.capturedObject( "/camera" ) self.assertEqual( capturedCamera.capturedSamples(), [ expectedCamera( 0.75 ), expectedCamera( 1.25 ) ] ) self.assertEqual( capturedCamera.capturedSampleTimes(), [ 0.75, 1.25 ] ) - def tearDown( self ) : - - GafferSceneTest.SceneTestCase.tearDown( self ) - GafferScene.RendererAlgo.deregisterAdaptor( "Test" ) - if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/SceneAlgoTest.py b/python/GafferSceneTest/SceneAlgoTest.py index bd06777c088..8dacf5fbffd 100644 --- a/python/GafferSceneTest/SceneAlgoTest.py +++ b/python/GafferSceneTest/SceneAlgoTest.py @@ -42,6 +42,7 @@ import IECore import Gaffer +import GafferTest import GafferImage import GafferScene import GafferSceneTest @@ -75,6 +76,11 @@ def test( self ) : self.assertEqual( matchingPaths.match( "/plane/instances/group/1121/plane" ), IECore.PathMatcher.Result.ExactMatch ) self.assertEqual( matchingPaths.match( "/plane/instances/group/1121/sphere" ), IECore.PathMatcher.Result.NoMatch ) + # Test root argument + matchingPaths = IECore.PathMatcher() + GafferScene.SceneAlgo.matchingPaths( filter["out"], instancer["out"], "/plane/instances/group/1121", matchingPaths ) + self.assertEqual( matchingPaths.paths(), [ "/plane/instances/group/1121/plane" ] ) + def testExists( self ) : sphere = GafferScene.Sphere() @@ -1373,5 +1379,134 @@ def testLinkingQueries( self ) : IECore.PathMatcher( [ "/group/defaultLight", "/group/nonDefaultLight" ] ) ) + def testMatchingPathsHash( self ) : + + # /group + # /sphere + # /cube + + sphere = GafferScene.Sphere() + cube = GafferScene.Cube() + + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + group["in"][1].setInput( cube["out"] ) + + filter1 = GafferScene.PathFilter() + filter1["paths"].setValue( IECore.StringVectorData( [ "/*" ] ) ) + + filter2 = GafferScene.PathFilter() + filter2["paths"].setValue( IECore.StringVectorData( [ "/*/*" ] ) ) + + filter3 = GafferScene.PathFilter() + filter3["paths"].setValue( IECore.StringVectorData( [ "/gro??" ] ) ) + + self.assertEqual( + GafferScene.SceneAlgo.matchingPathsHash( filter1["out"], group["out"] ), + GafferScene.SceneAlgo.matchingPathsHash( filter3["out"], group["out"] ) + ) + + self.assertNotEqual( + GafferScene.SceneAlgo.matchingPathsHash( filter1["out"], group["out"] ), + GafferScene.SceneAlgo.matchingPathsHash( filter2["out"], group["out"] ) + ) + + self.assertNotEqual( + GafferScene.SceneAlgo.matchingPathsHash( filter2["out"], group["out"] ), + GafferScene.SceneAlgo.matchingPathsHash( filter3["out"], group["out"] ) + ) + + rootFilter = GafferScene.PathFilter() + rootFilter["paths"].setValue( IECore.StringVectorData( [ "/" ] ) ) + emptyFilter = GafferScene.PathFilter() + + self.assertNotEqual( + GafferScene.SceneAlgo.matchingPathsHash( rootFilter["out"], group["out"] ), + GafferScene.SceneAlgo.matchingPathsHash( emptyFilter["out"], group["out"] ) + ) + + @GafferTest.TestRunner.PerformanceTestMethod() + def testMatchingPathsHashPerformance( self ) : + + # Trick to make an infinitely recursive scene. This high-depth but + # low-branching-factor scene is in deliberate contrast to the + # lots-of-children-at-one-location scene used in + # `FilterResultsTest.testHashPerformance()`. We need good parallel + # performance for both topologies. + scene = GafferScene.ScenePlug() + scene["childNames"].setValue( IECore.InternedStringVectorData( [ "one", "two" ] ) ) + # We use a PathMatcher to limit the search recursion, matching + # every item 22 deep, but no other. + pathMatcher = IECore.PathMatcher( [ "/*" * 22 ] ) + + with GafferTest.TestRunner.PerformanceScope() : + GafferScene.SceneAlgo.matchingPathsHash( pathMatcher, scene ) + + @GafferTest.TestRunner.PerformanceTestMethod() + def testMatchingPathsPerformance( self ) : + + # See comments in `testMatchingPathsHashPerformance()`. + scene = GafferScene.ScenePlug() + scene["childNames"].setValue( IECore.InternedStringVectorData( [ "one", "two" ] ) ) + pathMatcher = IECore.PathMatcher( [ "/*" * 21 ] ) + + with GafferTest.TestRunner.PerformanceScope() : + result = IECore.PathMatcher() + GafferScene.SceneAlgo.matchingPaths( pathMatcher, scene, result ) + + def testRenderAdaptors( self ) : + + sphere = GafferScene.Sphere() + + defaultAdaptors = GafferScene.SceneAlgo.createRenderAdaptors() + defaultAdaptors["in"].setInput( sphere["out"] ) + + def a() : + + r = GafferScene.StandardAttributes() + r["attributes"]["doubleSided"]["enabled"].setValue( True ) + r["attributes"]["doubleSided"]["value"].setValue( False ) + + return r + + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", a ) + + testAdaptors = GafferScene.SceneAlgo.createRenderAdaptors() + testAdaptors["in"].setInput( sphere["out"] ) + + self.assertFalse( "doubleSided" in sphere["out"].attributes( "/sphere" ) ) + self.assertTrue( "doubleSided" in testAdaptors["out"].attributes( "/sphere" ) ) + self.assertEqual( testAdaptors["out"].attributes( "/sphere" )["doubleSided"].value, False ) + + GafferScene.SceneAlgo.deregisterRenderAdaptor( "Test" ) + + defaultAdaptors2 = GafferScene.SceneAlgo.createRenderAdaptors() + defaultAdaptors2["in"].setInput( sphere["out"] ) + + self.assertScenesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) + self.assertSceneHashesEqual( defaultAdaptors["out"], defaultAdaptors2["out"] ) + + def testNullAdaptor( self ) : + + def a() : + + return None + + GafferScene.SceneAlgo.registerRenderAdaptor( "Test", a ) + + with IECore.CapturingMessageHandler() as mh : + GafferScene.SceneAlgo.createRenderAdaptors() + + self.assertEqual( len( mh.messages ), 1 ) + self.assertEqual( mh.messages[0].level, IECore.Msg.Level.Warning ) + self.assertEqual( mh.messages[0].context, "SceneAlgo::createRenderAdaptors" ) + self.assertEqual( mh.messages[0].message, "Adaptor \"Test\" returned null" ) + + def tearDown( self ) : + + GafferSceneTest.SceneTestCase.tearDown( self ) + GafferScene.SceneAlgo.deregisterRenderAdaptor( "Test" ) + + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/SceneNodeTest.py b/python/GafferSceneTest/SceneNodeTest.py index 55fa8a8c38e..b0a72d63900 100644 --- a/python/GafferSceneTest/SceneNodeTest.py +++ b/python/GafferSceneTest/SceneNodeTest.py @@ -35,6 +35,7 @@ # ########################################################################## +import inspect import unittest import threading import imath @@ -373,6 +374,25 @@ def testChildBoundsWhenNoChildren( self ) : self.assertEqual( plane["out"].childBounds( "/plane" ), imath.Box3f() ) self.assertEqual( sphere["out"].childBounds( "/sphere" ), imath.Box3f() ) + def testEnabledEvaluationUsesGlobalContext( self ) : + + script = Gaffer.ScriptNode() + script["plane"] = GafferScene.Plane() + + script["expression"] = Gaffer.Expression() + script["expression"].setExpression( inspect.cleandoc( + """ + path = context.get("scene:path", None ) + assert( path is None ) + parent["plane"]["enabled"] = True + """ + ) ) + + with Gaffer.ContextMonitor( script["expression"] ) as monitor : + self.assertSceneValid( script["plane"]["out"] ) + + self.assertEqual( monitor.combinedStatistics().numUniqueValues( "scene:path" ), 0 ) + def setUp( self ) : GafferSceneTest.SceneTestCase.setUp( self ) diff --git a/python/GafferSceneTest/ScenePlugTest.py b/python/GafferSceneTest/ScenePlugTest.py index 7cb4b395b76..15fef098f91 100644 --- a/python/GafferSceneTest/ScenePlugTest.py +++ b/python/GafferSceneTest/ScenePlugTest.py @@ -114,8 +114,8 @@ def testFullAttributes( self ) : "children" : { "ball" : { "attributes" : { - "b" : IECore.StringData( "bOverride" ), - "c" : IECore.StringData( "c" ), + "b" : IECore.StringData( "bOverride" ), + "c" : IECore.StringData( "c" ), }, } } @@ -201,6 +201,10 @@ def testStringToPath( self ) : self.assertEqual( GafferScene.ScenePlug.stringToPath( "//a//b//" ), IECore.InternedStringVectorData( [ "a", "b" ] ) ) self.assertEqual( GafferScene.ScenePlug.stringToPath( "/foo/bar/" ), IECore.InternedStringVectorData( [ "foo", "bar" ] ) ) self.assertEqual( GafferScene.ScenePlug.stringToPath( "foo/bar/" ), IECore.InternedStringVectorData( [ "foo", "bar" ] ) ) + self.assertEqual( GafferScene.ScenePlug.stringToPath( "foo/bar/.." ), IECore.InternedStringVectorData( [ "foo" ] ) ) + self.assertEqual( GafferScene.ScenePlug.stringToPath( "foo/bar/../.." ), IECore.InternedStringVectorData( [] ) ) + self.assertEqual( GafferScene.ScenePlug.stringToPath( "foo/bar/../../.." ), IECore.InternedStringVectorData( [] ) ) + self.assertEqual( GafferScene.ScenePlug.stringToPath( "foo/bar/../toto" ), IECore.InternedStringVectorData( [ "foo", "toto" ] ) ) def testPathToString( self ) : diff --git a/python/GafferSceneTest/SceneProcessorTest.py b/python/GafferSceneTest/SceneProcessorTest.py index 2dd780e9e9d..56e62357a15 100644 --- a/python/GafferSceneTest/SceneProcessorTest.py +++ b/python/GafferSceneTest/SceneProcessorTest.py @@ -34,6 +34,7 @@ # ########################################################################## +import inspect import unittest import imath @@ -42,8 +43,9 @@ import Gaffer import GafferTest import GafferScene +import GafferSceneTest -class SceneProcessorTest( GafferTest.TestCase ) : +class SceneProcessorTest( GafferSceneTest.SceneTestCase ) : def testNumberOfInputs( self ) : @@ -146,5 +148,45 @@ def testScriptedSubGraph( self ) : s2["processor"]["visibility"].setValue( True ) self.assertEqual( s2["processor"]["out"].attributes( "/plane" )["scene:visible"].value, True ) + def testEnabledEvaluationUsesGlobalContext( self ) : + + script = Gaffer.ScriptNode() + script["plane"] = GafferScene.Plane() + script["processor"] = GafferScene.StandardAttributes() + script["processor"]["in"].setInput( script["plane"]["out"] ) + + script["expression"] = Gaffer.Expression() + script["expression"].setExpression( inspect.cleandoc( + """ + path = context.get("scene:path", None ) + assert( path is None ) + parent["processor"]["enabled"] = True + """ + ) ) + + with Gaffer.ContextMonitor( script["expression"] ) as monitor : + self.assertSceneValid( script["processor"]["out"] ) + + self.assertEqual( monitor.combinedStatistics().numUniqueValues( "scene:path" ), 0 ) + + def testEnabledPlugTypeConversion( self ) : + + plane = GafferScene.Plane() + string = GafferTest.StringInOutNode() + + processor = GafferScene.StandardAttributes() + processor["in"].setInput( plane["out"] ) + processor["attributes"]["doubleSided"]["enabled"].setValue( True ) + processor["attributes"]["doubleSided"]["value"].setValue( True ) + processor["enabled"].setInput( string["out"] ) + + string["in"].setValue( "" ) + self.assertScenesEqual( processor["in"], processor["out"] ) + self.assertSceneHashesEqual( processor["in"], processor["out"] ) + + string["in"].setValue( "x" ) + self.assertNotEqual( processor["in"].attributes( "/plane" ), processor["out"].attributes( "/plane" ) ) + self.assertNotEqual( processor["in"].attributesHash( "/plane" ), processor["out"].attributesHash( "/plane" ) ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/SceneTestCase.py b/python/GafferSceneTest/SceneTestCase.py index d5db4afc6cd..d8f0e40c691 100644 --- a/python/GafferSceneTest/SceneTestCase.py +++ b/python/GafferSceneTest/SceneTestCase.py @@ -157,14 +157,14 @@ def walkScene( scenePath ) : self.assertTrue( coordinateSystemSet.value.match( scenePath ) & IECore.PathMatcher.Result.ExactMatch, scenePath + " in __coordinateSystems set" - ) + ) attributes = scenePlug.attributes( scenePath, _copy = False ) if any( [ n == "light" or n.endswith( ":light" ) for n in attributes.keys() ] ) : self.assertTrue( lightSet.value.match( scenePath ) & IECore.PathMatcher.Result.ExactMatch, scenePath + " in __lights set" - ) + ) childNames = scenePlug.childNames( scenePath, _copy = False ) for childName in childNames : @@ -229,16 +229,14 @@ def walkScene( scenePath1, scenePath2 ) : if "sets" in checks : self.assertEqual( scenePlug1.setNames(), scenePlug2.setNames() ) for setName in scenePlug1.setNames() : - if not pathsToPrune: - self.assertEqual( scenePlug1.set( setName ), scenePlug2.set( setName ) ) - else: - if scenePlug1.set( setName ) != scenePlug2.set( setName ): - pruned1 = scenePlug1.set( setName ) - pruned2 = scenePlug2.set( setName ) - for p in pathsToPrune: - pruned1.value.prune( p ) - pruned2.value.prune( p ) - self.assertEqual( pruned1, pruned2 ) + set1 = scenePlug1.set( setName ).value + set2 = scenePlug2.set( setName ).value + for p in pathsToPrune : + set1.prune( p ) + set2.prune( p ) + if scenePlug2PathPrefix : + set2 = set2.subTree( scenePlug2PathPrefix ) + self.assertEqual( set1, set2 ) def assertPathHashesEqual( self, scenePlug1, scenePath1, scenePlug2, scenePath2, checks = allPathChecks ) : diff --git a/python/GafferSceneTest/SetTest.py b/python/GafferSceneTest/SetTest.py index 2b2291a524e..4f8a4bacf05 100644 --- a/python/GafferSceneTest/SetTest.py +++ b/python/GafferSceneTest/SetTest.py @@ -432,5 +432,88 @@ def testFilterAndContextVariables( self ) : Gaffer.ValuePlug.clearCache() self.assertEqual( sphereSet["out"].set( "set" ).value, expectedSet ) + def testSetVariable( self ) : + + sphere = GafferScene.Sphere() + cube = GafferScene.Cube() + + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + group["in"][1].setInput( cube["out"] ) + + pathFilter = GafferScene.PathFilter() + + setNode = GafferScene.Set() + setNode["in"].setInput( group["out"] ) + setNode["filter"].setInput( pathFilter["out"] ) + setNode["name"].setValue( "round square" ) + setNode["setVariable"].setValue( "set" ) + + spreadsheet = Gaffer.Spreadsheet() + spreadsheet["selector"].setValue( "${set}" ) + spreadsheet["rows"].addColumn( pathFilter["paths"] ) + spreadsheet["rows"].addRows( 2 ) + spreadsheet["rows"][1]["name"].setValue( "round" ) + spreadsheet["rows"][1]["cells"]["paths"]["value"].setValue( IECore.StringVectorData( [ "/group/sphere" ] ) ) + spreadsheet["rows"][2]["name"].setValue( "square" ) + spreadsheet["rows"][2]["cells"]["paths"]["value"].setValue( IECore.StringVectorData( [ "/group/cube" ] ) ) + + pathFilter["paths"].setInput( spreadsheet["out"]["paths"] ) + + self.assertEqual( { str( x ) for x in setNode["out"].setNames() }, { "round", "square" } ) + self.assertEqual( setNode["out"].set( "round" ).value, IECore.PathMatcher( [ "/group/sphere" ] ) ) + self.assertEqual( setNode["out"].set( "square" ).value, IECore.PathMatcher( [ "/group/cube" ] ) ) + + def testSetVariableDoesntLeakToScene( self ) : + + sphere = GafferScene.Sphere() + sphere["sets"].setValue( "testSource" ) + + setFilter = GafferScene.SetFilter() + setFilter["set"].setValue( "${setVariable}Source" ) + + setNode = GafferScene.Set() + setNode["in"].setInput( sphere["out"] ) + setNode["filter"].setInput( setFilter["out"] ) + setNode["name"].setValue( "test" ) + setNode["setVariable"].setValue( "setVariable" ) + + with Gaffer.ContextMonitor( sphere ) as monitor : + self.assertEqual( + setNode["out"].set( "test" ), + sphere["out"].set( "testSource" ) + ) + + self.assertNotIn( "setVariable", monitor.combinedStatistics().variableNames() ) + + def testSetNameWildcards( self ) : + + sphere = GafferScene.Sphere() + sphere["sets"].setValue( "test1 test2 test3" ) + + cube = GafferScene.Cube() + + group = GafferScene.Group() + group["in"][0].setInput( sphere["out"] ) + group["in"][1].setInput( cube["out"] ) + + cubeFilter = GafferScene.PathFilter() + cubeFilter["paths"].setValue( IECore.StringVectorData( [ "/group/cube" ] ) ) + + setNode = GafferScene.Set() + setNode["in"].setInput( group["out"] ) + setNode["filter"].setInput( cubeFilter["out"] ) + setNode["mode"].setValue( setNode.Mode.Add ) + setNode["name"].setValue( "test[12] test4") + + self.assertEqual( { str( x ) for x in setNode["out"].setNames() }, { "test1", "test2", "test3", "test4" } ) + self.assertEqual( setNode["out"].set( "test1" ).value, IECore.PathMatcher( [ "/group/sphere", "/group/cube" ] ) ) + self.assertEqual( setNode["out"].set( "test2" ).value, IECore.PathMatcher( [ "/group/sphere", "/group/cube" ] ) ) + self.assertEqual( setNode["out"].set( "test3" ), setNode["in"].set( "test3") ) + self.assertEqual( setNode["out"].set( "test4" ).value, IECore.PathMatcher( [ "/group/cube" ] ) ) + + setNode["mode"].setValue( setNode.Mode.Create ) + self.assertEqual( { str( x ) for x in setNode["out"].setNames() }, { "test1", "test2", "test3", "test4" } ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/TransformQueryTest.py b/python/GafferSceneTest/TransformQueryTest.py new file mode 100644 index 00000000000..7155d4029c2 --- /dev/null +++ b/python/GafferSceneTest/TransformQueryTest.py @@ -0,0 +1,654 @@ +########################################################################## +# +# Copyright (c) 2021, 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 Gaffer +import GafferScene +import GafferSceneTest + +def randomName( gen, mnc, mxc ): + + from string import ascii_lowercase + + return ''.join( gen.choice( ascii_lowercase ) + for _ in range( gen.randrange( mnc, mxc ) ) ) + +class TransformQueryTest( GafferSceneTest.SceneTestCase ): + + def testDefault( self ): + + m = imath.M44f() + v0 = imath.V3f( 0.0, 0.0, 0.0 ) + v1 = imath.V3f( 1.0, 1.0, 1.0 ) + + tq = GafferScene.TransformQuery() + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + def testNoScene( self ): + + from random import Random + from datetime import datetime + + r = Random( datetime.now() ) + + name1 = randomName( r, 5, 10 ) + name2 = randomName( r, 5, 10 ) + + m = imath.M44f() + v0 = imath.V3f( 0.0, 0.0, 0.0 ) + v1 = imath.V3f( 1.0, 1.0, 1.0 ) + + tq = GafferScene.TransformQuery() + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + tq["location"].setValue( "/" ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["location"].setValue( "/" + name1 ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + tq["location"].setValue( "/" ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["location"].setValue( "/" + name1 ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + tq["location"].setValue( "/" ) + tq["relativeLocation"].setValue( "" ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["relativeLocation"].setValue( "/" ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["location"].setValue( "/" + name1 ) + tq["relativeLocation"].setValue( "" ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["relativeLocation"].setValue( "/" + name2 ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + def testRelativeSpaceLocationsEquivalent( self ): + + from random import Random + from datetime import datetime + + s1 = GafferScene.Sphere() + gr = GafferScene.Group() + tq = GafferScene.TransformQuery() + + gr["in"][0].setInput( s1["out"] ) + tq["scene"].setInput( gr["out"] ) + + s1["name"].setValue( "sphere1" ) + gr["name"].setValue( "group" ) + + r = Random( datetime.now() ) + + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + s1["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + + gr["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + gr["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + gr["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + + m = imath.M44f() + v0 = imath.V3f( 0.0, 0.0, 0.0 ) + v1 = imath.V3f( 1.0, 1.0, 1.0 ) + + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["location"].setValue( "/group/sphere1/" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["location"].setValue( "/group/" ) + tq["relativeLocation"].setValue( "/group/" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["relativeLocation"].setValue( "/group" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["relativeLocation"].setValue( "group" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["relativeLocation"].setValue( "group/" ) + tq["invert"].setValue( False ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + tq["invert"].setValue( True ) + + self.assertEqual( tq["matrix"].getValue(), m ) + self.assertEqual( tq["translate"].getValue(), v0 ) + self.assertEqual( tq["rotate"].getValue(), v0 ) + self.assertEqual( tq["scale"].getValue(), v1 ) + + def testMatrix( self ): + + from random import Random + from datetime import datetime + + s1 = GafferScene.Sphere() + s2 = GafferScene.Sphere() + gr = GafferScene.Group() + tq = GafferScene.TransformQuery() + + gr["in"][0].setInput( s1["out"] ) + gr["in"][1].setInput( s2["out"] ) + tq["scene"].setInput( gr["out"] ) + + s1["name"].setValue( "sphere1" ) + s2["name"].setValue( "sphere2" ) + gr["name"].setValue( "group" ) + + r = Random( datetime.now() ) + + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + s1["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + s1m = s1["transform"].matrix() + + s2["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + s2["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + s2["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + s2m = s2["transform"].matrix() + + gr["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + gr["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + gr["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + grm = gr["transform"].matrix() + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m.inverse() + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ) + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ).inverse() + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( s1m * grm ) * ( s2m * grm ).inverse() + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( ( s1m * grm ) * ( s2m * grm ).inverse() ).inverse() + self.assertTrue( tq["matrix"].getValue().equalWithAbsError( m, 0.000001 ) ) + + def testTranslate( self ): + + from random import Random + from datetime import datetime + + s1 = GafferScene.Sphere() + s2 = GafferScene.Sphere() + gr = GafferScene.Group() + tq = GafferScene.TransformQuery() + + gr["in"][0].setInput( s1["out"] ) + gr["in"][1].setInput( s2["out"] ) + tq["scene"].setInput( gr["out"] ) + + s1["name"].setValue( "sphere1" ) + s2["name"].setValue( "sphere2" ) + gr["name"].setValue( "group" ) + + r = Random( datetime.now() ) + + s1["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + s1m = s1["transform"].matrix() + + s2["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + s2m = s2["transform"].matrix() + + gr["transform"]["translate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0, + ( r.random() - 0.5 ) * 2.0 ) ) + grm = gr["transform"].matrix() + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + v = s1m.translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + v = s1m.inverse().translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + v = ( s1m * grm ).translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + v = ( s1m * grm ).inverse().translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + v = ( ( s1m * grm ) * ( s2m * grm ).inverse() ).translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + v = ( ( s1m * grm ) * ( s2m * grm ).inverse() ).inverse().translation() + self.assertTrue( tq["translate"].getValue().equalWithAbsError( v, 0.000001 ) ) + + def testRotate( self ): + + from math import pi + from random import Random + from datetime import datetime + + s1 = GafferScene.Sphere() + s2 = GafferScene.Sphere() + gr = GafferScene.Group() + tq = GafferScene.TransformQuery() + + gr["in"][0].setInput( s1["out"] ) + gr["in"][1].setInput( s2["out"] ) + tq["scene"].setInput( gr["out"] ) + + s1["name"].setValue( "sphere1" ) + s2["name"].setValue( "sphere2" ) + gr["name"].setValue( "group" ) + + r = Random( datetime.now() ) + + s1["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + s1m = s1["transform"].matrix() + + s2["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + s2m = s2["transform"].matrix() + + gr["transform"]["rotate"].setValue( imath.V3f( + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0, + ( r.random() - 0.5 ) * 2.0 * 180.0 ) ) + grm = gr["transform"].matrix() + + ro = imath.Eulerf.XYZ + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m.inverse() + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ) + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ).inverse() + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( s1m * grm ) * ( s2m * grm ).inverse() + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( ( s1m * grm ) * ( s2m * grm ).inverse() ).inverse() + e = imath.Eulerf( imath.M44f.sansScalingAndShear( m ), ro ) * ( 180.0 / pi ) + self.assertTrue( tq["rotate"].getValue().equalWithAbsError( e, 0.000001 ) ) + + def testScale( self ): + + from random import Random + from datetime import datetime + + s1 = GafferScene.Sphere() + s2 = GafferScene.Sphere() + gr = GafferScene.Group() + tq = GafferScene.TransformQuery() + + gr["in"][0].setInput( s1["out"] ) + gr["in"][1].setInput( s2["out"] ) + tq["scene"].setInput( gr["out"] ) + + s1["name"].setValue( "sphere1" ) + s2["name"].setValue( "sphere2" ) + gr["name"].setValue( "group" ) + + r = Random( datetime.now() ) + + s1["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + s1m = s1["transform"].matrix() + + s2["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + s2m = s2["transform"].matrix() + + gr["transform"]["scale"].setValue( imath.V3f( + ( r.random() * 2.0 ), + ( r.random() * 2.0 ), + ( r.random() * 2.0 ) ) ) + grm = gr["transform"].matrix() + + s = imath.V3f() + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Local ) + + m = s1m.inverse() + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ) + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.World ) + + m = ( s1m * grm ).inverse() + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( False ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( s2m * grm ).inverse() * ( s1m * grm ) + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + + tq["location"].setValue( "/group/sphere1" ) + tq["relativeLocation"].setValue( "/group/sphere2" ) + tq["invert"].setValue( True ) + tq["space"].setValue( GafferScene.TransformQuery.Space.Relative ) + + m = ( ( s2m * grm ).inverse() * ( s1m * grm ) ).inverse() + imath.M44f.extractScaling( m, s ) + self.assertTrue( tq["scale"].getValue().equalWithAbsError( s, 0.000001 ) ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/GafferSceneTest/UDIMQueryTest.py b/python/GafferSceneTest/UDIMQueryTest.py index a853b2295d5..1929c603da4 100644 --- a/python/GafferSceneTest/UDIMQueryTest.py +++ b/python/GafferSceneTest/UDIMQueryTest.py @@ -138,7 +138,7 @@ def dictResult(): [(i, {'/group/plane': {'attributeA': IECore.StringData( 'test' ), 'attributeC': IECore.StringData( 'inherited' )}}) for i in [ "1002", "1003", "1012", "1013" ]] + [("1027", {'/group/plane1': {'attributeA': IECore.StringData( 'baz' ), 'attributeB': IECore.IntData( 12 ), 'attributeC': IECore.StringData( 'inherited' )}})] + [(i, {'/group/plane2': {'attributeC': IECore.StringData( 'inherited' )}}) for i in ["1038", "1039", "1048", "1049"]] - ) ) + ) ) # Switch back to default uv set so that everything lands on top of each other udimQuery["uvSet"].setValue( 'uv' ) @@ -174,5 +174,50 @@ def dictResult(): self.assertNotEqual( initialHash, udimQuery["out"].hash() ) self.assertEqual( dictResult(), {'1001': {'/test': {}}} ) + @GafferTest.TestRunner.PerformanceTestMethod( repeat = 1) + def testCollaboratePerf( self ) : + + # Set up a scene with lots of spheres with different UVs + uvSphere = GafferScene.Sphere() + uvSphere["divisions"].setValue( imath.V2i( 2000 ) ) + uvSphere["expression"] = Gaffer.Expression() + uvSphere["expression"].setExpression( 'parent["transform"]["translate"]["y"] = 2 * int( context["collect:rootName"] )' ) + + camera = GafferScene.Camera() + camera["projection"].setValue( 'orthographic' ) + + parent = GafferScene.Parent() + parent["parent"].setValue( '/' ) + parent["children"][0].setInput( camera["out"] ) + parent["in"].setInput( uvSphere["out"] ) + + sphereFilter = GafferScene.PathFilter() + sphereFilter["paths"].setValue( IECore.StringVectorData( [ '/sphere' ] ) ) + + mapProjection = GafferScene.MapProjection() + mapProjection["in"].setInput( parent["out"] ) + mapProjection["filter"].setInput( sphereFilter["out"] ) + mapProjection["camera"].setValue( '/camera' ) + + collectScenes = GafferScene.CollectScenes() + collectScenes["in"].setInput( mapProjection["out"] ) + collectScenes["rootNames"].setValue( IECore.StringVectorData( [ str(i) for i in range( 50 ) ] ) ) + + allFilter = GafferScene.PathFilter() + allFilter["paths"].setValue( IECore.StringVectorData( [ '/...' ] ) ) + + # Set up query + query = GafferScene.UDIMQuery() + query["in"].setInput( collectScenes["out"] ) + query["filter"].setInput( allFilter["out"] ) + query["outInt"] = Gaffer.IntPlug() + query["outIntExpression"] = Gaffer.Expression() + + query["outIntExpression"].setExpression( 'parent["outInt"] = len( parent["out"] ) + context["iteration"]' ) + + with GafferTest.TestRunner.PerformanceScope() : + GafferTest.parallelGetValue( query["outInt"], 400, "iteration" ) + + if __name__ == "__main__": unittest.main() diff --git a/python/GafferSceneTest/__init__.py b/python/GafferSceneTest/__init__.py index ea3a30e4761..86df2f720bf 100644 --- a/python/GafferSceneTest/__init__.py +++ b/python/GafferSceneTest/__init__.py @@ -152,6 +152,10 @@ from .DeleteSetsTest import DeleteSetsTest from .UnencapsulateTest import UnencapsulateTest from .MotionPathTest import MotionPathTest +from .FilterQueryTest import FilterQueryTest +from .TransformQueryTest import TransformQueryTest +from .BoundQueryTest import BoundQueryTest +from .ExistenceQueryTest import ExistenceQueryTest from .IECoreScenePreviewTest import * from .IECoreGLPreviewTest import * diff --git a/python/GafferSceneUI/BoundQueryUI.py b/python/GafferSceneUI/BoundQueryUI.py new file mode 100644 index 00000000000..0c020d7397d --- /dev/null +++ b/python/GafferSceneUI/BoundQueryUI.py @@ -0,0 +1,141 @@ +########################################################################## +# +# Copyright (c) 2021, 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 + +Gaffer.Metadata.registerNode( + + GafferScene.BoundQuery, + + "description", + """ + Queries a particular location in a scene and outputs the bound. + """, + + "layout:activator:spaceIsRelative", lambda node : node["space"].getValue() == GafferScene.BoundQuery.Space.Relative, + + plugs = { + + "scene" : [ + + "description", + """ + The scene to query the bounds for. + """ + + ], + + "location" : [ + + "description", + """ + The location within the scene to query the bound at. + > Note : If the location does not exist then the query will not be + > performed and all outputs will be set to their default values. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "nodule:type", "" + + ], + + "space" : [ + + "description", + """ + The space to query the bound in. + """, + + "preset:Local", GafferScene.BoundQuery.Space.Local, + "preset:World", GafferScene.BoundQuery.Space.World, + "preset:Relative", GafferScene.BoundQuery.Space.Relative, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + "nodule:type", "" + ], + + "relativeLocation" : [ + + "description", + """ + The location within the scene to use for relative space mode. + > Note : If the location does not exist then the query will not be + > performed and all outputs will be set to their default values. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "layout:activator", "spaceIsRelative", + "nodule:type", "" + + ], + + "bound" : [ + + "description", + """ + Bounding box at specified location in specified space. + """, + + "layout:section", "Settings.Outputs" + + ], + + "center" : [ + + "description", + """ + Center point vector of the requested bound. + """, + + "layout:section", "Settings.Outputs" + + ], + + "size" : [ + + "description", + """ + Size vector of the requested bound. + """, + + "layout:section", "Settings.Outputs" + + ], + } +) diff --git a/python/GafferSceneUI/BranchCreatorUI.py b/python/GafferSceneUI/BranchCreatorUI.py index 7ccf4feac20..19778783f5f 100644 --- a/python/GafferSceneUI/BranchCreatorUI.py +++ b/python/GafferSceneUI/BranchCreatorUI.py @@ -72,5 +72,17 @@ ], + "destination" : [ + + # Deliberately not documenting destination plug, so that + # it is given documentation more specific to each + # derived class. + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "ui:spreadsheet:selectorValue", "${scene:path}", + "layout:index", -1, + + ], + } ) diff --git a/python/GafferSceneUI/CollectScenesUI.py b/python/GafferSceneUI/CollectScenesUI.py index 75ebb977e64..fc287911eb1 100644 --- a/python/GafferSceneUI/CollectScenesUI.py +++ b/python/GafferSceneUI/CollectScenesUI.py @@ -101,7 +101,9 @@ > Tip : > By specifying a leaf location as the root, it is possible to > collect single objects from the input scene. - """ + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", ], diff --git a/python/GafferSceneUI/DuplicateUI.py b/python/GafferSceneUI/DuplicateUI.py index 60c698795e1..275c953e0fe 100644 --- a/python/GafferSceneUI/DuplicateUI.py +++ b/python/GafferSceneUI/DuplicateUI.py @@ -55,6 +55,8 @@ a transform applied to them. """, + "layout:activator:targetInUse", lambda node : not node["target"].isSetToDefault(), + plugs = { "parent" : [ @@ -75,9 +77,14 @@ "description", """ The part of the scene to be duplicated. + + > Caution : Deprecated. Please connect a filter instead. """, "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + # We want people to use filters rather than the `target` plug. So + # hide it unless it is already being used. + "layout:visibilityActivator", "targetInUse", ], @@ -117,5 +124,15 @@ ], + "destination" : [ + + "description", + """ + The location where the copies will be placed in the output scene. + The default value places them alongside the original. + """, + + ], + } ) diff --git a/python/GafferSceneUI/EditScopeUI.py b/python/GafferSceneUI/EditScopeUI.py index a55a65c2cf0..b4026cf470a 100644 --- a/python/GafferSceneUI/EditScopeUI.py +++ b/python/GafferSceneUI/EditScopeUI.py @@ -101,4 +101,3 @@ def __pruningKeyPress( viewer, event ) : GafferScene.EditScopeAlgo.setPruned( editScope, selection, True ) return True - diff --git a/python/GafferSceneUI/ExistenceQueryUI.py b/python/GafferSceneUI/ExistenceQueryUI.py new file mode 100644 index 00000000000..156449fd631 --- /dev/null +++ b/python/GafferSceneUI/ExistenceQueryUI.py @@ -0,0 +1,95 @@ +########################################################################## +# +# Copyright (c) 2021, 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 + +Gaffer.Metadata.registerNode( + + GafferScene.ExistenceQuery, + + "description", + """ + Queries the existence of a specified location in a scene. + """, + + plugs = { + + "scene" : [ + + "description", + """ + The scene to query. + """ + + ], + + "location" : [ + + "description", + """ + The location to query for existence. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "nodule:type", "" + + ], + + "exists" : [ + + "description", + """ + Outputs true if the specified location exists, otherwise false. + """, + + "layout:section", "Settings.Outputs" + + ], + + "closestAncestor" : [ + + "description", + """ + Path to the closest ancestor that exists. + """, + + "layout:section", "Settings.Outputs" + + ] + } +) diff --git a/python/GafferSceneUI/FilterQueryUI.py b/python/GafferSceneUI/FilterQueryUI.py new file mode 100644 index 00000000000..927f092f48c --- /dev/null +++ b/python/GafferSceneUI/FilterQueryUI.py @@ -0,0 +1,142 @@ +########################################################################## +# +# Copyright (c) 2021, 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 + +Gaffer.Metadata.registerNode( + + GafferScene.FilterQuery, + + "description", + """ + Queries a filter for a particular location in a scene + and outputs the results. + """, + + # Work around StandardNodeGadget layout bug that would + # place the `filter` nodule inside the frame. + "nodeGadget:minWidth", 12.0, + + plugs = { + + "scene" : [ + + "description", + """ + The scene to query the filter for. + """, + + ], + + "filter" : [ + + "description", + """ + The filter to query. + """, + + "plugValueWidget:type", "GafferUI.ConnectionPlugValueWidget", + "noduleLayout:section", "right", + + ], + + "location" : [ + + "description", + """ + The location within the scene to query the filter at. + + > Note : If the location does not exist then the query will not be + > performed and all outputs will be set to their default values. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "nodule:type", "", + + ], + + "exactMatch" : [ + + "description", + """ + Outputs `True` if the filter matches the location, and `False` otherwise. + """, + + "layout:section", "Settings.Outputs" + + ], + + "descendantMatch" : [ + + "description", + """ + Outputs `True` if the filter matches a descendant of the location, + and `False` otherwise. + """, + + "layout:section", "Settings.Outputs" + + ], + + "ancestorMatch" : [ + + "description", + """ + Outputs `True` if the filter matches an ancestor of the location, + and `False` otherwise. + """, + + "layout:section", "Settings.Outputs" + + ], + + "closestAncestor" : [ + + "description", + """ + Outputs the location of the first ancestor matched by the filter. + In the case of an exact match, this will be the location itself. + """, + + "layout:section", "Settings.Outputs" + + ], + + } + +) diff --git a/python/GafferSceneUI/FilterResultsUI.py b/python/GafferSceneUI/FilterResultsUI.py index 109baad13ae..084167cb552 100644 --- a/python/GafferSceneUI/FilterResultsUI.py +++ b/python/GafferSceneUI/FilterResultsUI.py @@ -77,6 +77,19 @@ ], + "root" : [ + + "description", + """ + Isolates the search to this location and its descendants. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "nodule:type", "", + + ], + "out" : [ "description", diff --git a/python/GafferSceneUI/InteractiveRenderUI.py b/python/GafferSceneUI/InteractiveRenderUI.py index 569a3d74ce6..a6f9fbca546 100644 --- a/python/GafferSceneUI/InteractiveRenderUI.py +++ b/python/GafferSceneUI/InteractiveRenderUI.py @@ -461,4 +461,3 @@ def _updateFromPlug( self, *unused ) : } ) - diff --git a/python/GafferSceneUI/OutputsUI.py b/python/GafferSceneUI/OutputsUI.py index b971b5f8d5e..f7362d59a01 100644 --- a/python/GafferSceneUI/OutputsUI.py +++ b/python/GafferSceneUI/OutputsUI.py @@ -200,16 +200,7 @@ def __init__( self, childPlug ) : self.__deleteButton.clickedSignal().connect( Gaffer.WeakMethod( self.__deleteButtonClicked ), scoped = False ) self.__deleteButton.setVisible( False ) - with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing= 4 ) as self.__detailsColumn : - - GafferUI.PlugWidget( self.__namePlug() ) - GafferUI.PlugWidget( self.__fileNamePlug() ) - GafferUI.PlugWidget( childPlug["type"] ) - GafferUI.PlugWidget( childPlug["data"] ) - GafferUI.CompoundDataPlugValueWidget( childPlug["parameters"] ) - - GafferUI.Divider( GafferUI.Divider.Orientation.Horizontal ) - + self.__detailsColumn = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing = 4 ) self.__detailsColumn.setVisible( False ) header.enterSignal().connect( Gaffer.WeakMethod( self.__enter ), scoped = False ) @@ -252,6 +243,18 @@ def __leave( self, widget ) : def __collapseButtonClicked( self, button ) : visible = not self.__detailsColumn.getVisible() + + if visible and not len( self.__detailsColumn ) : + # Build details section the first time it is shown, + # to avoid excessive overhead in the initial UI build. + with self.__detailsColumn : + GafferUI.PlugWidget( self.__namePlug() ) + GafferUI.PlugWidget( self.__fileNamePlug() ) + GafferUI.PlugWidget( self.getPlug()["type"] ) + GafferUI.PlugWidget( self.getPlug()["data"] ) + GafferUI.CompoundDataPlugValueWidget( self.getPlug()["parameters"] ) + GafferUI.Divider( GafferUI.Divider.Orientation.Horizontal ) + self.__detailsColumn.setVisible( visible ) button.setImage( "collapsibleArrowDown.png" if visible else "collapsibleArrowRight.png" ) diff --git a/python/GafferSceneUI/ParentUI.py b/python/GafferSceneUI/ParentUI.py index e4fef331e44..410527fc931 100644 --- a/python/GafferSceneUI/ParentUI.py +++ b/python/GafferSceneUI/ParentUI.py @@ -84,6 +84,35 @@ ], + "parentVariable" : [ + + "description", + """ + A context variable used to pass the location of the parent to the + upstream nodes connected into the `children` plug. This can be used + to procedurally vary the children at each different parent location. + """, + + ], + + "destination" : [ + + "description", + """ + The location where the children will be placed in the output scene. + The default is to place the children under the parent, but they may + be relocated anywhere while still inheriting the parent's transform. + This is particularly useful when parenting lights to geometry but + wanting to group them and control their visibility separately. + + When the destination is evaluated, the `${scene:path}` variable holds + the source location matched by the filter. This allows the children + to be placed relative to the "parent". For example, `${scene:path}/..` + will place the children alongside the "parent" rather than under it. + """, + + ], + } ) diff --git a/python/GafferSceneUI/SceneInspector.py b/python/GafferSceneUI/SceneInspector.py index bd7982ca07f..ac58f258d48 100644 --- a/python/GafferSceneUI/SceneInspector.py +++ b/python/GafferSceneUI/SceneInspector.py @@ -1509,7 +1509,19 @@ def update( self, targets ) : _Rail( _Rail.Type.Middle ) if i == 0 or i == ( len( history ) - 1 ) or history[i-1].value != history[i].value : - GafferUI.NameLabel( history[i].target.scene.node(), formatter = lambda l : ".".join( x.getName() for x in l ) ) + GafferUI.NameLabel( + history[i].target.scene.node(), + formatter = lambda l : ".".join( x.getName() for x in l ), + numComponents = self.__distance( history[i].target.scene.node().scriptNode(), history[i].target.scene.node() ) + ) + editButton = GafferUI.Button( image = "editOn.png", hasFrame = False ) + if not Gaffer.MetadataAlgo.readOnly( history[i].target.scene.node() ) : + editButton.clickedSignal().connect( + functools.partial( _HistorySection.__editClicked, node = history[i].target.scene.node() ), + scoped = False + ) + else : + editButton.setEnabled( False ) else : GafferUI.Label( "..." ) @@ -1548,6 +1560,23 @@ def __sourceTarget( self, target ) : return None + @staticmethod + def __editClicked( button, node ) : + + GafferUI.NodeEditor.acquire( node, floating = True ) + return True + + @staticmethod + ## \todo This might make sense as part of a future GraphComponentAlgo. + def __distance( ancestor, descendant ) : + + result = 0 + while descendant is not None and descendant != ancestor : + result += 1 + descendant = descendant.parent() + + return result + SceneInspector.HistorySection = _HistorySection ## REMOVE ME!! ########################################################################## diff --git a/python/GafferSceneUI/SeedsUI.py b/python/GafferSceneUI/SeedsUI.py index 57dbb432eaf..43d8881159d 100644 --- a/python/GafferSceneUI/SeedsUI.py +++ b/python/GafferSceneUI/SeedsUI.py @@ -117,7 +117,22 @@ "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", - ] + ], + + "destination" : [ + + "description", + """ + The location where the points primitives will be placed in the output scene. + When the destination is evaluated, the `${scene:path}` variable holds + the location of the source mesh, so the default value parents the points + under the mesh. + + > Tip : `${scene:path}/..` may be used to place the points alongside the + > source mesh. + """, + + ], } diff --git a/python/GafferSceneUI/SetUI.py b/python/GafferSceneUI/SetUI.py index 3df3098db8e..d94a2b7828d 100644 --- a/python/GafferSceneUI/SetUI.py +++ b/python/GafferSceneUI/SetUI.py @@ -112,14 +112,27 @@ def getMenuPathFunction() : "description", """ - The name of the set that will be created or edited. - You can create multiple set names at once by separating them with spaces. + The name of the set that will be created or edited. Multiple sets + may be created or modified by entering their names separated by + spaces. Wildcards may also be used to match multiple input sets to + be modified. """, "ui:scene:acceptsSetName", True, ], + "setVariable" : [ + + "description", + """ + A context variable created to pass the name of the set + being processed to the nodes connected to the `filter` + plug. This can be used to vary the filter for each set. + """, + + ], + "paths" : [ "description", diff --git a/python/GafferSceneUI/StandardOptionsUI.py b/python/GafferSceneUI/StandardOptionsUI.py index ecb220f4739..9f1231e3cf4 100644 --- a/python/GafferSceneUI/StandardOptionsUI.py +++ b/python/GafferSceneUI/StandardOptionsUI.py @@ -78,8 +78,6 @@ def __cameraSummary( plug ) : def __motionBlurSummary( plug ) : info = [] - if plug["cameraBlur"]["enabled"].getValue() : - info.append( "Camera " + ( "On" if plug["cameraBlur"]["value"].getValue() else "Off" ) ) if plug["transformBlur"]["enabled"].getValue() : info.append( "Transform " + ( "On" if plug["transformBlur"]["value"].getValue() else "Off" ) ) if plug["deformationBlur"]["enabled"].getValue() : @@ -326,21 +324,6 @@ def __statisticsSummary( plug ) : # Motion blur plugs - "options.cameraBlur" : [ - - "description", - """ - Whether or not camera motion is taken into - account in the renderered image. To specify the - number of segments to use for camera motion, use - a StandardAttributes node filtered for the camera. - """, - - "layout:section", "Motion Blur", - "label", "Camera", - - ], - "options.transformBlur" : [ "description", diff --git a/python/GafferSceneUI/TransformQueryUI.py b/python/GafferSceneUI/TransformQueryUI.py new file mode 100644 index 00000000000..21c7216fe4e --- /dev/null +++ b/python/GafferSceneUI/TransformQueryUI.py @@ -0,0 +1,158 @@ +########################################################################## +# +# Copyright (c) 2021, 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 + +Gaffer.Metadata.registerNode( + + GafferScene.TransformQuery, + + "description", + """ + Queries a particular location in a scene and outputs the transform. + """, + + "layout:activator:spaceIsRelative", lambda node : node["space"].getValue() == GafferScene.TransformQuery.Space.Relative, + + plugs = { + + "scene" : [ + + "description", + """ + The scene to query the transform for. + """ + + ], + + "location" : [ + + "description", + """ + The location within the scene to query the transform at. + + > Note : If the location does not exist then the query will not be + > performed and all outputs will be set to their default values. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "nodule:type", "" + + ], + + "space" : [ + + "description", + """ + The space to query the transform. + """, + + "preset:Local", GafferScene.TransformQuery.Space.Local, + "preset:World", GafferScene.TransformQuery.Space.World, + "preset:Relative", GafferScene.TransformQuery.Space.Relative, + + "plugValueWidget:type", "GafferUI.PresetsPlugValueWidget", + "nodule:type", "" + ], + + "relativeLocation" : [ + + "description", + """ + The location within the scene to query the transform for relative space mode. + + > Note : If the location does not exist then the query will not be + > performed and all outputs will be set to their default values. + """, + + "plugValueWidget:type", "GafferSceneUI.ScenePathPlugValueWidget", + "scenePathPlugValueWidget:scene", "scene", + "layout:activator", "spaceIsRelative", + "nodule:type", "" + + ], + + "invert" : [ + + "description", + """ + Invert the result transform. + """, + "nodule:type", "" + + ], + + "matrix" : [ + + "description", + """ + 4x4 matrix of the requested transform. + """, + + "layout:section", "Settings.Outputs" + + ], + + "translate" : [ + "description", + """ + Translation component of requested transform. + """, + + "layout:section", "Settings.Outputs" + ], + + "rotate" : [ + "description", + """ + Rotation component of requested transform (degrees). + """, + + "layout:section", "Settings.Outputs" + ], + + "scale" : [ + "description", + """ + Scaling component of requested transform. + """, + + "layout:section", "Settings.Outputs" + ], + } +) diff --git a/python/GafferSceneUI/UDIMQueryUI.py b/python/GafferSceneUI/UDIMQueryUI.py index 746c8ef7b53..d1049639269 100644 --- a/python/GafferSceneUI/UDIMQueryUI.py +++ b/python/GafferSceneUI/UDIMQueryUI.py @@ -60,11 +60,11 @@ ``` { "1001" : { - "/mesh1" : { "bake:resolution", 512 }, - "/mesh2" : { "bake:resolution", 1024 }, + "/mesh1" : { "bake:resolution", 512 }, + "/mesh2" : { "bake:resolution", 1024 }, }, "1002" : { - "/mesh1" : { "bake:resolution", 512 }, + "/mesh1" : { "bake:resolution", 512 }, }, } ``` diff --git a/python/GafferSceneUI/__init__.py b/python/GafferSceneUI/__init__.py index 4500f05ac28..7bb445268a0 100644 --- a/python/GafferSceneUI/__init__.py +++ b/python/GafferSceneUI/__init__.py @@ -165,6 +165,10 @@ from . import CurveSamplerUI from . import UnencapsulateUI from . import MotionPathUI +from . import FilterQueryUI +from . import TransformQueryUI +from . import BoundQueryUI +from . import ExistenceQueryUI # then all the PathPreviewWidgets. note that the order # of import controls the order of display. diff --git a/python/GafferSceneUITest/SourceSetTest.py b/python/GafferSceneUITest/SourceSetTest.py deleted file mode 100644 index 6f35146d342..00000000000 --- a/python/GafferSceneUITest/SourceSetTest.py +++ /dev/null @@ -1,334 +0,0 @@ -########################################################################## -# -# Copyright (c) 2019, 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 imath - -import IECore - -import Gaffer -import GafferScene -import GafferSceneTest -import GafferUITest -import GafferSceneUI - -class SourceSetTest( GafferUITest.TestCase ) : - - def testAccessors( self ) : - - s = Gaffer.StandardSet() - c = Gaffer.Context() - - h = GafferSceneUI.SourceSet( c, s ) - - self.assertEqual( h.getContext(), c ) - self.assertEqual( h.getNodeSet(), s ) - - self.assertEqual( h.size(), 0 ) - - s2 = Gaffer.StandardSet() - c2 = Gaffer.Context() - - h.setContext( c2 ) - self.assertEqual( h.getContext(), c2 ) - self.assertEqual( h.getNodeSet(), s ) - - h.setNodeSet( s2 ) - self.assertEqual( h.getContext(), c2 ) - self.assertEqual( h.getNodeSet(), s2 ) - - def testSource( self ) : - - s = Gaffer.ScriptNode() - - s["pA"] = GafferScene.Plane() - s["pA"]["name"].setValue( "planeA" ) - s["pB"] = GafferScene.Plane() - s["pB"]["name"].setValue( "planeB" ) - s["g"] = GafferScene.Group() - s["g"]["in"][0].setInput( s["pA"]["out"] ) - s["g"]["in"][1].setInput( s["pB"]["out"] ) - - s["s"] = GafferSceneTest.TestShader() - s["a"] = GafferScene.ShaderAssignment() - s["a"]["in"].setInput( s["g"]["out"] ) - s["a"]["shader"].setInput( s["s"]["out"] ) - - s["s2"] = GafferSceneTest.TestShader() - s["a2"] = GafferScene.ShaderAssignment() - s["a2"]["in"].setInput( s["a"]["out"] ) - s["a2"]["shader"].setInput( s["s2"]["out"] ) - - s["g2"] = GafferScene.Group() - s["g2"]["in"][0].setInput( s["a2"]["out"] ) - - s["o"] = GafferScene.SceneWriter() - s["o"]["in"].setInput( s["g2"]["out"] ) - - n = Gaffer.StandardSet() - c = Gaffer.Context() - - self.assertEqual( len(GafferSceneUI.ContextAlgo.getSelectedPaths( c ).paths()), 0 ) - - h = GafferSceneUI.SourceSet( c, n ) - self.assertEqual( h.size(), 0 ) - - a = "/group/group/planeA" - b = "/group/group/planeB" - - n.add( s["o"] ) - - # Test scene selection changed - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ a ] ) ) - self.assertEqual( set(h), { s["pA"] } ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ a, b ] ) ) - - self.assertEqual( set(h), { s["pA"] } ) - - # Test defaulting to last input node if no valid plug or path - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher() ) - self.assertEqual( set(h), { s["o"] } ) - - # Test nodes changed - - n.clear() - self.assertEqual( h.size(), 0 ) - - n.add( s["g2" ] ) - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ a ] ) ) - self.assertEqual( set(h), { s["pA"] } ) - - n.remove( s["g2"] ) - n.add( s["g"] ) - self.assertEqual( set(h), { s["g"] } ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ "/group/planeA" ] ) ) - self.assertEqual( set(h), { s["pA"] } ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ "/group" ] ) ) - n.clear() - n.add( s["g2"] ) - self.assertEqual( set(h), { s["g2"] } ) - - # Test incoming scene dirtied - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ b ] ) ) - self.assertEqual( set(h), { s["pB"] } ) - - s["pB"]["name"].setValue( "notPlaneB" ) - self.assertEqual( set(h), { s["g2"] } ) - - # Test multiple non-scene nodes - n.clear() - n.add( s["s"] ) - self.assertEqual( set(h), { s["s"] } ) - n.add( s["s2"] ) - self.assertEqual( set(h), { s["s2"] } ) - n.remove( s["s2"] ) - self.assertEqual( set(h), { s["s"] } ) - - def testReadOnlyNodeHierarchies( self ) : - - class TestShaderBall( GafferScene.ShaderBall ) : - def __init__( self, name = "TestShaderBall" ) : - GafferScene.ShaderBall.__init__( self, name ) - - # Test to make sure we don't surface internal nodes - - s = Gaffer.StandardSet() - c = Gaffer.Context() - h = GafferSceneUI.SourceSet( c, s ) - - b = TestShaderBall() - s.add( b ) - self.assertEqual( set(h), set( [ b, ] ) ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ "/sphere" ] ) ) - self.assertEqual( set(h), set( [ b, ] ) ) - - def testSignals( self ) : - - s = Gaffer.ScriptNode() - - s["pA"] = GafferScene.Plane() - s["pA"]["name"].setValue( "planeA" ) - s["pB"] = GafferScene.Plane() - s["pB"]["name"].setValue( "planeB" ) - s["g"] = GafferScene.Group() - s["g"]["in"][0].setInput( s["pA"]["out"] ) - s["g"]["in"][1].setInput( s["pB"]["out"] ) - - s["g2"] = GafferScene.Group() - s["g2"]["in"][0].setInput( s["g"]["out"] ) - - mirror = set() - - def added( _, member ) : - mirror.add( member ) - - def removed( _, member ) : - mirror.remove( member ) - - n = Gaffer.StandardSet() - c = Gaffer.Context() - - h = GafferSceneUI.SourceSet( c, n ) - ca = h.memberAddedSignal().connect( added ) - cr = h.memberRemovedSignal().connect( removed ) - - self.assertEqual( h.size(), 0 ) - self.assertEqual( len(mirror), 0 ) - - a = "/group/group/planeA" - b = "/group/group/planeB" - - n.add( s["g2"] ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ a ] ) ) - self.assertEqual( set(h), mirror ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ b ] ) ) - self.assertEqual( set(h), mirror ) - - GafferSceneUI.ContextAlgo.setSelectedPaths( c, IECore.PathMatcher( [ a, b ] ) ) - self.assertEqual( set(h), mirror ) - - def testSignalOrder( self ) : - - s = Gaffer.ScriptNode() - - s["p1"] = GafferScene.Plane() - s["p2"] = GafferScene.Plane() - - n = Gaffer.StandardSet() - c = Gaffer.Context() - h = GafferSceneUI.SourceSet( c, n ) - - callbackFailures = { "added" : 0, "removed" : 0 } - - # Check we have no members when one is removed as we're - # defined as only ever containing one node. We can't assert - # here as the exception gets eaten and the test passes anyway - def removed( _, member ) : - if set(h) != set() : - callbackFailures["removed"] += 1 - - cr = h.memberRemovedSignal().connect( removed ) - - n.add( s["p1"] ) - n.add( s["p2"] ) - n.remove( s["p1"] ) - n.remove( s["p2"] ) - - self.assertEqual( callbackFailures["removed"], 0 ) - - # Check member is added before signal, same deal re: asserts - def added( _, member ) : - if set(h) != { s["p1"] } : - callbackFailures["added"] += 1 - - ca = h.memberAddedSignal().connect( added ) - - n.add( s["p1"] ) - self.assertEqual( callbackFailures["added"], 0 ) - - def testGILManagement( self ) : - - script = Gaffer.ScriptNode() - - # Build a contrived scene that will cause `childNames` queries to spawn - # a threaded compute that will execute a Python expression. - - script["plane"] = GafferScene.Plane() - script["plane"]["divisions"].setValue( imath.V2i( 50 ) ) - - script["planeFilter"] = GafferScene.PathFilter() - script["planeFilter"]["paths"].setValue( IECore.StringVectorData( [ "/plane" ] ) ) - - script["sphere"] = GafferScene.Sphere() - - script["instancer"] = GafferScene.Instancer() - script["instancer"]["in"].setInput( script["plane"]["out"] ) - script["instancer"]["prototypes"].setInput( script["sphere"]["out"] ) - script["instancer"]["filter"].setInput( script["planeFilter"]["out"] ) - - script["instanceFilter"] = GafferScene.PathFilter() - script["instanceFilter"]["paths"].setValue( IECore.StringVectorData( [ "/plane/instances/*/*" ] ) ) - - script["cube"] = GafferScene.Cube() - - script["parent"] = GafferScene.Parent() - script["parent"]["in"].setInput( script["instancer"]["out"] ) - script["parent"]["children"][0].setInput( script["cube"]["out"] ) - script["parent"]["filter"].setInput( script["instanceFilter"]["out"] ) - - script["expression"] = Gaffer.Expression() - script["expression"].setExpression( 'parent["sphere"]["name"] = context["sphereName"]' ) - - # Test that the `SourceSet` constructor releases the GIL so that the compute - # doesn't hang. If we're lucky, the expression executes on the main - # thread anyway, so loop to give it plenty of chances to fail. - - for i in range( 0, 100 ) : - - context = Gaffer.Context() - context["sphereName"] = "sphere{}".format( i ) - GafferSceneUI.ContextAlgo.setSelectedPaths( - context, - IECore.PathMatcher( [ - "/plane/instances/{}/2410/cube".format( context["sphereName"] ) - ] ) - ) - - sourceSet = GafferSceneUI.SourceSet( context, Gaffer.StandardSet( [ script["parent"] ] ) ) - - def testNullConstructorArguments( self ) : - - sphere = GafferScene.Sphere() - context = Gaffer.Context() - - with self.assertRaises( Exception ) : - GafferSceneUI.SourceSet( None, Gaffer.StandardSet( [ sphere ] ) ) - - with self.assertRaises( Exception ) : - GafferSceneUI.SourceSet( context, None ) - - with self.assertRaises( Exception ) : - GafferSceneUI.SourceSet( None, None ) - -if __name__ == "__main__": - unittest.main() diff --git a/python/GafferSceneUITest/__init__.py b/python/GafferSceneUITest/__init__.py index 3456951616c..b40299a3333 100644 --- a/python/GafferSceneUITest/__init__.py +++ b/python/GafferSceneUITest/__init__.py @@ -50,7 +50,6 @@ from .CameraToolTest import CameraToolTest from .VisualiserTest import VisualiserTest from .TransformToolTest import TransformToolTest -from .SourceSetTest import SourceSetTest from .CropWindowToolTest import CropWindowToolTest from .NodeUITest import NodeUITest diff --git a/python/GafferTest/CompoundNumericPlugTest.py b/python/GafferTest/CompoundNumericPlugTest.py index 6115a06e56a..fe33452134f 100644 --- a/python/GafferTest/CompoundNumericPlugTest.py +++ b/python/GafferTest/CompoundNumericPlugTest.py @@ -175,6 +175,21 @@ def testDynamicSerialisation( self ) : self.assertEqual( s["n"]["p"].getValue(), imath.V3f( 1, 2, 3 ) ) + def testDynamicSerialisationNonDefaultInterpretation( self ): + + s = Gaffer.ScriptNode() + n = Gaffer.Node() + n["p"] = Gaffer.V3fPlug( flags=Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, interpretation = IECore.GeometricData.Interpretation.Vector ) + n["p"].setValue( imath.V3f( 1, 2, 3 ) ) + s["n"] = n + + ss = s.serialise() + + s = Gaffer.ScriptNode() + s.execute( ss ) + + self.assertEqual( s["n"]["p"].interpretation(), IECore.GeometricData.Interpretation.Vector ) + def testDynamicSerialisationWithConnection( self ) : s = Gaffer.ScriptNode() diff --git a/python/GafferTest/ContextTest.py b/python/GafferTest/ContextTest.py index 4f28e7e7aef..19b1881787d 100644 --- a/python/GafferTest/ContextTest.py +++ b/python/GafferTest/ContextTest.py @@ -65,33 +65,37 @@ def testChangedSignal( self ) : def f( context, name ) : self.assertTrue( context.isSame( c ) ) - changes.append( ( name, context.get( name, None ) ) ) + changes.append( ( name, context.get( name, None ), context.hash() ) ) cn = c.changedSignal().connect( f ) c["a"] = 2 - self.assertEqual( changes, [ ( "a", 2 ) ] ) + hash1 = c.hash() + self.assertEqual( changes, [ ( "a", 2, hash1 ) ] ) c["a"] = 3 - self.assertEqual( changes, [ ( "a", 2 ), ( "a", 3 ) ] ) + hash2 = c.hash() + self.assertEqual( changes, [ ( "a", 2, hash1 ), ( "a", 3, hash2 ) ] ) c["b"] = 1 - self.assertEqual( changes, [ ( "a", 2 ), ( "a", 3 ), ( "b", 1 ) ] ) + hash3 = c.hash() + self.assertEqual( changes, [ ( "a", 2, hash1 ), ( "a", 3, hash2 ), ( "b", 1, hash3 ) ] ) - # when an assignment makes no actual change, the signal should not - # be triggered again. + # Assigning the same value again should not trigger the change signal c["b"] = 1 - self.assertEqual( changes, [ ( "a", 2 ), ( "a", 3 ), ( "b", 1 ) ] ) + self.assertEqual( changes, [ ( "a", 2, hash1 ), ( "a", 3, hash2 ), ( "b", 1, hash3 ) ] ) # Removing variables should also trigger the changed signal. del changes[:] c.remove( "a" ) - self.assertEqual( changes, [ ( "a", None ) ] ) + hash4 = c.hash() + self.assertEqual( changes, [ ( "a", None, hash4 ) ] ) del c["b"] - self.assertEqual( changes, [ ( "a", None ), ( "b", None ) ] ) + hash5 = c.hash() + self.assertEqual( changes, [ ( "a", None, hash4 ), ( "b", None, hash5 ) ] ) def testTypes( self ) : @@ -100,63 +104,135 @@ def testTypes( self ) : c["int"] = 1 self.assertEqual( c["int"], 1 ) self.assertEqual( c.get( "int" ), 1 ) + compare = c.hash() c.set( "int", 2 ) self.assertEqual( c["int"], 2 ) self.assertIsInstance( c["int"], int ) + self.assertNotEqual( compare, c.hash() ) c["float"] = 1.0 self.assertEqual( c["float"], 1.0 ) self.assertEqual( c.get( "float" ), 1.0 ) + compare = c.hash() c.set( "float", 2.0 ) self.assertEqual( c["float"], 2.0 ) self.assertIsInstance( c["float"], float ) + self.assertNotEqual( compare, c.hash() ) c["string"] = "hi" self.assertEqual( c["string"], "hi" ) self.assertEqual( c.get( "string" ), "hi" ) + compare = c.hash() c.set( "string", "bye" ) self.assertEqual( c["string"], "bye" ) self.assertIsInstance( c["string"], str ) + self.assertNotEqual( compare, c.hash() ) c["v2i"] = imath.V2i( 1, 2 ) self.assertEqual( c["v2i"], imath.V2i( 1, 2 ) ) self.assertEqual( c.get( "v2i" ), imath.V2i( 1, 2 ) ) - c.set( "v2i", imath.V2i( 1, 2 ) ) - self.assertEqual( c["v2i"], imath.V2i( 1, 2 ) ) + compare = c.hash() + c.set( "v2i", imath.V2i( 3, 4 ) ) + self.assertEqual( c["v2i"], imath.V2i( 3, 4 ) ) self.assertIsInstance( c["v2i"], imath.V2i ) + self.assertNotEqual( compare, c.hash() ) c["v3i"] = imath.V3i( 1, 2, 3 ) self.assertEqual( c["v3i"], imath.V3i( 1, 2, 3 ) ) self.assertEqual( c.get( "v3i" ), imath.V3i( 1, 2, 3 ) ) - c.set( "v3i", imath.V3i( 1, 2, 3 ) ) - self.assertEqual( c["v3i"], imath.V3i( 1, 2, 3 ) ) + compare = c.hash() + c.set( "v3i", imath.V3i( 4, 5, 6 ) ) + self.assertEqual( c["v3i"], imath.V3i( 4, 5, 6 ) ) self.assertIsInstance( c["v3i"], imath.V3i ) + self.assertNotEqual( compare, c.hash() ) c["v2f"] = imath.V2f( 1, 2 ) self.assertEqual( c["v2f"], imath.V2f( 1, 2 ) ) self.assertEqual( c.get( "v2f" ), imath.V2f( 1, 2 ) ) - c.set( "v2f", imath.V2f( 1, 2 ) ) - self.assertEqual( c["v2f"], imath.V2f( 1, 2 ) ) + compare = c.hash() + c.set( "v2f", imath.V2f( 3, 4 ) ) + self.assertEqual( c["v2f"], imath.V2f( 3, 4 ) ) self.assertIsInstance( c["v2f"], imath.V2f ) + self.assertNotEqual( compare, c.hash() ) c["v3f"] = imath.V3f( 1, 2, 3 ) self.assertEqual( c["v3f"], imath.V3f( 1, 2, 3 ) ) self.assertEqual( c.get( "v3f" ), imath.V3f( 1, 2, 3 ) ) - c.set( "v3f", imath.V3f( 1, 2, 3 ) ) - self.assertEqual( c["v3f"], imath.V3f( 1, 2, 3 ) ) + compare = c.hash() + c.set( "v3f", imath.V3f( 4, 5, 6 ) ) + self.assertEqual( c["v3f"], imath.V3f( 4, 5, 6 ) ) self.assertIsInstance( c["v3f"], imath.V3f ) + self.assertNotEqual( compare, c.hash() ) + + def testSwitchTypes( self ) : + + c = Gaffer.Context() + + c["x"] = 1 + self.assertEqual( c["x"], 1 ) + self.assertEqual( c.get( "x" ), 1 ) + c.set( "x", 2 ) + self.assertEqual( c["x"], 2 ) + self.assertIsInstance( c["x"], int ) + + c["x"] = 1.0 + self.assertEqual( c["x"], 1.0 ) + self.assertEqual( c.get( "x" ), 1.0 ) + c.set( "x", 2.0 ) + self.assertEqual( c["x"], 2.0 ) + self.assertIsInstance( c["x"], float ) + + c["x"] = "hi" + self.assertEqual( c["x"], "hi" ) + self.assertEqual( c.get( "x" ), "hi" ) + c.set( "x", "bye" ) + self.assertEqual( c["x"], "bye" ) + self.assertIsInstance( c["x"], str ) + + c["x"] = imath.V2i( 1, 2 ) + self.assertEqual( c["x"], imath.V2i( 1, 2 ) ) + self.assertEqual( c.get( "x" ), imath.V2i( 1, 2 ) ) + c.set( "x", imath.V2i( 1, 2 ) ) + self.assertEqual( c["x"], imath.V2i( 1, 2 ) ) + self.assertIsInstance( c["x"], imath.V2i ) + + c["x"] = imath.V3i( 1, 2, 3 ) + self.assertEqual( c["x"], imath.V3i( 1, 2, 3 ) ) + self.assertEqual( c.get( "x" ), imath.V3i( 1, 2, 3 ) ) + c.set( "x", imath.V3i( 1, 2, 3 ) ) + self.assertEqual( c["x"], imath.V3i( 1, 2, 3 ) ) + self.assertIsInstance( c["x"], imath.V3i ) + + c["x"] = imath.V2f( 1, 2 ) + self.assertEqual( c["x"], imath.V2f( 1, 2 ) ) + self.assertEqual( c.get( "x" ), imath.V2f( 1, 2 ) ) + c.set( "x", imath.V2f( 1, 2 ) ) + self.assertEqual( c["x"], imath.V2f( 1, 2 ) ) + self.assertIsInstance( c["x"], imath.V2f ) + + c["x"] = imath.V3f( 1, 2, 3 ) + self.assertEqual( c["x"], imath.V3f( 1, 2, 3 ) ) + self.assertEqual( c.get( "x" ), imath.V3f( 1, 2, 3 ) ) + c.set( "x", imath.V3f( 1, 2, 3 ) ) + self.assertEqual( c["x"], imath.V3f( 1, 2, 3 ) ) + self.assertIsInstance( c["x"], imath.V3f ) def testCopying( self ) : c = Gaffer.Context() c["i"] = 10 + c["testIntVector"] = IECore.IntVectorData( [ 10 ] ) c2 = Gaffer.Context( c ) self.assertEqual( c2["i"], 10 ) + self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) c["i"] = 1 + c2["testIntVector"] = IECore.IntVectorData( [ 20 ] ) self.assertEqual( c["i"], 1 ) self.assertEqual( c2["i"], 10 ) + self.assertEqual( c["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) + self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 20 ] ) ) def testEquality( self ) : @@ -347,7 +423,7 @@ def testManyContexts( self ) : GafferTest.testManyContexts() - def testGetWithAndWithoutCopying( self ) : + def testGetCopies( self ) : c = Gaffer.Context() c["test"] = IECore.IntVectorData( [ 1, 2 ] ) @@ -358,137 +434,13 @@ def testGetWithAndWithoutCopying( self ) : c["test"].append( 10 ) self.assertEqual( c["test"], IECore.IntVectorData( [ 1, 2 ] ) ) - # if we ask nicely, we can get a reference to the internal - # value without any copying. - self.assertTrue( c.get( "test", _copy=False ).isSame( c.get( "test", _copy=False ) ) ) - # but then if we modify the returned value, we are changing the - # context itself too. this should be avoided - we're just doing it - # here to test that we are indeed referencing the internal value. - c.get( "test", _copy=False ).append( 10 ) - self.assertEqual( c["test"], IECore.IntVectorData( [ 1, 2, 10 ] ) ) - - def testGetWithDefaultAndCopyArgs( self ) : + def testGetWithDefault( self ) : c = Gaffer.Context() c["test"] = IECore.IntVectorData( [ 1, 2 ] ) - self.assertTrue( c.get( "test", 10, _copy=False ).isSame( c.get( "test", 20, _copy=False ) ) ) - self.assertTrue( c.get( "test", defaultValue=10, _copy=False ).isSame( c.get( "test", defaultValue=20, _copy=False ) ) ) - - def testCopyWithSharedOwnership( self ) : - - c1 = Gaffer.Context() - - c1["testInt"] = 10 - c1["testIntVector"] = IECore.IntVectorData( [ 10 ] ) - - self.assertEqual( c1["testInt"], 10 ) - self.assertEqual( c1["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - r = c1.get( "testIntVector", _copy=False ).refCount() - - c2 = Gaffer.Context( c1, ownership = Gaffer.Context.Ownership.Shared ) - - self.assertEqual( c2["testInt"], 10 ) - self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - c1["testInt"] = 20 - self.assertEqual( c1["testInt"], 20 ) - # c2 has changed too! with slightly improved performance comes - # great responsibility! - self.assertEqual( c2["testInt"], 20 ) - - # both contexts reference the same object, but c2 at least owns - # a reference to its values, and can be used after c1 has been - # deleted. - self.assertTrue( c2.get( "testIntVector", _copy=False ).isSame( c1.get( "testIntVector", _copy=False ) ) ) - self.assertEqual( c2.get( "testIntVector", _copy=False ).refCount(), r + 1 ) - - del c1 - - self.assertEqual( c2["testInt"], 20 ) - self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - self.assertEqual( c2.get( "testIntVector", _copy=False ).refCount(), r ) - - def testCopyWithBorrowedOwnership( self ) : - - c1 = Gaffer.Context() - - c1["testInt"] = 10 - c1["testIntVector"] = IECore.IntVectorData( [ 10 ] ) - - self.assertEqual( c1["testInt"], 10 ) - self.assertEqual( c1["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - r = c1.get( "testIntVector", _copy=False ).refCount() - - c2 = Gaffer.Context( c1, ownership = Gaffer.Context.Ownership.Borrowed ) - - self.assertEqual( c2["testInt"], 10 ) - self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - c1["testInt"] = 20 - self.assertEqual( c1["testInt"], 20 ) - # c2 has changed too! with slightly improved performance comes - # great responsibility! - self.assertEqual( c2["testInt"], 20 ) - - # check that c2 doesn't own a reference - self.assertTrue( c2.get( "testIntVector", _copy=False ).isSame( c1.get( "testIntVector", _copy=False ) ) ) - self.assertEqual( c2.get( "testIntVector", _copy=False ).refCount(), r ) - - # make sure we delete c2 before we delete c1 - del c2 - - # check that we're ok to access c1 after deleting c2 - self.assertEqual( c1["testInt"], 20 ) - self.assertEqual( c1["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - def testSetOnBorrowedContextsDoesntAffectOriginal( self ) : - - c1 = Gaffer.Context() - - c1["testInt"] = 10 - c1["testIntVector"] = IECore.IntVectorData( [ 10 ] ) - - c2 = Gaffer.Context( c1, ownership = Gaffer.Context.Ownership.Borrowed ) - c2["testInt"] = 20 - c2["testIntVector"] = IECore.IntVectorData( [ 20 ] ) - - self.assertEqual( c1["testInt"], 10 ) - self.assertEqual( c1["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - self.assertEqual( c2["testInt"], 20 ) - self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 20 ] ) ) - - def testSetOnSharedContextsDoesntAffectOriginal( self ) : - - c1 = Gaffer.Context() - - c1["testInt"] = 10 - c1["testIntVector"] = IECore.IntVectorData( [ 10 ] ) - - c2 = Gaffer.Context( c1, ownership = Gaffer.Context.Ownership.Shared ) - c2["testInt"] = 20 - c2["testIntVector"] = IECore.IntVectorData( [ 20 ] ) - - self.assertEqual( c1["testInt"], 10 ) - self.assertEqual( c1["testIntVector"], IECore.IntVectorData( [ 10 ] ) ) - - self.assertEqual( c2["testInt"], 20 ) - self.assertEqual( c2["testIntVector"], IECore.IntVectorData( [ 20 ] ) ) - - def testSetOnSharedContextsReleasesReference( self ) : - - c1 = Gaffer.Context() - c1["testIntVector"] = IECore.IntVectorData( [ 10 ] ) - - r = c1.get( "testIntVector", _copy=False ).refCount() - - c2 = Gaffer.Context( c1, ownership = Gaffer.Context.Ownership.Shared ) - c2["testIntVector"] = IECore.IntVectorData( [ 20 ] ) - - self.assertEqual( c1.get( "testIntVector", _copy=False ).refCount(), r ) + self.assertEqual( c.get( "test", 10 ), IECore.IntVectorData( [ 1, 2 ] ) ) + self.assertEqual( c.get( "testX", 10 ), 10 ) def testHash( self ) : @@ -510,32 +462,27 @@ def testHash( self ) : c["test2"] = "test2" # no change self.assertEqual( c.hash(), hashes[-1] ) - def testChanged( self ) : + def testHashIgnoresUIEntries( self ) : c = Gaffer.Context() - c["test"] = IECore.StringVectorData( [ "one" ] ) h = c.hash() - cs = GafferTest.CapturingSlot( c.changedSignal() ) - - d = c.get( "test", _copy = False ) # dangerous! the context won't know if we make changes - d.append( "two" ) - self.assertEqual( c.get( "test" ), IECore.StringVectorData( [ "one", "two" ] ) ) - self.assertEqual( len( cs ), 0 ) - - c.changed( "test" ) # let the context know what we've been up to - self.assertEqual( len( cs ), 1 ) - self.assertEqual( cs[0], ( c, "test" ) ) - self.assertNotEqual( c.hash(), h ) - - def testHashIgnoresUIEntries( self ) : + c["ui:test"] = 1 + self.assertEqual( h, c.hash() ) - c = Gaffer.Context() - h = c.hash() + del c["ui:test"] + self.assertEqual( c.names(), Gaffer.Context().names() ) + self.assertEqual( h, c.hash() ) c["ui:test"] = 1 + c["ui:test2"] = "foo" + self.assertEqual( h, c.hash() ) + + c.removeMatching( "ui:test*" ) + self.assertEqual( c.names(), Gaffer.Context().names() ) self.assertEqual( h, c.hash() ) + @GafferTest.TestRunner.PerformanceTestMethod() def testManySubstitutions( self ) : @@ -684,5 +631,161 @@ def testOmitCanceller( self ) : context3 = Gaffer.Context( context1, omitCanceller = False ) self.assertIsNotNone( context3.canceller() ) + def testVariableHash( self ) : + + context = Gaffer.Context() + context["foo"] = 1 + context["bar"] = 1 + + self.assertEqual( context.variableHash( "doesntExist" ), IECore.MurmurHash() ) + + # Hash includes name, so the hash of different variables is different + self.assertNotEqual( context.variableHash( "foo" ), context.variableHash( "bar" ) ) + + initialHash = context.variableHash( "foo" ) + context["foo"] = 2 + self.assertNotEqual( context.variableHash( "foo" ), initialHash ) + + # Hashing should include the type, so different types of 32bit zeros should compare different + context["foo"] = 0 + zeroHash1 = context.variableHash( "foo" ) + context["foo"] = float(0) + zeroHash2 = context.variableHash( "foo" ) + self.assertNotEqual( zeroHash1, zeroHash2 ) + + # Check the intial value again + context["foo"] = 1 + self.assertEqual( context.variableHash( "foo" ), initialHash ) + + # With a single entry, the hash for the one variable is the same as the total hash of the context + context.remove("bar") + context.remove("framesPerSecond") + context.remove("frame") + self.assertEqual( context.variableHash( "foo" ), context.hash() ) + + + @staticmethod + def collisionCountParallelHelper( seed, mode, countList ): + r = GafferTest.countContextHash32Collisions( 2**20, mode, seed ) + for i in range(4): + countList[i] = r[i] + + # TODO - add new test flag `gaffer test -type all|standard|performance|verySlow`, where "verySlow" + # would enable tests like this + @unittest.skipIf( True, "Too expensive to run currently" ) + def testContextHashCollisions( self ) : + + iters = 10 + # Test all 4 context creation modes ( see ContextTest.cpp for descriptions ) + for mode in [0,1,2,3]: + # This would be much cleaner once we can use concurrent.futures + import threading + + results = [ [0,0,0,0] for i in range( iters ) ] + threads = [] + for i in range( iters ): + x = threading.Thread( target=self.collisionCountParallelHelper, args=( i, mode, results[i] ) ) + x.start() + threads.append( x ) + + for x in threads: + x.join() + + s = [0,0,0,0] + for l in results: + for i in range(4): + s[i] += l[i] + + # countContextHash32Collisions return the number of collisions in each of four 32 bits chunks + # of the hash independently - as long as as the uniformity of each chunk is good, the chance + # of a full collision of all four chunks is extremely small. + # + # We test it with 2**20 entries. We can approximate the number of expected 32 bit + # collisions as a binomial distribution - the probability changes as entries are inserted, but + # the average probability of a collision per item is when half the entries have been processed, + # for a probability of `0.5 * 2**20 / 2**32 = 0.00012207`. Using this probability, and N=2**20, + # in a binomial distribution, yields a mean outcome of 128.0 collisions. + # From: https://en.wikipedia.org/wiki/Birthday_problem, section "Collision Counting", the true + # result is `2**20 - 2**32 + 2**32 * ( (2**32 - 1)/2**32 )**(2**20) = 127.989` .. close enough + # to suggest our approximation works. Based on our approximated binomial distribution then, + # taking the number of iterations up to 10 * 2**20 for our sum over 10 trials, the sum should + # have a mean of 1280, and be in the range [1170 .. 1390] 99.8% of the time. + # + # This means there is some chance of this test failing ( especially because this math is + # approximate ), but it is too slow to run on CI anyway, and this test should provide some + # assurance that our MurmurHash is performing extremely similarly to a theoretical ideal + # uniformly distributed hash, even after a trick like summing the entry hashes. + # + # Because the bits of the hash are all evenly mixed, the rate of collisions should be identical + # regardless of which 32bit chunk we test. + # + # One weird note for myself in the future: There is one weird pattern you will see if you + # print s : If using 64bit sums of individual hashes, then the low 32 bit chunks + # ( chunks 0 and 3 ) will always produce the same number of collisions in mode 0 and 1. + # This is because most of the entries in mode 1 do not change, and the only source of variation + # is the one entry which matches mode 0 ... but this is expected, and crucially, while the + # number of collisions within mode 0 and mode 1 come out identical, this does not increase the + # chances of a collision between contexts with one varying entry, and context with one varying + # entry plus fixed entries ( mode 3 demonstrates this ). + + for i in range(4): + self.assertLess( s[i], 1390.0 ) + self.assertGreater( s[i], 1170.0 ) + + def testSetFrameSignalling( self ) : + + c = Gaffer.Context() + cs = GafferTest.CapturingSlot( c.changedSignal() ) + + c.setFrame( 10 ) + self.assertEqual( cs, [ ( c, "frame" ) ] ) + self.assertEqual( c.getFrame(), 10 ) + + c.setFrame( 20 ) + self.assertEqual( cs, [ ( c, "frame" ) ] * 2 ) + self.assertEqual( c.getFrame(), 20 ) + + c.setFrame( 20 ) + self.assertEqual( cs, [ ( c, "frame" ) ] * 2 ) + self.assertEqual( c.getFrame(), 20 ) + + c.setFrame( 30 ) + self.assertEqual( cs, [ ( c, "frame" ) ] * 3 ) + self.assertEqual( c.getFrame(), 30 ) + + @unittest.skipIf( not Gaffer.isDebug(), "Only valid for debug builds" ) + def testHashValidation( self ) : + + GafferTest.testContextHashValidation() + + @GafferTest.TestRunner.PerformanceTestMethod() + def testContextHashPerformance( self ) : + + GafferTest.testContextHashPerformance( 10, 10, False ) + + @GafferTest.TestRunner.PerformanceTestMethod() + def testContextHashPerformanceStartInitialized( self ) : + + GafferTest.testContextHashPerformance( 10, 10, True ) + + @GafferTest.TestRunner.PerformanceTestMethod() + def testContextCopyPerformance( self ) : + + GafferTest.testContextCopyPerformance( 10, 10 ) + + def testCopyEditableScope( self ) : + + GafferTest.testCopyEditableScope() + + def testSubstituteInternedString( self ) : + + c = Gaffer.Context() + c["test"] = IECore.InternedStringData( "value" ) + self.assertEqual( c.substitute( "${test}" ), "value" ) + + c["test1"] = IECore.InternedStringData( "${test2}" ) + c["test2"] = IECore.InternedStringData( "recursion!" ) + self.assertEqual( c.substitute( "${test1}" ), "recursion!" ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/ContextVariablesTest.py b/python/GafferTest/ContextVariablesTest.py index 98fb9ae060c..fbeca844aea 100644 --- a/python/GafferTest/ContextVariablesTest.py +++ b/python/GafferTest/ContextVariablesTest.py @@ -200,5 +200,21 @@ def testSerialisationUsesSetup( self ) : self.assertIsInstance( s2["c"]["in"], Gaffer.IntPlug ) self.assertIsInstance( s2["c"]["out"], Gaffer.IntPlug ) + @GafferTest.TestRunner.PerformanceTestMethod() + def testPerformance( self ): + c = Gaffer.ContextVariables() + c.setup( Gaffer.IntPlug() ) + for i in range( 10 ): + c["variables"].addChild( Gaffer.NameValuePlug( "a%i"%i, IECore.StringData( "A" * 100 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) + c["variables"].addChild( Gaffer.NameValuePlug( "intName", IECore.IntData( 100 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) ) + + # This would be a bit more representative if our source node was actually affected by the context variables, + # but without access to OSL in this test we don't have any efficient way to read context variables handy, + # and we're mostly just interested in the amount of overhead anyway + n = GafferTest.MultiplyNode() + c["in"].setInput( n["product"] ) + + GafferTest.parallelGetValue( c["out"], 1000000, "iter" ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/ExpressionTest.py b/python/GafferTest/ExpressionTest.py index 99cdabcd639..fdbc4e69935 100644 --- a/python/GafferTest/ExpressionTest.py +++ b/python/GafferTest/ExpressionTest.py @@ -1454,5 +1454,47 @@ def testIndependentOfOrderOfPlugNames( self ) : self.assertAlmostEqual( s["dest%i"%i]["p"].getValue().y, 0.2 + 0.3 * i + 10 * i, places = 5 ) self.assertAlmostEqual( s["dest%i"%i]["p"].getValue().z, 0.3 + 0.3 * i + 10 * i, places = 5 ) + def testNoneOutput( self ) : + + s = Gaffer.ScriptNode() + s["n"] = Gaffer.Node() + s["n"]["user"]["p"] = Gaffer.IntPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + + s["e"] = Gaffer.Expression() + s["e"].setExpression( "parent['n']['user']['p'] = None" ) + six.assertRaisesRegex( self, + Gaffer.ProcessException, + ".*TypeError: Unsupported type for result \"None\" for expression output \"n.user.p\"", + s["n"]["user"]["p"].getValue + ) + + s["e"].setExpression( "import math; parent['n']['user']['p'] = math" ) + six.assertRaisesRegex( self, + Gaffer.ProcessException, + ".*TypeError: Unsupported type for result \"Node Name" ) + # NameLabel with a fixed formatter, to be used as a drag source. + self.__nameLabel = GafferUI.NameLabel( None, formatter = lambda graphComponents : "

Node Name

" ) + # NameWidget to allow editing of the name. self.__nameWidget = GafferUI.NameWidget( None ) with GafferUI.ListContainer( @@ -141,6 +143,7 @@ def _updateFromSet( self ) : node = self._lastAddedNode() if node is None : + self.__nameLabel.setGraphComponent( None ) self.__nameWidget.setGraphComponent( None ) self.__nodeUI = None # Spacer is necessary to allow bookmark shortcuts to work in an @@ -149,6 +152,7 @@ def _updateFromSet( self ) : self.__header.setVisible( False ) return + self.__nameLabel.setGraphComponent( node ) self.__nameWidget.setGraphComponent( node ) self.__typeLabel.setText( "

" + node.typeName().rpartition( ":" )[-1] + "

" ) diff --git a/python/GafferUI/NodeSetEditor.py b/python/GafferUI/NodeSetEditor.py index 63fec37660d..0861b952b22 100644 --- a/python/GafferUI/NodeSetEditor.py +++ b/python/GafferUI/NodeSetEditor.py @@ -116,6 +116,9 @@ def nodeSetChangedSignal( self ) : # If drivingEditor is None, any existing links will be broken. def setNodeSetDriver( self, drivingEditor, mode = DriverModeNodeSet ) : + if mode not in self.__nodeSetDriverModes : + raise ValueError( "Unknown driver mode '%s'" % mode ) + if drivingEditor is not None : assert( isinstance( drivingEditor, GafferUI.NodeSetEditor ) ) # We also need to stop people creating infinite loops diff --git a/python/GafferUI/NumericSlider.py b/python/GafferUI/NumericSlider.py deleted file mode 100644 index a077895aa78..00000000000 --- a/python/GafferUI/NumericSlider.py +++ /dev/null @@ -1,221 +0,0 @@ -########################################################################## -# -# Copyright (c) 2012, John Haddon. All rights reserved. -# Copyright (c) 2013, 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 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 math - -import IECore - -import Gaffer -import GafferUI - -from Qt import QtGui - -## The NumericSlider extends the slider class to provide a mapping between the positions -# and a range defined by a minimum and maximum value. -class NumericSlider( GafferUI.Slider ) : - - ## The type of value (int or float) determines the type of the slider. - # The min and max arguments define the numeric values at the ends of the slider. - # By default, values outside this range will be clamped, but hardMin and hardMax - # may be specified to move the point at which the clamping happens outside of the - # slider itself. - def __init__( self, value=None, min=0, max=1, hardMin=None, hardMax=None, values=None, **kw ) : - - GafferUI.Slider.__init__( self, **kw ) - - assert( ( value is None ) or ( values is None) ) - - self.__min = min - self.__max = max - self.__hardMin = hardMin if hardMin is not None else self.__min - self.__hardMax = hardMax if hardMax is not None else self.__max - - # It would be nice to not store the values, but always infer them from - # the positions. This isn't possible though, as the range may be 0 length - # and then we would lose the values. - self.__values = [] - if values is not None : - self.__setValuesInternal( values, self.PositionChangedReason.SetPositions ) - else : - self.__setValuesInternal( [ 0 if value is None else value ], self.PositionChangedReason.SetPositions ) - - ## Convenience function to call setValues( [ value ] ) - def setValue( self, value ) : - - self.setValues( [ value ] ) - - ## Convenience function returning getValues()[0] if there - # is only one value, and raising ValueError if not. - def getValue( self ) : - - if len( self.__values ) != 1 : - raise ValueError - - return self.__values[0] - - def setValues( self, values ) : - - self.__setValuesInternal( values, self.PositionChangedReason.SetPositions ) - - def getValues( self ) : - - return self.__values - - def setRange( self, min, max, hardMin=None, hardMax=None ) : - - if hardMin is None : - hardMin = min - if hardMax is None : - hardMax = max - - if min==self.__min and max==self.__max and hardMin==self.__hardMin and hardMax==self.__hardMax : - return - - self.__min = min - self.__max = max - self.__hardMin = hardMin - self.__hardMax = hardMax - - self.__setValuesInternal( self.__values, self.PositionChangedReason.Invalid ) # reclamps the values to the range if necessary - # __setValuesInternal() won't trigger an update if the position is the same - if - # the position is at one end of the range, and that end is the same as before. - self._qtWidget().update() - - def getRange( self ) : - - return self.__min, self.__max, self.__hardMin, self.__hardMax - - def valueChangedSignal( self ) : - - try : - return self.__valueChangedSignal - except : - self.__valueChangedSignal = Gaffer.Signal2() - - return self.__valueChangedSignal - - def _setPositionsInternal( self, positions, reason ) : - - # change it into a __setValuesInternal call so we can apply clamping etc. - # __setValuesInternal will call Slider._setPositionsInternal for us. - self.__setValuesInternal( - [ self.__min + x * ( self.__max - self.__min ) for x in positions ], - reason - ) - - def __setValuesInternal( self, values, reason ) : - - # clamp values - values = [ max( self.__hardMin, min( self.__hardMax, x ) ) for x in values ] - - # convert them to positions - range = self.__max - self.__min - if range == 0 : - positions = [ 0 ] * len( values ) - else : - positions = [ float( x - self.__min ) / range for x in values ] - - # store values. we do this before storing positions so they'll - # be in sync when the position change is signalled. - dragBeginOrEnd = reason in ( self.PositionChangedReason.DragBegin, self.PositionChangedReason.DragEnd ) - valueChangedSignal = None - if values != self.__values or dragBeginOrEnd : - self.__values = values - with IECore.IgnoredExceptions( AttributeError ) : - valueChangedSignal = self.__valueChangedSignal - - # store positions. - GafferUI.Slider._setPositionsInternal( self, positions, reason ) - - # signal value change if necessary. we do this after storing - # positions so that the positions are in sync. - if valueChangedSignal is not None : - valueChangedSignal( self, reason ) - - def _drawBackground( self, painter ) : - - size = self.size() - valueRange = self.__max - self.__min - if valueRange == 0 : - return - - idealSpacing = 10 - idealNumTicks = float( size.x ) / idealSpacing - tickStep = valueRange / idealNumTicks - - logTickStep = math.log10( tickStep ) - flooredLogTickStep = math.floor( logTickStep ) - tickStep = math.pow( 10, flooredLogTickStep ) - blend = (logTickStep - flooredLogTickStep) - - tickValue = math.floor( self.__min / tickStep ) * tickStep - i = 0 - while tickValue <= self.__max : - x = size.x * ( tickValue - self.__min ) / valueRange - if i % 100 == 0 : - height0 = height1 = 0.75 - alpha0 = alpha1 = 1 - elif i % 50 == 0 : - height0 = 0.75 - height1 = 0.5 - alpha0 = alpha1 = 1 - elif i % 10 == 0 : - height0 = 0.75 - height1 = 0.25 - alpha0 = alpha1 = 1 - elif i % 5 == 0 : - height0 = 0.5 - height1 = 0 - alpha0 = 1 - alpha1 = 0 - else : - height0 = 0.25 - height1 = 0 - alpha0 = 1 - alpha1 = 0 - - alpha = alpha0 + (alpha1 - alpha0) * blend - height = height0 + (height1 - height0) * blend - - pen = QtGui.QPen() - pen.setWidth( 0 ) - pen.setColor( QtGui.QColor( 0, 0, 0, alpha * 255 ) ) - painter.setPen( pen ) - - painter.drawLine( x, size.y, x, size.y * ( 1 - height ) ) - tickValue += tickStep - i += 1 diff --git a/python/GafferUI/PathWidget.py b/python/GafferUI/PathWidget.py index 2b8dee03c01..49738a6c089 100644 --- a/python/GafferUI/PathWidget.py +++ b/python/GafferUI/PathWidget.py @@ -119,8 +119,8 @@ def __keyPress( self, widget, event ) : elif event.key == "Down" : if event.modifiers & GafferUI.ModifiableEvent.Modifiers.Shift : - # select all! - self.setSelection( None, None ) + # select all! + self.setSelection( None, None ) else : text = self.getText() position = self.getCursorPosition() diff --git a/python/GafferUI/PlugLayout.py b/python/GafferUI/PlugLayout.py index e70aa9cc34c..fb90ccd1e44 100644 --- a/python/GafferUI/PlugLayout.py +++ b/python/GafferUI/PlugLayout.py @@ -546,7 +546,7 @@ def __plugMetadataChanged( self, plug, key, reason ) : def __plugDirtied( self, plug ) : - if not self.visible() or plug.direction() != plug.Direction.In : + if plug.direction() != plug.Direction.In : return self.__activationsDirty = True diff --git a/python/GafferUI/PlugValueWidget.py b/python/GafferUI/PlugValueWidget.py index 64c66377200..76a200d8faa 100644 --- a/python/GafferUI/PlugValueWidget.py +++ b/python/GafferUI/PlugValueWidget.py @@ -464,7 +464,6 @@ def __plugInputChanged( self, plug ) : if plug in self.__plugs : self.__updateContextConnection() - self._updateFromPlugs() def __plugMetadataChanged( self, plug, key, reason ) : diff --git a/python/GafferUI/RampPlugValueWidget.py b/python/GafferUI/RampPlugValueWidget.py index d79703e9344..50cf8a5751f 100644 --- a/python/GafferUI/RampPlugValueWidget.py +++ b/python/GafferUI/RampPlugValueWidget.py @@ -71,7 +71,7 @@ def __init__( self, plug, **kw ) : self.__slider = GafferUI.Slider() self.__slider.setMinimumSize( 2 ) - self.__positionsChangedConnection = self.__slider.positionChangedSignal().connect( Gaffer.WeakMethod( self.__positionsChanged ), scoped = False ) + self.__positionsChangedConnection = self.__slider.valueChangedSignal().connect( Gaffer.WeakMethod( self.__positionsChanged ), scoped = False ) self.__slider.indexRemovedSignal().connect( Gaffer.WeakMethod( self.__indexRemoved ), scoped = False ) self.__slider.selectedIndexChangedSignal().connect( Gaffer.WeakMethod( self.__selectedIndexChanged ), scoped = False ) @@ -131,7 +131,7 @@ def _updateFromPlug( self ) : positions.append( plug.pointXPlug( i ).getValue() ) with Gaffer.BlockedConnection( self.__positionsChangedConnection ) : - self.__slider.setPositions( positions ) + self.__slider.setValues( positions ) def __positionsChanged( self, slider, reason ) : @@ -145,10 +145,10 @@ def __positionsChanged( self, slider, reason ) : mergeGroup = "RampPlugValudWidget%d%d" % ( id( self, ), self.__positionsMergeGroupId ) ) : - if len( slider.getPositions() ) == plug.numPoints() : + if len( slider.getValues() ) == plug.numPoints() : rejected = False # the user has moved an existing point on the slider - for index, position in enumerate( slider.getPositions() ) : + for index, position in enumerate( slider.getValues() ) : if plug.pointXPlug( index ).getValue() != position : curPlug = plug.pointXPlug( index ) if curPlug.settable() and not Gaffer.MetadataAlgo.readOnly( curPlug ): @@ -162,9 +162,9 @@ def __positionsChanged( self, slider, reason ) : # a new position was added on the end by the user clicking # on an empty area of the slider. numPoints = plug.numPoints() - assert( len( slider.getPositions() ) == numPoints + 1 ) + assert( len( slider.getValues() ) == numPoints + 1 ) spline = plug.getValue().spline() - position = slider.getPositions()[numPoints] + position = slider.getValues()[numPoints] plug.addPoint() plug.pointXPlug( numPoints ).setValue( position ) plug.pointYPlug( numPoints ).setValue( spline( position ) ) @@ -197,4 +197,4 @@ def __selectedIndexChanged( self, slider ) : # in the node editor. this means the SplinePlugValueWidget will be used instead, and # that will launch a dialogue containing a RampPlugValueWidget when appropriate. for # nodes which want a large editor directly in the node editor, the RampPlugValueWidget -# can be registered directly for specific plugs. \ No newline at end of file +# can be registered directly for specific plugs. diff --git a/python/GafferUI/ScriptWindow.py b/python/GafferUI/ScriptWindow.py index bbcb3da5f1e..4310394c8ac 100644 --- a/python/GafferUI/ScriptWindow.py +++ b/python/GafferUI/ScriptWindow.py @@ -235,4 +235,3 @@ def __metadataChanged( self, nodeTypeId, key, node ) : if Gaffer.MetadataAlgo.readOnlyAffectedByChange( self.__script(), nodeTypeId, key, node ) : self.__updateTitle() - diff --git a/python/GafferUI/Slider.py b/python/GafferUI/Slider.py index bcf0b2031e8..a0ee479389c 100644 --- a/python/GafferUI/Slider.py +++ b/python/GafferUI/Slider.py @@ -36,6 +36,7 @@ ########################################################################## import math +import six import IECore @@ -46,34 +47,41 @@ from Qt import QtGui from Qt import QtWidgets -## The Slider class allows a user to specify a number of positions on a scale of 0.0 at one end -# of the Widget and 1.0 at the other. Positions off the ends of the widget are mapped -# to negative numbers and numbers greater than 1.0 respectively. Derived classes may -# provide alternative interpretations for the scale and clamp values as appropriate. In -# particular see the NumericSlider which allows the specification of the values at either -# end of the scale along with hard minimum and maximum values. class Slider( GafferUI.Widget ) : - PositionChangedReason = IECore.Enum.create( "Invalid", "SetPositions", "Click", "IndexAdded", "IndexRemoved", "DragBegin", "DragMove", "DragEnd", "Increment" ) + ValueChangedReason = IECore.Enum.create( "Invalid", "SetValues", "Click", "IndexAdded", "IndexRemoved", "DragBegin", "DragMove", "DragEnd", "Increment" ) - def __init__( self, position=None, positions=None, **kw ) : + # The min and max arguments define the numeric values at the ends of the slider. + # By default, values outside this range will be clamped, but hardMin and hardMax + # may be specified to move the point at which the clamping happens outside of the + # slider itself. + # + # A single slider may show more than one value. Multiple values may be specified + # by passing a list to the `values` argument, or calling `setValues()` after + # construction. + def __init__( self, values=0.5, min=0, max=1, hardMin=None, hardMax=None, **kw ) : - GafferUI.Widget.__init__( self, _Widget(), **kw ) + if "value" in kw : + # Backwards compatibility with old `value` argument + assert( values == 0.5 ) + values = kw["value"] + del kw["value"] - assert( ( position is None ) or ( positions is None ) ) + GafferUI.Widget.__init__( self, _Widget(), **kw ) - if positions is not None : - self.__positions = positions - else : - self.__positions = [ 0.5 if position is None else position ] + self.__min = min + self.__max = max + self.__hardMin = hardMin if hardMin is not None else self.__min + self.__hardMax = hardMax if hardMax is not None else self.__max self.__selectedIndex = None self.__sizeEditable = False self.__minimumSize = 1 - self.__positionIncrement = None - self._entered = False + self.__increment = None + self.__snapIncrement = None + self.__hoverPositionVisible = False + self.__hoverEvent = None # The mouseMove event that gives us hover status - self.enterSignal().connect( Gaffer.WeakMethod( self.__enter ), scoped = False ) self.leaveSignal().connect( Gaffer.WeakMethod( self.__leave ), scoped = False ) self.mouseMoveSignal().connect( Gaffer.WeakMethod( self.__mouseMove ), scoped = False ) self.buttonPressSignal().connect( Gaffer.WeakMethod( self.__buttonPress ), scoped = False ) @@ -83,38 +91,44 @@ def __init__( self, position=None, positions=None, **kw ) : self.dragEndSignal().connect( Gaffer.WeakMethod( self.__dragEnd ), scoped = False ) self.keyPressSignal().connect( Gaffer.WeakMethod( self.__keyPress ), scoped = False ) - ## Convenience function to call setPositions( [ position ] ) - def setPosition( self, p ) : + self.__values = [] + if isinstance( values, ( six.integer_types, float ) ) : + self.__setValuesInternal( [ values ], self.ValueChangedReason.SetValues ) + else : + self.__setValuesInternal( values, self.ValueChangedReason.SetValues ) + + ## Convenience function to call setValues( [ value ] ) + def setValue( self, value ) : - self.setPositions( [ p ] ) + self.setValues( [ value ] ) - ## Convenience function returning getPositions()[0] if there - # is only one position, and raising ValueError if not. - def getPosition( self ) : + ## Convenience function returning getValues()[0] if there + # is only one value, and raising ValueError if not. + def getValue( self ) : - if len( self.__positions ) != 1 : + if len( self.__values ) != 1 : raise ValueError - return self.__positions[0] + return self.__values[0] - def setPositions( self, positions ) : + def setValues( self, values ) : - self._setPositionsInternal( positions, self.PositionChangedReason.SetPositions ) + self.__setValuesInternal( values, self.ValueChangedReason.SetValues ) - def getPositions( self ) : + def getValues( self ) : - return self.__positions + return self.__values - ## A signal emitted whenever a position has been changed. Slots should - # have the signature slot( Slider, PositionChangedReason ). - def positionChangedSignal( self ) : + ## A signal emitted whenever a value has been changed. Slots should + # have the signature slot( Slider, ValueChangedReason ). + def valueChangedSignal( self ) : - signal = getattr( self, "_positionChangedSignal", None ) - if signal is None : - signal = Gaffer.Signal2() - self._positionChangedSignal = signal + try : + return self.__valueChangedSignal + except : + self.__valueChangedSignal = Gaffer.Signal2() - return signal + return self.__valueChangedSignal ## Returns True if a user would expect the specified sequence # of changes to be merged into one undoable event. @@ -126,14 +140,36 @@ def changesShouldBeMerged( cls, firstReason, secondReason ) : return ( firstReason, secondReason ) in ( # click and drag - ( cls.PositionChangedReason.Click, cls.PositionChangedReason.DragBegin ), - ( cls.PositionChangedReason.DragBegin, cls.PositionChangedReason.DragMove ), - ( cls.PositionChangedReason.DragMove, cls.PositionChangedReason.DragMove ), - ( cls.PositionChangedReason.DragMove, cls.PositionChangedReason.DragEnd ), + ( cls.ValueChangedReason.Click, cls.ValueChangedReason.DragBegin ), + ( cls.ValueChangedReason.DragBegin, cls.ValueChangedReason.DragMove ), + ( cls.ValueChangedReason.DragMove, cls.ValueChangedReason.DragMove ), + ( cls.ValueChangedReason.DragMove, cls.ValueChangedReason.DragEnd ), # increment - ( cls.PositionChangedReason.Increment, cls.PositionChangedReason.Increment ), + ( cls.ValueChangedReason.Increment, cls.ValueChangedReason.Increment ), ) + def setRange( self, min, max, hardMin=None, hardMax=None ) : + + if hardMin is None : + hardMin = min + if hardMax is None : + hardMax = max + + if min==self.__min and max==self.__max and hardMin==self.__hardMin and hardMax==self.__hardMax : + return + + self.__min = min + self.__max = max + self.__hardMin = hardMin + self.__hardMax = hardMax + + self.__setValuesInternal( self.__values, self.ValueChangedReason.Invalid ) # reclamps the values to the range if necessary + self._qtWidget().update() + + def getRange( self ) : + + return self.__min, self.__max, self.__hardMin, self.__hardMax + def indexRemovedSignal( self ) : signal = getattr( self, "_indexRemovedSignal", None ) @@ -149,7 +185,7 @@ def setSelectedIndex( self, index ) : return if index is not None : - if not len( self.__positions ) or index < 0 or index >= len( self.__positions ) : + if not len( self.__values ) or index < 0 or index >= len( self.__values ) : raise IndexError self.__selectedIndex = index @@ -173,7 +209,7 @@ def selectedIndexChangedSignal( self ) : return signal - ## Determines whether or not positions may be added/removed + ## Determines whether or not values may be added/removed def setSizeEditable( self, editable ) : self.__sizeEditable = editable @@ -182,7 +218,7 @@ def getSizeEditable( self ) : return self.__sizeEditable - ## Sets a size after which no more positions can + ## Sets a size after which no more values can # be removed. def setMinimumSize( self, minimumSize ) : @@ -192,65 +228,119 @@ def getMinimumSize( self ) : return self.__minimumSize - ## Sets the size of the position increment added/subtracted + ## Sets the value increment added/subtracted # when using the cursor keys. The default value of None # uses an increment equivalent to the size of one pixel at # the current slider size. An increment of 0 can be specified # to disable the behaviour entirely. - ## \todo Add setValueIncrement() method on NumericSlider. - def setPositionIncrement( self, increment ) : + def setIncrement( self, increment ) : - self.__positionIncrement = increment + self.__increment = increment - def getPositionIncrement( self ) : + def getIncrement( self ) : - return self.__positionIncrement + return self.__increment - ## May be overridden by derived classes if necessary, but - # implementations must call the base class implementation - # after performing their own work, as the base class is - # responsible for emitting positionChangedSignal(). - def _setPositionsInternal( self, positions, reason ) : + ## Sets the increment used for snapping values generated + # by interactions such as drags and button presses. Snapping + # can be ignored by by holding the `Ctrl` modifier. + def setSnapIncrement( self, increment ) : - dragBeginOrEnd = reason in ( self.PositionChangedReason.DragBegin, self.PositionChangedReason.DragEnd ) - if positions == self.__positions and not dragBeginOrEnd : - # early out if the positions haven't changed, but not if the - # reason is either end of a drag - we always signal those so - # that they will always come in matching pairs. - return + self.__snapIncrement = increment - self.__positions = positions - self._qtWidget().update() + def getSnapIncrement( self ) : - self.__emitPositionChanged( reason ) + return self.__snapIncrement - ## \todo Colours should come from some unified style somewhere - def _drawBackground( self, painter ) : + def setHoverPositionVisible( self, visible ) : - size = self.size() + self.__hoverPositionVisible = visible - pen = QtGui.QPen( QtGui.QColor( 0, 0, 0 ) ) - pen.setWidth( 1 ) - painter.setPen( pen ) + def getHoverPositionVisible( self ) : - painter.drawLine( 0, size.y / 2, size.x, size.y / 2 ) + return self.__hoverPositionVisible - def _drawPosition( self, painter, position, highlighted, opacity=1 ) : + ## May be overridden by derived classes to customise + # the drawing of the background. + def _drawBackground( self, painter ) : size = self.size() + valueRange = self.__max - self.__min + if valueRange == 0 : + return - pen = QtGui.QPen( QtGui.QColor( 0, 0, 0, 255 * opacity ) ) + idealSpacing = 10 + idealNumTicks = float( size.x ) / idealSpacing + tickStep = valueRange / idealNumTicks + + logTickStep = math.log10( tickStep ) + flooredLogTickStep = math.floor( logTickStep ) + tickStep = math.pow( 10, flooredLogTickStep ) + blend = (logTickStep - flooredLogTickStep) + + tickValue = math.floor( self.__min / tickStep ) * tickStep + i = 0 + while tickValue <= self.__max : + x = size.x * ( tickValue - self.__min ) / valueRange + if i % 100 == 0 : + height0 = height1 = 0.75 + alpha0 = alpha1 = 1 + elif i % 50 == 0 : + height0 = 0.75 + height1 = 0.5 + alpha0 = alpha1 = 1 + elif i % 10 == 0 : + height0 = 0.75 + height1 = 0.25 + alpha0 = alpha1 = 1 + elif i % 5 == 0 : + height0 = 0.5 + height1 = 0 + alpha0 = 1 + alpha1 = 0 + else : + height0 = 0.25 + height1 = 0 + alpha0 = 1 + alpha1 = 0 + + alpha = alpha0 + (alpha1 - alpha0) * blend + height = height0 + (height1 - height0) * blend + + pen = QtGui.QPen() + pen.setWidth( 0 ) + pen.setColor( QtGui.QColor( 0, 0, 0, alpha * 255 ) ) + painter.setPen( pen ) + + painter.drawLine( x, size.y, x, size.y * ( 1 - height ) ) + tickValue += tickStep + i += 1 + + ## May be overridden by derived classes to customise the + # drawing of the value indicator. + # + # `value` : The value itself. + # `position` : The widget-relative position where the + # indicator should be drawn. + # `state` : A GafferUI.Style.State. DisabledState is used + # to draw hover indicators, since there is + # currently no dedicated state for this purpose. + def _drawValue( self, painter, value, position, state ) : + + size = self.size() + + pen = QtGui.QPen( QtGui.QColor( 0, 0, 0, 255 ) ) pen.setWidth( 1 ) painter.setPen( pen ) - ## \todo These colours need to come from the style, once we've - # unified the Gadget and Widget styling. - if highlighted : - brush = QtGui.QBrush( QtGui.QColor( 119, 156, 255, 255 * opacity ) ) + if state == state.NormalState : + color = QtGui.QColor( 128, 128, 128, 255 ) else : - brush = QtGui.QBrush( QtGui.QColor( 128, 128, 128, 255 * opacity ) ) + color = QtGui.QColor( 119, 156, 255, 255 ) + painter.setBrush( QtGui.QBrush( color ) ) - painter.setBrush( brush ) + if state == state.DisabledState : + painter.setOpacity( 0.5 ) if position < 0 : painter.drawPolygon( @@ -262,7 +352,7 @@ def _drawPosition( self, painter, position, highlighted, opacity=1 ) : ] ) ) - elif position > 1 : + elif position > size.x : painter.drawPolygon( QtGui.QPolygonF( [ @@ -273,19 +363,19 @@ def _drawPosition( self, painter, position, highlighted, opacity=1 ) : ) ) else : - painter.drawEllipse( QtCore.QPoint( position * size.x, size.y / 2 ), size.y / 4, size.y / 4 ) + painter.drawEllipse( QtCore.QPoint( position, size.y / 2 ), size.y / 4, size.y / 4 ) - def _indexUnderMouse( self ) : + def __indexUnderMouse( self ) : size = self.size() - mousePosition = GafferUI.Widget.mousePosition( relativeTo = self ).x / float( size.x ) + mousePosition = GafferUI.Widget.mousePosition( relativeTo = self ).x result = None - for i, p in enumerate( self.__positions ) : - # clamp position inside 0-1 range so we can select + for i, v in enumerate( self.__values ) : + # clamp value inside range so we can select # handles representing points outside the widget. - p = max( min( p, 1.0 ), 0.0 ) - dist = math.fabs( mousePosition - p ) + v = max( min( v, self.__max ), self.__min ) + dist = math.fabs( mousePosition - self.__valueToPosition( v ) ) if result is None or dist < minDist : result = i minDist = dist @@ -299,42 +389,42 @@ def _indexUnderMouse( self ) : # but when the size is editable, we consider points to # be under the mouse when they genuinely are beneath it, # so that clicks elsewhere can add points. - pixelDist = minDist * size.x - if pixelDist < size.y / 2.0 : + if minDist < size.y / 2.0 : return result else : return None - def __enter( self, widget ) : - - self._entered = True - self._qtWidget().update() - def __leave( self, widget ) : - self._entered = False + self.__hoverEvent = None self._qtWidget().update() def __mouseMove( self, widget, event ) : - self._qtWidget().update() + if not event.buttons : + self.__hoverEvent = event + self._qtWidget().update() def __buttonPress( self, widget, event ) : if event.buttons != GafferUI.ButtonEvent.Buttons.Left : return - index = self._indexUnderMouse() + index = self.__indexUnderMouse() if index is not None : self.setSelectedIndex( index ) - if len( self.getPositions() ) == 1 : - self.__setPositionInternal( index, event.line.p0.x, self.PositionChangedReason.Click, clamp=True ) + if len( self.getValues() ) == 1 : + self.__setValueInternal( index, self.__eventValue( event ), self.ValueChangedReason.Click ) elif self.getSizeEditable() : - positions = self.getPositions()[:] - positions.append( float( event.line.p0.x ) / self.size().x ) - self._setPositionsInternal( positions, self.PositionChangedReason.IndexAdded ) - self.setSelectedIndex( len( self.getPositions() ) - 1 ) - + values = self.getValues()[:] + values.append( self.__eventValue( event ) ) + self.__setValuesInternal( values, self.ValueChangedReason.IndexAdded ) + self.setSelectedIndex( len( self.getValues() ) - 1 ) + + # Clear hover so we don't draw hover state on top + # of a just-clicked value or during drags. + self.__hoverEvent = None + self._qtWidget().update() return True def __dragBegin( self, widget, event ) : @@ -347,30 +437,21 @@ def __dragBegin( self, widget, event ) : def __dragEnter( self, widget, event ) : if event.sourceWidget is self : - self.__setPositionInternal( - self.getSelectedIndex(), event.line.p0.x, - self.PositionChangedReason.DragBegin, - clamp = not (event.modifiers & event.modifiers.Shift ), - ) return True return False def __dragMove( self, widget, event ) : - self.__setPositionInternal( - self.getSelectedIndex(), event.line.p0.x, - self.PositionChangedReason.DragMove, - clamp = not (event.modifiers & event.modifiers.Shift ), + self.__setValueInternal( + self.getSelectedIndex(), + self.__eventValue( event ), + self.ValueChangedReason.DragMove ) def __dragEnd( self, widget, event ) : - self.__setPositionInternal( - self.getSelectedIndex(), event.line.p0.x, - self.PositionChangedReason.DragEnd, - clamp = not (event.modifiers & event.modifiers.Shift ), - ) + self.__dragMove( widget, event ) def __keyPress( self, widget, event ) : @@ -379,54 +460,118 @@ def __keyPress( self, widget, event ) : if event.key in ( "Left", "Right", "Up", "Down" ) : - if self.__positionIncrement == 0 : + if self.__increment == 0 : return False - if self.__positionIncrement is None : - pixelIncrement = 1 + if self.__increment is None : + increment = ( self.__max - self.__min ) / float( self.size().x ) else : - pixelIncrement = self.__positionIncrement * self.size().x + increment = self.__increment + + x = self.getValues()[self.getSelectedIndex()] + x += increment if event.key in ( "Right", "Up" ) else -increment + if not (event.modifiers & event.modifiers.Shift ) : + x = max( self.__min, min( self.__max, x ) ) - x = self.getPositions()[self.getSelectedIndex()] * self.size().x - x += pixelIncrement if event.key in ( "Right", "Up" ) else -pixelIncrement - self.__setPositionInternal( + self.__setValueInternal( self.getSelectedIndex(), x, - self.PositionChangedReason.Increment, - clamp = not (event.modifiers & event.modifiers.Shift ), + self.ValueChangedReason.Increment, ) return True elif event.key in ( "Backspace", "Delete" ) : index = self.getSelectedIndex() - if index is not None and self.getSizeEditable() and len( self.getPositions() ) > self.getMinimumSize() : + if index is not None and self.getSizeEditable() and len( self.getValues() ) > self.getMinimumSize() : - del self.__positions[index] + del self.__values[index] signal = getattr( self, "_indexRemovedSignal", None ) if signal is not None : signal( self, index ) - self.__emitPositionChanged( self.PositionChangedReason.IndexRemoved ) + self.__emitValueChanged( self.ValueChangedReason.IndexRemoved ) self._qtWidget().update() return True return False - def __setPositionInternal( self, index, widgetX, reason, clamp ) : + def __setValueInternal( self, index, value, reason ) : - position = float( widgetX ) / self.size().x - if clamp : - position = min( 1.0, max( 0.0, position ) ) + values = self.getValues()[:] + values[index] = value + self.__setValuesInternal( values, reason ) - positions = self.getPositions()[:] - positions[index] = position - self._setPositionsInternal( positions, reason ) + def __setValuesInternal( self, values, reason ) : - def __emitPositionChanged( self, reason ) : + # We _always_ clamp to the hard min and max, as those are not optional. + # Optional clamping to soft min and max is performed before calling this + # function, typically in `__eventValue()`. + values = [ max( self.__hardMin, min( self.__hardMax, x ) ) for x in values ] - signal = getattr( self, "_positionChangedSignal", None ) - if signal is not None : - signal( self, reason ) + dragBeginOrEnd = reason in ( self.ValueChangedReason.DragBegin, self.ValueChangedReason.DragEnd ) + if values == self.__values and not dragBeginOrEnd : + # early out if the values haven't changed, but not if the + # reason is either end of a drag - we always signal those so + # that they will always come in matching pairs. + return + + self.__values = values + self._qtWidget().update() + + self.__emitValueChanged( reason ) + + def __emitValueChanged( self, reason ) : + + try : + signal = self.__valueChangedSignal + except : + return + + signal( self, reason ) + + def __eventValue( self, event ) : + + f = event.line.p0.x / float( self.size().x ) + value = self.__min + ( self.__max - self.__min ) * f + if not (event.modifiers & event.modifiers.Shift) : + # Clamp + value = max( self.__min, min( self.__max, value ) ) + if self.__snapIncrement and not (event.modifiers & GafferUI.ModifiableEvent.Modifiers.Control) : + # Snap + value = self.__snapIncrement * round( value / self.__snapIncrement ) + + return value + + def __valueToPosition( self, value ) : + + f = ( value - self.__min ) / ( self.__max - self.__min ) + return f * self.size().x + + def __draw( self, painter ) : + + self._drawBackground( painter ) + + indexUnderMouse = self.__indexUnderMouse() + for index, value in enumerate( self.getValues() ) : + self._drawValue( + painter, + value, + self.__valueToPosition( value ), + GafferUI.Style.State.HighlightedState if index == indexUnderMouse or index == self.getSelectedIndex() + else GafferUI.Style.State.NormalState + ) + + if self.__hoverEvent is not None : + if ( + self.getHoverPositionVisible() or + ( self.getSizeEditable() and indexUnderMouse is None ) + ) : + self._drawValue( + painter, + self.__eventValue( self.__hoverEvent ), + self.__valueToPosition( self.__eventValue( self.__hoverEvent ) ), + state = GafferUI.Style.State.DisabledState + ) class _Widget( QtWidgets.QWidget ) : @@ -448,24 +593,7 @@ def paintEvent( self, event ) : painter = QtGui.QPainter( self ) painter.setRenderHint( QtGui.QPainter.Antialiasing ) - owner._drawBackground( painter ) - - indexUnderMouse = owner._indexUnderMouse() - for index, position in enumerate( owner.getPositions() ) : - owner._drawPosition( - painter, - position, - highlighted = index == indexUnderMouse or index == owner.getSelectedIndex() - ) - - if indexUnderMouse is None and owner.getSizeEditable() and owner._entered : - mousePosition = GafferUI.Widget.mousePosition( relativeTo = owner ).x / float( owner.size().x ) - owner._drawPosition( - painter, - mousePosition, - highlighted = True, - opacity = 0.5 - ) + owner._Slider__draw( painter ) def event( self, event ) : @@ -474,7 +602,7 @@ def event( self, event ) : event.accept() return True if event.key() in ( QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_Left, QtCore.Qt.Key_Right ) : - if GafferUI.Widget._owner( self ).getPositionIncrement() != 0 : + if GafferUI.Widget._owner( self ).getIncrement() != 0 : event.accept() return True diff --git a/python/GafferUI/SpreadsheetUI/_Algo.py b/python/GafferUI/SpreadsheetUI/_Algo.py index 9a83b8ca3a8..43e0d825005 100644 --- a/python/GafferUI/SpreadsheetUI/_Algo.py +++ b/python/GafferUI/SpreadsheetUI/_Algo.py @@ -51,6 +51,8 @@ def createSpreadsheet( plug ) : spreadsheet = Gaffer.Spreadsheet() + spreadsheet["selector"].setValue( Gaffer.Metadata.value( plug,"ui:spreadsheet:selectorValue" ) or "" ) + addColumn( spreadsheet, plug ) spreadsheet["rows"].addRow() diff --git a/python/GafferUI/SpreadsheetUI/_Metadata.py b/python/GafferUI/SpreadsheetUI/_Metadata.py index fc64538fa4c..774f5459752 100644 --- a/python/GafferUI/SpreadsheetUI/_Metadata.py +++ b/python/GafferUI/SpreadsheetUI/_Metadata.py @@ -177,7 +177,8 @@ An output plug containing the names of all currently active rows. """, - "plugValueWidget:type", "", + "layout:section", "Advanced", + "plugValueWidget:type", "GafferUI.ConnectionPlugValueWidget" ], diff --git a/python/GafferUI/SpreadsheetUI/_PlugTableDelegate.py b/python/GafferUI/SpreadsheetUI/_PlugTableDelegate.py index a99e2a1e099..60d396f4ff6 100644 --- a/python/GafferUI/SpreadsheetUI/_PlugTableDelegate.py +++ b/python/GafferUI/SpreadsheetUI/_PlugTableDelegate.py @@ -98,4 +98,3 @@ def editorEvent( self, event, model, option, index ) : return False else : return super( _PlugTableDelegate, self ).editorEvent( event, model, option, index ) - diff --git a/python/GafferUI/SpreadsheetUI/_PlugTableModel.py b/python/GafferUI/SpreadsheetUI/_PlugTableModel.py index fd9150267cd..eae3e1e52ef 100644 --- a/python/GafferUI/SpreadsheetUI/_PlugTableModel.py +++ b/python/GafferUI/SpreadsheetUI/_PlugTableModel.py @@ -62,6 +62,7 @@ def __init__( self, rowsPlug, context, parent = None ) : self.__plugDirtiedConnection = rowsPlug.node().plugDirtiedSignal().connect( Gaffer.WeakMethod( self.__plugDirtied ) ) self.__rowAddedConnection = rowsPlug.childAddedSignal().connect( Gaffer.WeakMethod( self.__rowAdded ) ) self.__rowRemovedConnection = rowsPlug.childRemovedSignal().connect( Gaffer.WeakMethod( self.__rowRemoved ) ) + self.__rowsReorderedConnection = rowsPlug.childrenReorderedSignal().connect( Gaffer.WeakMethod( self.__rowsReordered ) ) self.__columnAddedConnection = rowsPlug.defaultRow()["cells"].childAddedSignal().connect( Gaffer.WeakMethod( self.__columnAdded ) ) self.__columnRemovedConnection = rowsPlug.defaultRow()["cells"].childRemovedSignal().connect( Gaffer.WeakMethod( self.__columnRemoved ) ) self.__plugMetadataChangedConnection = Gaffer.Metadata.plugValueChangedSignal( rowsPlug.node() ).connect( Gaffer.WeakMethod( self.__plugMetadataChanged ) ) @@ -175,7 +176,9 @@ def headerData( self, section, orientation, role ) : else : - return section + # We don't want to display the indices, but we do need + # a big enough section to use for drag and drop. + return " " def flags( self, index ) : @@ -307,6 +310,11 @@ def __rowRemoved( self, rowsPlug, row ) : ## \todo Is there any benefit in finer-grained signalling? self.__emitModelReset() + def __rowsReordered( self, rowsPlug, oldIndices ) : + + ## \todo Is there any benefit in finer-grained signalling? + self.__emitModelReset() + def __columnAdded( self, cellsPlug, cellPlug ) : ## \todo Is there any benefit in finer-grained signalling? diff --git a/python/GafferUI/SpreadsheetUI/_PlugTableView.py b/python/GafferUI/SpreadsheetUI/_PlugTableView.py index 67fde962e11..0fb18558d26 100644 --- a/python/GafferUI/SpreadsheetUI/_PlugTableView.py +++ b/python/GafferUI/SpreadsheetUI/_PlugTableView.py @@ -70,7 +70,7 @@ def __init__( self, selectionModel, mode, **kw ) : tableView = _NavigableTable() GafferUI.Widget.__init__( self, tableView, **kw ) - self.__mode = mode; + self.__mode = mode self.__setupModels( selectionModel ) # Headers and column sizing @@ -87,17 +87,23 @@ def __init__( self, selectionModel, mode, **kw ) : if mode in ( self.Mode.Cells, self.Mode.Defaults ) : self.__applyColumnWidthMetadata() - self.__applySectionOrderMetadata() + self.__applyColumnOrderMetadata() tableView.horizontalHeader().setSectionsMovable( True ) - tableView.horizontalHeader().sectionResized.connect( Gaffer.WeakMethod( self.__sectionResized ) ) - tableView.horizontalHeader().sectionMoved.connect( Gaffer.WeakMethod( self.__sectionMoved ) ) + tableView.horizontalHeader().sectionResized.connect( Gaffer.WeakMethod( self.__columnResized ) ) + tableView.horizontalHeader().sectionMoved.connect( Gaffer.WeakMethod( self.__columnMoved ) ) - self.__ignoreSectionResized = False - self.__callingMoveSection = False + self.__ignoreColumnResized = False + self.__ignoreColumnMoved = False else : # RowNames mode + if self.__canReorderRows() : + tableView.verticalHeader().setVisible( True ) + tableView.verticalHeader().setSectionsMovable( True ) + tableView.verticalHeader().sectionMoved.connect( Gaffer.WeakMethod( self.__rowMoved ) ) + self.__ignoreRowMoved = False + tableView.horizontalHeader().resizeSection( 1, 22 ) self.__applyRowNamesWidth() # Style the row enablers as toggles rather than checkboxes. @@ -224,9 +230,9 @@ def __setupModels( self, selectionModel ) : selectionProxy = _ProxySelectionModel( viewProxy, selectionModel, tableView ) tableView.setSelectionModel( selectionProxy ) - def __sectionMoved( self, logicalIndex, oldVisualIndex, newVisualIndex ) : + def __columnMoved( self, logicalIndex, oldVisualIndex, newVisualIndex ) : - if self.__callingMoveSection : + if self.__ignoreColumnMoved : return model = self._qtWidget().model() @@ -238,9 +244,9 @@ def __sectionMoved( self, logicalIndex, oldVisualIndex, newVisualIndex ) : plug = model.plugForIndex( model.index( 0, logicalIndex ) ) Gaffer.Metadata.registerValue( plug, "spreadsheet:columnIndex", header.visualIndex( logicalIndex ) ) - def __sectionResized( self, logicalIndex, oldSize, newSize ) : + def __columnResized( self, logicalIndex, oldSize, newSize ) : - if self.__ignoreSectionResized : + if self.__ignoreColumnResized : return model = self._qtWidget().model() @@ -264,7 +270,7 @@ def __applyColumnWidthMetadata( self, cellPlug = None ) : try : - self.__ignoreSectionResized = True + self.__ignoreColumnResized = True for index, plug in indicesAndPlugs : @@ -276,9 +282,9 @@ def __applyColumnWidthMetadata( self, cellPlug = None ) : finally : - self.__ignoreSectionResized = False + self.__ignoreColumnResized = False - def __applySectionOrderMetadata( self ) : + def __applyColumnOrderMetadata( self ) : if self.__mode == self.Mode.RowNames : return @@ -287,9 +293,9 @@ def __applySectionOrderMetadata( self ) : header = self._qtWidget().horizontalHeader() for index, plug in enumerate( rowsPlug.defaultRow()["cells"] ) : visualIndex = Gaffer.Metadata.value( plug, "spreadsheet:columnIndex" ) - self.__callingMoveSection = True + self.__ignoreColumnMoved = True header.moveSection( header.visualIndex( index ), visualIndex if visualIndex is not None else index ) - self.__callingMoveSection = False + self.__ignoreColumnMoved = False def __applyColumnVisibility( self ) : @@ -299,7 +305,7 @@ def __applyColumnVisibility( self ) : # Changing column visibility seems to cause the # `sectionResized()` signal to be emitted unnecessarily, # so we suppress the slot we've attached to it. - self.__ignoreSectionResized = True + self.__ignoreColumnResized = True try : rowsPlug = self._qtWidget().model().rowsPlug() for i, plug in enumerate( rowsPlug.defaultRow()["cells"].children() ) : @@ -312,7 +318,53 @@ def __applyColumnVisibility( self ) : else : self._qtWidget().hideColumn( i ) finally : - self.__ignoreSectionResized = False + self.__ignoreColumnResized = False + + def __canReorderRows( self ) : + + rowsPlug = self._qtWidget().model().rowsPlug() + if Gaffer.MetadataAlgo.readOnly( rowsPlug ) : + return False + + if isinstance( rowsPlug.node(), Gaffer.Reference ) : + reference = rowsPlug.node() + # Default row (`[0]`) is irrelevant because it is always + # referenced and we won't try to reorder it anyway. + for row in rowsPlug.children()[1:] : + if not reference.isChildEdit( row ) : + return False + + return True + + def __rowMoved( self, logicalIndex, oldVisualIndex, newVisualIndex ) : + + if self.__ignoreRowMoved : + return + + # Qt implements row moves as a visual transform on top of the model. + # We want to implement them as edits to the order of the underlying + # RowPlugs. So we translate the change in visual transform to a call to + # `reorderChildren()`, and then reset the visual transform. + + assert( oldVisualIndex == logicalIndex ) # Otherwise a previous visual transform reset failed + + # Reorder rows + + rowsPlug = self._qtWidget().model().rowsPlug() + rows = list( rowsPlug.children() ) + header = self._qtWidget().verticalHeader() + assert( len( rows ) == header.count() + 1 ) # Header doesn't know about the default row + rows = [ rows[0] ] + [ rows[header.logicalIndex(i)+1] for i in range( 0, header.count() ) ] + + with Gaffer.UndoScope( rowsPlug.ancestor( Gaffer.ScriptNode ) ) : + rowsPlug.reorderChildren( rows ) + + # Reset visual transform + + self.__ignoreRowMoved = True + for i in range( min( oldVisualIndex, newVisualIndex ), max( oldVisualIndex, newVisualIndex ) + 1 ) : + header.moveSection( header.visualIndex( i ), i ) + self.__ignoreRowMoved = False def __applyRowNamesWidth( self ) : @@ -323,9 +375,9 @@ def __applyRowNamesWidth( self ) : self._qtWidget().horizontalHeader().resizeSection( 0, width ) @GafferUI.LazyMethod() - def __applySectionOrderLazily( self ) : + def __applyColumnOrderLazily( self ) : - self.__applySectionOrderMetadata() + self.__applyColumnOrderMetadata() def __plugMetadataChanged( self, plug, key, reason ) : @@ -352,7 +404,7 @@ def __plugMetadataChanged( self, plug, key, reason ) : # Typically we get a flurry of edits to columnIndex at once, # so we use a lazy method to update the order once everything # has been done. - self.__applySectionOrderLazily() + self.__applyColumnOrderLazily() elif key == "spreadsheet:section" : diff --git a/python/GafferUI/TabbedContainer.py b/python/GafferUI/TabbedContainer.py index 3980e17188d..3b9ade25cf8 100644 --- a/python/GafferUI/TabbedContainer.py +++ b/python/GafferUI/TabbedContainer.py @@ -35,6 +35,8 @@ # ########################################################################## +import functools + import IECore import Gaffer @@ -53,6 +55,9 @@ def __init__( self, cornerWidget=None, **kw ) : GafferUI.ContainerWidget.__init__( self, _TabWidget(), **kw ) + # Tab bar + # ------- + self.__tabBar = GafferUI.Widget( QtWidgets.QTabBar() ) self.__tabBar._qtWidget().setDrawBase( False ) self.__tabBar._qtWidget().tabMoved.connect( Gaffer.WeakMethod( self.__moveWidget ) ) @@ -69,15 +74,50 @@ def __init__( self, cornerWidget=None, **kw ) : self.__tabBar._qtWidget().setPalette( TabbedContainer.__palette ) self._qtWidget().setTabBar( self.__tabBar._qtWidget() ) - - self._qtWidget().setUsesScrollButtons( False ) self._qtWidget().setElideMode( QtCore.Qt.ElideNone ) - self.__widgets = [] - - self.__cornerWidget = None + # Corner widget and scrolling + # --------------------------- + # + # QTabBar does provide scroll buttons for use when there is not enough + # horizontal space to show all tabs. But these are really awkward to use + # : you may not know which way to scroll, it may take several clicks to + # find the thing you want, and it's hard to track the jumpy movement of + # the tabs. Instead we provide a dropdown menu which provides an + # overview of all tabs and allows you to jump to the right one with a + # single click. This is stored in `self.__cornerContainer[0]`, alongside + # an optional user-provided corner widget which is stored in + # `self.__cornerContainer[1]`. + + self.__cornerContainer = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal ) + with self.__cornerContainer : + GafferUI.MenuButton( + image = "tabScrollMenu.png", hasFrame = False, + menu = GafferUI.Menu( Gaffer.WeakMethod( self.__scrollMenuDefinition ) ) + ) + self._qtWidget().setCornerWidget( self.__cornerContainer._qtWidget() ) self.setCornerWidget( cornerWidget ) + # When there's not enough horizontal space, we need QTabWidget to scroll + # to show the current tab. But `QTabBarPrivate::makeVisible()` refuses + # to do this unless the scroll buttons are visible. So we have to + # pretend to be using them and then use the stylesheet to set their size to + # 0. One benefit of this hack is that we can track the show/hide events + # for the buttons so that we only show our menu button when scrolling + # is necessary. This is a bit more reliable than trying to do it ourselves + # using resize events and size queries. + + self._qtWidget().setUsesScrollButtons( True ) + assert( len( self._qtWidget().tabBar().children() ) == 2 ) # We expect a left and a right button + _VisibilityLink( + self._qtWidget().tabBar().children()[0], + self.__cornerContainer[0]._qtWidget() + ) + + # Child storage and signals + # ------------------------- + + self.__widgets = [] self.__currentChangedSignal = GafferUI.WidgetEventSignal() self._qtWidget().currentChanged.connect( Gaffer.WeakMethod( self.__currentChanged ) ) @@ -167,49 +207,41 @@ def addChild( self, child, label="" ) : def removeChild( self, child ) : - assert( child is self.__cornerWidget or child in self.__widgets ) - - if child is self.__cornerWidget : - self._qtWidget().setCornerWidget( None ) - self.__cornerWidget = None + if child is self.getCornerWidget() : + self.setCornerWidget( None ) else : + assert( child in self.__widgets ) # We must remove the child from __widgets before the tab, otherwise # currentChangedSignal will be emit with the old widget. removalIndex = self.__widgets.index( child ) self.__widgets.remove( child ) self._qtWidget().removeTab( removalIndex ) - child._qtWidget().setParent( None ) - child._applyVisibility() + child._qtWidget().setParent( None ) + child._applyVisibility() def setCornerWidget( self, cornerWidget ) : - if self.__cornerWidget is not None : - self.removeChild( self.__cornerWidget ) + if len( self.__cornerContainer ) > 1 : + del self.__cornerContainer[1] if cornerWidget is not None : oldParent = cornerWidget.parent() if oldParent is not None : oldParent.removeChild( cornerWidget ) - self._qtWidget().setCornerWidget( cornerWidget._qtWidget() ) - cornerWidget._applyVisibility() - assert( cornerWidget._qtWidget().parent() is self._qtWidget() ) - else : - self._qtWidget().setCornerWidget( None ) - - self.__cornerWidget = cornerWidget + assert( len( self.__cornerContainer ) == 1 ) + self.__cornerContainer.append( cornerWidget ) def getCornerWidget( self ) : - return self.__cornerWidget + return self.__cornerContainer[1] if len( self.__cornerContainer ) > 1 else None ## If the tabs are hidden, then the corner widget will # also be hidden. def setTabsVisible( self, visible ) : self._qtWidget().tabBar().setVisible( visible ) - if self.__cornerWidget is not None : - self.__cornerWidget.setVisible( visible ) + self.__cornerContainer.setVisible( visible ) def getTabsVisible( self ) : @@ -270,6 +302,29 @@ def __switchToTabUnderCursor( self ) : if tab >= 0 : self._qtWidget().setCurrentIndex( tab ) + def __scrollMenuDefinition( self ) : + + result = IECore.MenuDefinition() + + current = self.getCurrent() + for child in self : + result.append( + self.getLabel( child ), + { + "checkBox" : child is current, + "command" : functools.partial( + Gaffer.WeakMethod( self.__scrollTo ), + child + ) + } + ) + + return result + + def __scrollTo( self, child, *unused ) : + + self.setCurrent( child ) + # Private implementation - a QTabWidget with custom size behaviour. class _TabWidget( QtWidgets.QTabWidget ) : @@ -303,3 +358,21 @@ def minimumSizeHint( self ) : return result +# Used to synchronise the visibility of our "scroll menu" with the visibility +# of Qt's scroll buttons. +class _VisibilityLink( QtCore.QObject ) : + + def __init__( self, sourceWidget, destinationWidget ) : + + # Parent to destination widget + QtCore.QObject.__init__( self, destinationWidget ) + + sourceWidget.installEventFilter( self ) + + def eventFilter( self, qObject, qEvent ) : + + qEventType = qEvent.type() + if qEventType == qEvent.Show or qEventType == qEvent.Hide : + self.parent().setVisible( qObject.isVisible() ) + + return False diff --git a/python/GafferUI/Timeline.py b/python/GafferUI/Timeline.py index 1125f393347..b4272b77501 100644 --- a/python/GafferUI/Timeline.py +++ b/python/GafferUI/Timeline.py @@ -74,7 +74,9 @@ def __init__( self, scriptNode, **kw ) : max = float( scriptNode["frameRange"]["end"].getValue() ), parenting = { "expand" : True }, ) - self.__slider.setPositionIncrement( 0 ) # disable so the slider doesn't mask our global frame increment shortcut + self.__slider.setIncrement( 0 ) # disable so the slider doesn't mask our global frame increment shortcut + self.__slider.setSnapIncrement( 1 ) + self.__slider.setHoverPositionVisible( True ) self.__sliderValueChangedConnection = self.__slider.valueChangedSignal().connect( Gaffer.WeakMethod( self.__valueChanged ), scoped = False ) self.__startButton = GafferUI.Button( image = "timelineStart.png", hasFrame=False ) @@ -151,13 +153,7 @@ def __valueChanged( self, widget, reason ) : assert( widget is self.__slider or widget is self.__frame ) - if widget is self.__slider : - ## \todo Have the rounding come from NumericSlider, and allow the shift - # modifier to choose fractional frame values. - frame = int( self.__slider.getValue() ) - else : - frame = self.__frame.getValue() - + frame = widget.getValue() frame = float( max( frame, self.scriptNode()["frameRange"]["start"].getValue() ) ) frame = float( min( frame, self.scriptNode()["frameRange"]["end"].getValue() ) ) @@ -250,14 +246,40 @@ def __repr__( self ) : GafferUI.Editor.registerType( "Timeline", Timeline ) -class _TimelineSlider( GafferUI.NumericSlider ) : +class _TimelineSlider( GafferUI.Slider ) : - def __init__( self, value=None, min=0, max=1, hardMin=None, hardMax=None, values=None, **kw ) : + def __init__( self, value, min=0, max=1, **kw ) : - GafferUI.NumericSlider.__init__( self, value, min, max, hardMin, hardMax, values, **kw ) + GafferUI.Slider.__init__( self, value, min, max, **kw ) - def _drawPosition( self, painter, position, highlighted, opacity=1 ) : + def _drawValue( self, painter, value, position, state ) : size = self.size() - # \todo: make sure the TimelineSlider and the AnimationGadget always use the same color - painter.fillRect( int(position * size.x), 0, 2, size.y, QtGui.QColor( 240, 220, 40, 255 * opacity ) ) + + color = QtGui.QColor( 120, 120, 120 ) if state == state.DisabledState else QtGui.QColor( 240, 220, 40 ) + painter.setPen( color ) + + # Draw vertical line at position ensuring we don't clip it + # at the edges of the widget. + + lineWidth = 2 + position = max( min( int( position ) - ( lineWidth / 2 ), size.x - lineWidth ), 0 ) + painter.fillRect( position, 0, lineWidth, size.y, color ) + + # Draw frame number to the left of the playhead (unless we'd go off the + # edge). Most cursors are pointing to the left so this makes it easier + # to read the number when hovering. + + font = painter.font() + font.setPixelSize( 10 ) + painter.setFont( font ) + + frameText = GafferUI.NumericWidget.valueToString( value ) + frameTextSize = QtGui.QFontMetrics( painter.font() ).size( QtCore.Qt.TextSingleLine, frameText ) + + textMargin = 6 + textX = position - frameTextSize.width() - textMargin + if textX < textMargin : + textX = position + textMargin + + painter.drawText( textX, frameTextSize.height(), frameText ) diff --git a/python/GafferUI/Widget.py b/python/GafferUI/Widget.py index cf542dd2dc0..91913d77923 100644 --- a/python/GafferUI/Widget.py +++ b/python/GafferUI/Widget.py @@ -234,21 +234,21 @@ def setVisible( self, visible ) : # parent Widgets. def getVisible( self ) : - # I'm very reluctant to have an explicit visibility field on Widget like this, - # as you'd think that would be duplicating the real information Qt holds inside - # QWidget. But Qt shows and hides things behind your back when parenting widgets, - # so there's no real way of knowing whether the Qt visibility is a result of - # your explicit actions or of Qt's implicit actions. Qt does have a flag - # WA_WState_ExplicitShowHide which appears to record whether or not the current - # visibility was requested explicitly, but in the case that that is false, - # I can't see a way of determining what the explicit visibility should be - # without tracking it separately. The only time our idea of visibility should - # differ from Qt's is if we're a parentless widget, so at least most of the time - # the assertion covers our asses a bit. - if self.__qtWidget.parent() or isinstance( self, GafferUI.Window ) : - assert( self.__visible == ( not self.__qtWidget.isHidden() ) ) - - return self.__visible + # I'm very reluctant to have an explicit visibility field on Widget like this, + # as you'd think that would be duplicating the real information Qt holds inside + # QWidget. But Qt shows and hides things behind your back when parenting widgets, + # so there's no real way of knowing whether the Qt visibility is a result of + # your explicit actions or of Qt's implicit actions. Qt does have a flag + # WA_WState_ExplicitShowHide which appears to record whether or not the current + # visibility was requested explicitly, but in the case that that is false, + # I can't see a way of determining what the explicit visibility should be + # without tracking it separately. The only time our idea of visibility should + # differ from Qt's is if we're a parentless widget, so at least most of the time + # the assertion covers our asses a bit. + if self.__qtWidget.parent() or isinstance( self, GafferUI.Window ) : + assert( self.__visible == ( not self.__qtWidget.isHidden() ) ) + + return self.__visible ## Returns True if this Widget and all its parents up to the specified # ancestor are visible. @@ -1243,18 +1243,16 @@ def __doDragEnterAndLeave( self, qObject, qEvent ) : candidateWidget = Widget.widgetAt( imath.V2i( qEvent.globalPos().x(), qEvent.globalPos().y() ) ) - if candidateWidget is self.__dragDropEvent.destinationWidget : - return - newDestinationWidget = None - if candidateWidget is not None : - while candidateWidget is not None : - if candidateWidget._dragEnterSignal is not None : - self.__dragDropEvent.line = self.__widgetSpaceLine( qEvent, candidateWidget ) - if candidateWidget._dragEnterSignal( candidateWidget, self.__dragDropEvent ) : - newDestinationWidget = candidateWidget - break - candidateWidget = candidateWidget.parent() + while candidateWidget is not None : + if candidateWidget is self.__dragDropEvent.destinationWidget : + return + if candidateWidget._dragEnterSignal is not None : + self.__dragDropEvent.line = self.__widgetSpaceLine( qEvent, candidateWidget ) + if candidateWidget._dragEnterSignal( candidateWidget, self.__dragDropEvent ) : + newDestinationWidget = candidateWidget + break + candidateWidget = candidateWidget.parent() if newDestinationWidget is None : if self.__dragDropEvent.destinationWidget is self.__dragDropEvent.sourceWidget : diff --git a/python/GafferUI/_StyleSheet.py b/python/GafferUI/_StyleSheet.py index 6895f2f96a4..e018c82ec25 100644 --- a/python/GafferUI/_StyleSheet.py +++ b/python/GafferUI/_StyleSheet.py @@ -643,6 +643,15 @@ def styleColor( key ) : border-bottom-color: $brightColor; } + QTabWidget[gafferClasses~="GafferUI.TabbedContainer"] > QTabBar::scroller { + /* Hide scroll buttons - see TabbedContainer.__init__ for motivation */ + width: 0px; + } + + QTabBar::tear { + image: none; + } + /* TabBars not inside a QTabWidget. Currently these are only used by SpreadsheetUI. @@ -693,10 +702,6 @@ def styleColor( key ) : width: 40px; } - QTabBar[gafferClass="GafferUI.SpreadsheetUI._SectionChooser"]::tear { - image: none; - } - QTabBar[gafferClass="GafferUI.SpreadsheetUI._SectionChooser"] QToolButton { background: $backgroundHighlight; border: 1px solid $backgroundDark; @@ -1533,6 +1538,16 @@ def styleColor( key ) : /* SceneInspector */ + *[gafferClass="GafferSceneUI.SceneInspector.Row"] > QFrame + { + /* Needed to avoid unwanted gaps between sections in the + * HistorySection and InheritanceSection. + */ + padding: 0px; + } + + /* SceneViewInspector */ + *[gafferClass="GafferSceneUI._SceneViewInspector"] > QFrame { margin-right: 1px; diff --git a/python/GafferUI/__init__.py b/python/GafferUI/__init__.py index a94b30f97e5..38c26aa511b 100644 --- a/python/GafferUI/__init__.py +++ b/python/GafferUI/__init__.py @@ -170,7 +170,6 @@ def __shiboken() : from .MatchPatternPathFilterWidget import MatchPatternPathFilterWidget from .FileSequencePathFilterWidget import FileSequencePathFilterWidget from .BusyWidget import BusyWidget -from .NumericSlider import NumericSlider from .ColorChooser import ColorChooser from .ColorChooserDialogue import ColorChooserDialogue from .MessageWidget import MessageWidget, MessageSummaryWidget @@ -271,6 +270,7 @@ def __shiboken() : from .ShufflePlugValueWidget import ShufflePlugValueWidget from .ShufflePlugValueWidget import ShufflesPlugValueWidget from .BackgroundTaskDialogue import BackgroundTaskDialogue +from . import AnnotationsUI # and then specific node uis diff --git a/python/GafferUITest/MenuBarTest.py b/python/GafferUITest/MenuBarTest.py index d4e9e95290a..22b88e9a8e0 100644 --- a/python/GafferUITest/MenuBarTest.py +++ b/python/GafferUITest/MenuBarTest.py @@ -270,4 +270,3 @@ def __simulateShortcut( self, widget ) : if __name__ == "__main__": unittest.main() - diff --git a/python/GafferUITest/NumericSliderTest.py b/python/GafferUITest/NumericSliderTest.py deleted file mode 100644 index 0cde1499d61..00000000000 --- a/python/GafferUITest/NumericSliderTest.py +++ /dev/null @@ -1,185 +0,0 @@ -########################################################################## -# -# Copyright (c) 2012, John Haddon. All rights reserved. -# Copyright (c) 2013, 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 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 GafferTest -import GafferUI -import GafferUITest - -class NumericSliderTest( GafferUITest.TestCase ) : - - def testConstruction( self ) : - - s = GafferUI.NumericSlider( value = 0, min = 0, max = 1 ) - - self.assertEqual( s.getPosition(), 0 ) - self.assertEqual( s.getValue(), 0 ) - self.assertEqual( s.getRange(), ( 0, 1, 0, 1 ) ) - - def testSetValue( self ) : - - s = GafferUI.NumericSlider( value = 0, min = 0, max = 2 ) - - self.assertEqual( s.getPosition(), 0 ) - self.assertEqual( s.getValue(), 0 ) - - s.setValue( 0.5 ) - self.assertEqual( s.getPosition(), 0.25 ) - self.assertEqual( s.getValue(), 0.5 ) - - def testSetRange( self ) : - - s = GafferUI.NumericSlider( value = 1, min = 0, max = 2 ) - - self.assertEqual( s.getPosition(), 0.5 ) - self.assertEqual( s.getValue(), 1 ) - - s.setRange( 0, 1 ) - self.assertEqual( s.getPosition(), 1 ) - self.assertEqual( s.getValue(), 1 ) - - def testSetZeroRange( self ) : - - s = GafferUI.NumericSlider( value = 1, min = 1, max = 2 ) - - self.assertEqual( s.getPosition(), 0 ) - self.assertEqual( s.getValue(), 1 ) - - s.setRange( 1, 1 ) - self.assertEqual( s.getValue(), 1 ) - - def testSetPosition( self ) : - - s = GafferUI.NumericSlider( value = 0, min = 0, max = 2 ) - - self.assertEqual( s.getPosition(), 0 ) - self.assertEqual( s.getValue(), 0 ) - - s.setPosition( 0.5 ) - self.assertEqual( s.getPosition(), 0.5 ) - self.assertEqual( s.getValue(), 1 ) - - def testValuesOutsideRangeAreClamped( self ) : - - s = GafferUI.NumericSlider( value = 0.1, min = 0, max = 2 ) - - cs = GafferTest.CapturingSlot( s.valueChangedSignal(), s.positionChangedSignal() ) - - s.setValue( 3 ) - self.assertEqual( s.getValue(), 2 ) - self.assertEqual( s.getPosition(), 1 ) - - self.assertEqual( len( cs ), 2 ) - - s.setValue( 3 ) - self.assertEqual( s.getValue(), 2 ) - self.assertEqual( s.getPosition(), 1 ) - - # second attempt was clamped to same position as before, so shouldn't - # signal any changes. - self.assertEqual( len( cs ), 2 ) - - def testPositionsOutsideRangeAreClamped( self ) : - - s = GafferUI.NumericSlider( value = 0.1, min = 0, max = 2 ) - - cs = GafferTest.CapturingSlot( s.valueChangedSignal(), s.positionChangedSignal() ) - - s.setPosition( 2 ) - self.assertEqual( s.getValue(), 2 ) - self.assertEqual( s.getPosition(), 1 ) - - self.assertEqual( len( cs ), 2 ) - - s.setPosition( 2 ) - self.assertEqual( s.getValue(), 2 ) - self.assertEqual( s.getPosition(), 1 ) - - # second attempt was clamped to same position as before, so shouldn't - # signal any changes. - self.assertEqual( len( cs ), 2 ) - - def testHardRange( self ) : - - s = GafferUI.NumericSlider( value = 0.1, min = 0, max = 2, hardMin=-1, hardMax=3 ) - self.assertEqual( s.getRange(), ( 0, 2, -1, 3 ) ) - - cs = GafferTest.CapturingSlot( s.valueChangedSignal(), s.positionChangedSignal() ) - - s.setValue( 3 ) - self.assertEqual( s.getValue(), 3 ) - self.assertEqual( s.getPosition(), 1.5 ) - self.assertEqual( len( cs ), 2 ) - - s.setValue( 3.5 ) - self.assertEqual( s.getValue(), 3 ) - self.assertEqual( s.getPosition(), 1.5 ) - self.assertEqual( len( cs ), 2 ) - - s.setValue( -1 ) - self.assertEqual( s.getValue(), -1 ) - self.assertEqual( s.getPosition(), -0.5) - self.assertEqual( len( cs ), 4 ) - - s.setValue( -2 ) - self.assertEqual( s.getValue(), -1 ) - self.assertEqual( s.getPosition(), -0.5) - self.assertEqual( len( cs ), 4 ) - - def testSetRangeClampsValue( self ) : - - s = GafferUI.NumericSlider( value = 0.5, min = 0, max = 2 ) - - self.assertEqual( s.getPosition(), 0.25 ) - self.assertEqual( s.getValue(), 0.5 ) - - s.setRange( 1, 2 ) - self.assertEqual( s.getPosition(), 0 ) - self.assertEqual( s.getValue(), 1 ) - - def testMultipleValues( self ) : - - self.assertRaises( Exception, GafferUI.NumericSlider, value = 0, values = [ 1, 2 ] ) - - s = GafferUI.NumericSlider( values = [ 1, 1.5 ], min = 0, max = 2 ) - self.assertEqual( s.getValues(), [ 1, 1.5 ] ) - self.assertEqual( s.getPositions(), [ 0.5, 0.75 ] ) - self.assertRaises( ValueError, s.getValue ) - -if __name__ == "__main__": - unittest.main() diff --git a/python/GafferUITest/SliderTest.py b/python/GafferUITest/SliderTest.py index 430ffa434eb..85107b8c530 100644 --- a/python/GafferUITest/SliderTest.py +++ b/python/GafferUITest/SliderTest.py @@ -45,39 +45,39 @@ class SliderTest( GafferUITest.TestCase ) : def testConstructor( self ) : s = GafferUI.Slider() - self.assertEqual( s.getPosition(), 0.5 ) - self.assertEqual( s.getPositions(), [ 0.5 ] ) + self.assertEqual( s.getValue(), 0.5 ) + self.assertEqual( s.getValues(), [ 0.5 ] ) - s = GafferUI.Slider( position = 1 ) - self.assertEqual( s.getPosition(), 1 ) - self.assertEqual( s.getPositions(), [ 1 ] ) + s = GafferUI.Slider( value = 1 ) + self.assertEqual( s.getValue(), 1 ) + self.assertEqual( s.getValues(), [ 1 ] ) - s = GafferUI.Slider( positions = [ 0, 1 ] ) - self.assertRaises( ValueError, s.getPosition ) - self.assertEqual( s.getPositions(), [ 0, 1 ] ) + s = GafferUI.Slider( values = [ 0, 1 ] ) + self.assertRaises( ValueError, s.getValue ) + self.assertEqual( s.getValues(), [ 0, 1 ] ) - self.assertRaises( Exception, GafferUI.Slider, position = 1, positions = [ 1, 2 ] ) + self.assertRaises( Exception, GafferUI.Slider, value = 1, values = [ 1, 2 ] ) - def testPositionAccessors( self ) : + def testValueAccessors( self ) : - s = GafferUI.Slider( position = 1 ) + s = GafferUI.Slider( value = 1, max = 4 ) - self.assertEqual( s.getPosition(), 1 ) - self.assertEqual( s.getPositions(), [ 1 ] ) + self.assertEqual( s.getValue(), 1 ) + self.assertEqual( s.getValues(), [ 1 ] ) - s.setPosition( 2 ) + s.setValue( 2 ) - self.assertEqual( s.getPosition(), 2 ) - self.assertEqual( s.getPositions(), [ 2 ] ) + self.assertEqual( s.getValue(), 2 ) + self.assertEqual( s.getValues(), [ 2 ] ) - s.setPositions( [ 2, 3 ] ) + s.setValues( [ 2, 3 ] ) - self.assertRaises( ValueError, s.getPosition ) - self.assertEqual( s.getPositions(), [ 2, 3 ] ) + self.assertRaises( ValueError, s.getValue ) + self.assertEqual( s.getValues(), [ 2, 3 ] ) def testSelectedIndex( self ) : - s = GafferUI.Slider( positions = [ 1, 2 ] ) + s = GafferUI.Slider( values = [ 1, 2 ] ) self.assertEqual( s.getSelectedIndex(), None ) s.setSelectedIndex( 0 ) @@ -106,5 +106,83 @@ def testSelectedIndex( self ) : self.assertTrue( cs[0][0] is s ) self.assertTrue( cs[1][0] is s ) + def testSetRange( self ) : + + s = GafferUI.Slider( value = 1, min = 0, max = 2 ) + self.assertEqual( s.getValue(), 1 ) + s.setRange( 0, 1 ) + self.assertEqual( s.getValue(), 1 ) + + def testSetZeroRange( self ) : + + s = GafferUI.Slider( value = 1, min = 1, max = 2 ) + + self.assertEqual( s.getValue(), 1 ) + s.setRange( 1, 1 ) + self.assertEqual( s.getValue(), 1 ) + + def testValuesOutsideRangeAreClamped( self ) : + + s = GafferUI.Slider( value = 0.1, min = 0, max = 2 ) + + cs = GafferTest.CapturingSlot( s.valueChangedSignal() ) + + s.setValue( 3 ) + self.assertEqual( s.getValue(), 2 ) + + self.assertEqual( len( cs ), 1 ) + + s.setValue( 3 ) + self.assertEqual( s.getValue(), 2 ) + + # second attempt was clamped to same position as before, so shouldn't + # signal any changes. + self.assertEqual( len( cs ), 1 ) + + def testHardRange( self ) : + + s = GafferUI.Slider( value = 0.1, min = 0, max = 2, hardMin=-1, hardMax=3 ) + self.assertEqual( s.getRange(), ( 0, 2, -1, 3 ) ) + + cs = GafferTest.CapturingSlot( s.valueChangedSignal() ) + + s.setValue( 3 ) + self.assertEqual( s.getValue(), 3 ) + self.assertEqual( len( cs ), 1 ) + + s.setValue( 3.5 ) + self.assertEqual( s.getValue(), 3 ) + self.assertEqual( len( cs ), 1 ) + + s.setValue( -1 ) + self.assertEqual( s.getValue(), -1 ) + self.assertEqual( len( cs ), 2 ) + + s.setValue( -2 ) + self.assertEqual( s.getValue(), -1 ) + self.assertEqual( len( cs ), 2 ) + + def testSetRangeClampsValue( self ) : + + s = GafferUI.Slider( value = 0.5, min = 0, max = 2 ) + + self.assertEqual( s.getValue(), 0.5 ) + + s.setRange( 1, 2 ) + self.assertEqual( s.getValue(), 1 ) + + def testMultipleValues( self ) : + + self.assertRaises( Exception, GafferUI.Slider, value = 0, values = [ 1, 2 ] ) + + s = GafferUI.Slider( values = [ 1, 1.5 ], min = 0, max = 2 ) + self.assertEqual( s.getValues(), [ 1, 1.5 ] ) + self.assertRaises( ValueError, s.getValue ) + + def testConstructWithValueOutOfRange( self ) : + + s = GafferUI.Slider( 2, min = 0, max = 1 ) + self.assertEqual( s.getValue(), 1 ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferUITest/TabbedContainerTest.py b/python/GafferUITest/TabbedContainerTest.py index 15bc1bb5cb1..51195b6beb8 100644 --- a/python/GafferUITest/TabbedContainerTest.py +++ b/python/GafferUITest/TabbedContainerTest.py @@ -70,12 +70,12 @@ def testCornerWidget( self ) : b = GafferUI.Button( "baby" ) t.setCornerWidget( b ) self.assertTrue( t.getCornerWidget() is b ) - self.assertTrue( b.parent() is t ) + self.assertTrue( t.isAncestorOf( b ) ) b2 = GafferUI.Button( "b" ) t.setCornerWidget( b2 ) self.assertTrue( t.getCornerWidget() is b2 ) - self.assertTrue( b2.parent() is t ) + self.assertTrue( t.isAncestorOf( b2 ) ) self.assertIsNone( b.parent() ) def testIndex( self ) : @@ -228,7 +228,7 @@ def testTransferCornerWidget( self ) : t.setCornerWidget( b ) self.assertEqual( len( l ), 0 ) - self.assertEqual( b.parent(), t ) + self.assertTrue( t.isAncestorOf( b ) ) def testInsert( self ) : diff --git a/python/GafferUITest/TestCase.py b/python/GafferUITest/TestCase.py index 04f4bf6ce8e..25b5d1655ca 100644 --- a/python/GafferUITest/TestCase.py +++ b/python/GafferUITest/TestCase.py @@ -150,4 +150,3 @@ def __widgetInstances() : result.append( w() ) return result - diff --git a/python/GafferUITest/__init__.py b/python/GafferUITest/__init__.py index 9d622370942..227e9d7d7c5 100644 --- a/python/GafferUITest/__init__.py +++ b/python/GafferUITest/__init__.py @@ -67,7 +67,6 @@ from .SelectionMenuTest import SelectionMenuTest from .StandardStyleTest import StandardStyleTest from .EditorTest import EditorTest -from .NumericSliderTest import NumericSliderTest from .PlugValueWidgetTest import PlugValueWidgetTest from .PathListingWidgetTest import PathListingWidgetTest from .MultiLineTextWidgetTest import MultiLineTextWidgetTest diff --git a/python/GafferVDBTest/LevelSetOffsetTest.py b/python/GafferVDBTest/LevelSetOffsetTest.py index 1baf8090cf5..903cc5798f9 100644 --- a/python/GafferVDBTest/LevelSetOffsetTest.py +++ b/python/GafferVDBTest/LevelSetOffsetTest.py @@ -69,13 +69,13 @@ def testBoundsUpdated( self ) : # sphere centred at the origin so we just take the x value of the max and it should equal the radius # hopefully the leafCounts should go like the square of the radius. - self.assertEqualTolerance( 5.0, levelSetOffset['out'].bound( "sphere" ).max()[0], 0.05 ) + self.assertAlmostEqual( 5.0, levelSetOffset['out'].bound( "sphere" ).max()[0], delta = 0.05 ) self.assertTrue( 1020 <= levelSetOffset['out'].object( "sphere" ).findGrid( "surface" ).leafCount() <= 1040 ) levelSetOffset["offset"].setValue( -1.0 ) - self.assertEqualTolerance( 6.0, levelSetOffset['out'].bound( "sphere" ).max()[0], 0.05 ) + self.assertAlmostEqual( 6.0, levelSetOffset['out'].bound( "sphere" ).max()[0], delta = 0.05 ) self.assertTrue( 1420 <= levelSetOffset['out'].object( "sphere" ).findGrid( "surface" ).leafCount() <= 1450) levelSetOffset["offset"].setValue( 1.0 ) - self.assertEqualTolerance( 4.0, levelSetOffset['out'].bound( "sphere" ).max()[0], 0.05 ) + self.assertAlmostEqual( 4.0, levelSetOffset['out'].bound( "sphere" ).max()[0], delta = 0.05 ) self.assertTrue( 640 <= levelSetOffset['out'].object( "sphere" ).findGrid( "surface" ).leafCount() <= 650) diff --git a/python/GafferVDBTest/LevelSetToMeshTest.py b/python/GafferVDBTest/LevelSetToMeshTest.py index 151aa5933e4..a8d9bf23f23 100644 --- a/python/GafferVDBTest/LevelSetToMeshTest.py +++ b/python/GafferVDBTest/LevelSetToMeshTest.py @@ -75,27 +75,41 @@ def testCanConvertLevelSetToMesh( self ) : self.assertTrue( isinstance( mesh, IECoreScene.MeshPrimitive) ) def testChangingIsoValueUpdatesBounds ( self ) : + sphere = GafferScene.Sphere() sphere["radius"].setValue( 5 ) meshToLevelSet = GafferVDB.MeshToLevelSet() self.setFilter( meshToLevelSet, path='/sphere' ) meshToLevelSet["voxelSize"].setValue( 0.05 ) - meshToLevelSet["exteriorBandwidth"].setValue( 4.0 ) - meshToLevelSet["interiorBandwidth"].setValue( 4.0 ) + meshToLevelSet["interiorBandwidth"].setValue( 100 ) meshToLevelSet["in"].setInput( sphere["out"] ) levelSetToMesh = GafferVDB.LevelSetToMesh() self.setFilter( levelSetToMesh, path='/sphere' ) levelSetToMesh["in"].setInput( meshToLevelSet["out"] ) - self.assertEqualTolerance( 5.0, levelSetToMesh['out'].bound( "sphere" ).max()[0], 0.05 ) + self.assertSceneValid( levelSetToMesh["out"] ) + self.assertEqual( levelSetToMesh["adjustBounds"].getValue(), False ) + self.assertEqual( levelSetToMesh["out"].bound( "/sphere" ), levelSetToMesh["in"].bound( "/sphere" ) ) - levelSetToMesh['isoValue'].setValue(0.5) - self.assertEqualTolerance( 5.5, levelSetToMesh['out'].bound( "sphere" ).max()[0], 0.05 ) + levelSetToMesh["adjustBounds"].setValue( True ) + self.assertSceneValid( levelSetToMesh["out"] ) + self.assertEqual( + levelSetToMesh["out"].bound( "/sphere" ), + levelSetToMesh["out"].object( "/sphere" ).bound() + ) + bound = levelSetToMesh["out"].bound( "/sphere" ) - levelSetToMesh['isoValue'].setValue(-0.5) - self.assertEqualTolerance( 4.5, levelSetToMesh['out'].bound( "sphere" ).max()[0], 0.05 ) + levelSetToMesh["isoValue"].setValue( -0.5 ) # Shrinks the output mesh + + self.assertSceneValid( levelSetToMesh["out"] ) + self.assertEqual( + levelSetToMesh["out"].bound( "/sphere" ), + levelSetToMesh["out"].object( "/sphere" ).bound() + ) + self.assertTrue( bound.intersects( levelSetToMesh["out"].bound( "/sphere" ).min() ) ) + self.assertTrue( bound.intersects( levelSetToMesh["out"].bound( "/sphere" ).max() ) ) def testIncreasingAdapativityDecreasesPolyCount( self ) : sphere = GafferScene.Sphere() @@ -117,4 +131,3 @@ def testIncreasingAdapativityDecreasesPolyCount( self ) : levelSetToMesh['adaptivity'].setValue(1.0) self.assertTrue( 2800 <= len( levelSetToMesh['out'].object( "sphere" ).verticesPerFace ) <= 3200 ) - diff --git a/python/GafferVDBTest/MeshToLevelSetTest.py b/python/GafferVDBTest/MeshToLevelSetTest.py index ff987973309..12cd4603800 100644 --- a/python/GafferVDBTest/MeshToLevelSetTest.py +++ b/python/GafferVDBTest/MeshToLevelSetTest.py @@ -44,12 +44,21 @@ import os import GafferScene - class MeshToLevelSetTest( GafferVDBTest.VDBTestCase ) : - def setUp( self ) : - GafferVDBTest.VDBTestCase.setUp( self ) - self.sourcePath = os.path.join( self.dataDir, "sphere.vdb" ) - self.sceneInterface = IECoreScene.SceneInterface.create( self.sourcePath, IECore.IndexedIO.OpenMode.Read ) + + def testAffects( self ) : + + sphere = GafferScene.Sphere() + meshToLevelSet = GafferVDB.MeshToLevelSet() + meshToLevelSet["in"].setInput( sphere["out"] ) + + cs = GafferTest.CapturingSlot( meshToLevelSet.plugDirtiedSignal() ) + self.setFilter( meshToLevelSet, path = "/sphere" ) + self.assertIn( meshToLevelSet["out"]["object"], { x[0] for x in cs } ) + + del cs[:] + sphere["radius"].setValue( 2 ) + self.assertIn( meshToLevelSet["out"]["object"], { x[0] for x in cs } ) def testCanConvertMeshToLevelSetVolume( self ) : sphere = GafferScene.Sphere() diff --git a/python/GafferVDBTest/PointsGridToPointsTest.py b/python/GafferVDBTest/PointsGridToPointsTest.py index 5c78498dc6b..959a9a1a34f 100644 --- a/python/GafferVDBTest/PointsGridToPointsTest.py +++ b/python/GafferVDBTest/PointsGridToPointsTest.py @@ -58,8 +58,12 @@ def testCanConvertPointsGridToPoints( self ) : sceneReader = GafferScene.SceneReader( "SceneReader" ) sceneReader["fileName"].setValue( self.sourcePath ) + pointsFilter = GafferScene.PathFilter() + pointsFilter["paths"].setValue( IECore.StringVectorData( [ "/vdb" ] ) ) + pointsGridToPoints = GafferVDB.PointsGridToPoints( "PointsGridToPoints" ) pointsGridToPoints["in"].setInput( sceneReader["out"] ) + pointsGridToPoints["filter"].setInput( pointsFilter["out"] ) points = pointsGridToPoints["out"].object("/vdb") @@ -73,12 +77,16 @@ def testVDBObjectLeftUnchangedIfIncorrectGrid( self ) : sceneReader = GafferScene.SceneReader( "SceneReader" ) sceneReader["fileName"].setValue( self.sourcePath ) + pointsFilter = GafferScene.PathFilter() + pointsFilter["paths"].setValue( IECore.StringVectorData( [ "/vdb" ] ) ) + pointsGridToPoints = GafferVDB.PointsGridToPoints( "PointsGridToPoints" ) pointsGridToPoints["in"].setInput( sceneReader["out"] ) + pointsGridToPoints["filter"].setInput( pointsFilter["out"] ) pointsGridToPoints["grid"].setValue( "nogridhere" ) vdb = pointsGridToPoints["out"].object("/vdb") self.assertTrue( isinstance( vdb, IECoreVDB.VDBObject) ) if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/python/GafferVDBTest/VDBTestCase.py b/python/GafferVDBTest/VDBTestCase.py index c5a8d2c9242..f00c6975789 100644 --- a/python/GafferVDBTest/VDBTestCase.py +++ b/python/GafferVDBTest/VDBTestCase.py @@ -43,9 +43,6 @@ class VDBTestCase( GafferSceneTest.SceneTestCase ) : - def assertEqualTolerance(self, a, b, tolerance): - self.assertTrue( abs(a - b) < tolerance) - def setUp( self ) : GafferSceneTest.SceneTestCase.setUp( self ) self.dataDir = os.path.join( os.path.dirname( __file__ ), "data") diff --git a/resources/graphics.py b/resources/graphics.py index 57203c155c0..bd39ac600bf 100644 --- a/resources/graphics.py +++ b/resources/graphics.py @@ -235,7 +235,46 @@ 'viewPaused' ] - } + }, + + "tabIcons" : { + + "ids" : [ + "tabScrollMenu", + "deleteSmall", + ], + + }, + + "colorInspectorIcons" : { + + "options" : { + "requiredWidth" : 16, + "requiredHeight" : 16, + "validatePixelAlignment" : True + }, + + "ids" : [ + 'sourceCursor', + 'sourceArea', + 'sourcePixel' + ] + + }, + + "graphEditor" : { + + "options" : { + "requiredWidth" : 25, + "requiredHeight" : 25, + "validatePixelAlignment" : True + }, + + "ids" : [ + "annotations", + ], + + }, }, @@ -249,7 +288,6 @@ 'debugNotification', 'debugSmall', 'delete', - 'deleteSmall', 'duplicate', 'editScopeNode', 'editScopeProcessorNode', @@ -276,10 +314,8 @@ 'navigationArrow', 'nodeSetDriverNodeSelection', 'nodeSetDriverNodeSet', - 'nodeSetDriverSceneSelectionSource', 'nodeSetDrivertestMode', 'nodeSetNumericBookmarkSet', - 'nodeSetSourceSet', 'nodeSetStandardSet', 'plugAdder', 'plugAdderHighlighted', diff --git a/resources/graphics.svg b/resources/graphics.svg index cad98402146..ac697b9e709 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -15,7 +15,7 @@ height="1000" id="svg2" version="1.1" - inkscape:version="0.92.2 5c3e80d, 2017-08-06" + inkscape:version="0.92.2 (5c3e80d, 2017-08-06)" sodipodi:docname="graphics.svg" enable-background="new"> + gradientTransform="translate(54.13258,7492.5003)"> - - - - - - - - - - + + + @@ -1636,7 +1570,7 @@ image/svg+xml - + @@ -1644,8 +1578,7 @@ inkscape:groupmode="layer" id="layer3" inkscape:label="Annotations" - style="display:inline;opacity:1" - sodipodi:insensitive="true"> + style="display:inline;opacity:1"> - + + + Tab icons Color Inspector Icons + + GraphEditor + transform="translate(0,-54)" + style="display:inline"> - - + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shaders/MaterialX/mx_pack_color.osl b/shaders/MaterialX/mx_pack_color.osl new file mode 100644 index 00000000000..741db9a45ec --- /dev/null +++ b/shaders/MaterialX/mx_pack_color.osl @@ -0,0 +1,17 @@ +// MaterialX removed this shader in a minor version revision. But we still need it in +// `IECore::ShaderNetworkAlgo::convertOSLComponentConnections()` so we have reimplementd it here. +// We originally used it to emulate component-level connections for colors, but OSL supports +// that natively now. But we continue to use it to work around bugs in Arnold - see +// `ArnoldRenderTest.testOSLShaders()`. +/// \todo Remove once the Arnold bug is fixed. +shader mx_pack_color +( + float in1 = 0, + float in2 = 0, + float in3 = 0, + float in4 = 0, + output color out = 0 +) +{ + out = color( in1, in2, in3 ); +} diff --git a/shaders/Surface/Constant.osl b/shaders/Surface/Constant.osl index 92f99f8c8bd..49e576e32f4 100644 --- a/shaders/Surface/Constant.osl +++ b/shaders/Surface/Constant.osl @@ -36,8 +36,13 @@ surface Constant ( - color Cs = 1 + color Cs = 1, + // Arnold doesn't support assignment to `Ci`, even though that's the + // standard way to provide the final shading result in OSL. So we mirror + // `Ci` into an output closure that Arnold _will_ render. + output closure color out = 0 ) { Ci = Cs * emission(); + out = Ci; } diff --git a/src/Gaffer/Animation.cpp b/src/Gaffer/Animation.cpp index b997fb7ae4e..8e6ee4065dc 100644 --- a/src/Gaffer/Animation.cpp +++ b/src/Gaffer/Animation.cpp @@ -500,7 +500,7 @@ Animation::CurvePlug *Animation::acquire( ValuePlug *plug ) throw IECore::Exception( "Plug does not belong to a node" ); } - for( RecursivePlugIterator it( plug->node() ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug->node() ); !it.done(); ++it ) { ValuePlug *valuePlug = runTimeCast( it->get() ); if( !valuePlug ) diff --git a/src/Gaffer/ArrayPlug.cpp b/src/Gaffer/ArrayPlug.cpp index b2b6d7d999a..c477200e3d3 100644 --- a/src/Gaffer/ArrayPlug.cpp +++ b/src/Gaffer/ArrayPlug.cpp @@ -55,7 +55,7 @@ bool hasInput( const Plug *p ) { return true; } - for( PlugIterator it( p ); !it.done(); ++it ) + for( Plug::Iterator it( p ); !it.done(); ++it ) { if( hasInput( it->get() ) ) { @@ -127,7 +127,7 @@ void ArrayPlug::setInput( PlugPtr input ) PlugPtr ArrayPlug::createCounterpart( const std::string &name, Direction direction ) const { ArrayPlugPtr result = new ArrayPlug( name, direction, nullptr, m_minSize, m_maxSize, getFlags(), resizeWhenInputsChange() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } @@ -219,8 +219,9 @@ void ArrayPlug::inputChanged( Gaffer::Plug *plug ) if( const ScriptNode *script = ancestor() ) { - if( script->currentActionStage() == Action::Undo || - script->currentActionStage() == Action::Redo + if( + script->currentActionStage() == Action::Undo || + script->currentActionStage() == Action::Redo ) { // If we're currently in an undo or redo, we don't diff --git a/src/Gaffer/BackgroundTask.cpp b/src/Gaffer/BackgroundTask.cpp index 27bc07a5ffb..be9c5cd1672 100644 --- a/src/Gaffer/BackgroundTask.cpp +++ b/src/Gaffer/BackgroundTask.cpp @@ -46,7 +46,7 @@ #include "boost/multi_index/hashed_index.hpp" #include "boost/multi_index_container.hpp" -#include "tbb/task.h" +#include "tbb/task_arena.h" using namespace IECore; using namespace Gaffer; @@ -58,29 +58,6 @@ using namespace Gaffer; namespace { -class FunctionTask : public tbb::task -{ - public : - - typedef std::function Function; - - FunctionTask( const Function &f ) - : m_f( f ) - { - } - - tbb::task *execute() override - { - m_f(); - return nullptr; - } - - private : - - Function m_f; - -}; - const ScriptNode *scriptNode( const GraphComponent *subject ) { if( !subject ) @@ -164,9 +141,9 @@ BackgroundTask::BackgroundTask( const Plug *subject, const Function &function ) { activeTasks().insert( ActiveTask{ this, scriptNode( subject ) } ); - auto taskData = m_taskData; - tbb::task *functionTask = new( tbb::task::allocate_root() ) FunctionTask( - [taskData] { + // Enqueue task into current arena. + tbb::task_arena( tbb::task_arena::attach() ).enqueue( + [taskData = m_taskData] { // Early out if we were cancelled before the task // even started. @@ -221,7 +198,6 @@ BackgroundTask::BackgroundTask( const Plug *subject, const Function &function ) taskData->conditionVariable.notify_one(); } ); - tbb::task::enqueue( *functionTask ); } BackgroundTask::~BackgroundTask() diff --git a/src/Gaffer/Box.cpp b/src/Gaffer/Box.cpp index f63d95b3c99..29d474662a2 100644 --- a/src/Gaffer/Box.cpp +++ b/src/Gaffer/Box.cpp @@ -60,9 +60,9 @@ namespace std::unordered_set boxOutPassThroughSources( const Node *parent ) { std::unordered_set result; - for( BoxOutIterator it( parent ); !it.done(); ++it ) + for( const auto &boxOut : BoxOut::Range( *parent ) ) { - Plug *plug = (*it)->passThroughPlug(); + Plug *plug = boxOut->passThroughPlug(); while( plug ) { if( const Node *node = plug->node() ) @@ -188,21 +188,21 @@ BoxPtr Box::create( Node *parent, const Set *childNodes ) // which should remain where they are. std::unordered_set boxOutPassThroughSources = ::boxOutPassThroughSources( parent ); StandardSetPtr verifiedChildNodes = new StandardSet(); - for( NodeIterator nodeIt( parent ); !nodeIt.done(); ++nodeIt ) + for( const auto &node : Node::Range( *parent ) ) { - if( !childNodes->contains( nodeIt->get() ) ) + if( !childNodes->contains( node.get() ) ) { continue; } - if( IECore::runTimeCast( *nodeIt ) ) + if( IECore::runTimeCast( node.get() ) ) { continue; } - if( boxOutPassThroughSources.find( nodeIt->get() ) != boxOutPassThroughSources.end() ) + if( boxOutPassThroughSources.find( node.get() ) != boxOutPassThroughSources.end() ) { continue; } - verifiedChildNodes->add( *nodeIt ); + verifiedChildNodes->add( node ); } // When a node we're putting in the box has connections to @@ -219,7 +219,7 @@ BoxPtr Box::create( Node *parent, const Set *childNodes ) Node *childNode = static_cast( verifiedChildNodes->member( i ) ); result->addChild( childNode ); // Reroute any connections to external nodes - for( RecursivePlugIterator plugIt( childNode ); !plugIt.done(); ++plugIt ) + for( Plug::RecursiveIterator plugIt( childNode ); !plugIt.done(); ++plugIt ) { Plug *plug = plugIt->get(); if( plug->direction() == Plug::In ) diff --git a/src/Gaffer/BoxIO.cpp b/src/Gaffer/BoxIO.cpp index 4c9c5e83c75..8123934d1ac 100644 --- a/src/Gaffer/BoxIO.cpp +++ b/src/Gaffer/BoxIO.cpp @@ -127,7 +127,7 @@ void applyDynamicFlag( Plug *plug ) auto compoundTypes = { PlugTypeId, ValuePlugTypeId, ArrayPlugTypeId }; if( find( begin( compoundTypes ), end( compoundTypes ), (Gaffer::TypeId)plug->typeId() ) != end( compoundTypes ) ) { - for( RecursivePlugIterator it( plug ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug ); !it.done(); ++it ) { (*it)->setFlags( Plug::Dynamic, true ); if( find( begin( compoundTypes ), end( compoundTypes ), (Gaffer::TypeId)(*it)->typeId() ) == end( compoundTypes ) ) @@ -151,7 +151,7 @@ void setFrom( Plug *dst, const Plug *src ) } else { - for( PlugIterator it( dst ); !it.done(); ++it ) + for( Plug::Iterator it( dst ); !it.done(); ++it ) { Plug *dstChild = it->get(); const Plug *srcChild = src->getChild( dstChild->getName() ); @@ -487,8 +487,9 @@ void BoxIO::promotedPlugParentChanged( GraphComponent *graphComponent ) // ourselves too. if( const ScriptNode *script = scriptNode() ) { - if( script->currentActionStage() == Action::Undo || - script->currentActionStage() == Action::Redo + if( + script->currentActionStage() == Action::Undo || + script->currentActionStage() == Action::Redo ) { // We don't need to do anything during undo/redo @@ -620,7 +621,7 @@ Plug *BoxIO::promote( Plug *plug ) bool BoxIO::canInsert( const Box *box ) { - for( PlugIterator it( box ); !it.done(); ++it ) + for( Plug::Iterator it( box ); !it.done(); ++it ) { const Plug *plug = it->get(); if( plug->direction() == Plug::In ) @@ -650,9 +651,9 @@ bool BoxIO::canInsert( const Box *box ) void BoxIO::insert( Box *box ) { // Must take a copy of children because adding a child - // would invalidate our PlugIterator. + // would invalidate our Plug::Iterator. GraphComponent::ChildContainer children = box->children(); - for( PlugIterator it( children ); !it.done(); ++it ) + for( Plug::Iterator it( children ); !it.done(); ++it ) { Plug *plug = it->get(); if( plug->direction() == Plug::In ) diff --git a/src/Gaffer/BoxPlug.cpp b/src/Gaffer/BoxPlug.cpp index a20f2077c1b..b2ec0a7b9cd 100644 --- a/src/Gaffer/BoxPlug.cpp +++ b/src/Gaffer/BoxPlug.cpp @@ -209,4 +209,3 @@ template class BoxPlug; template class BoxPlug; } - diff --git a/src/Gaffer/CompoundDataPlug.cpp b/src/Gaffer/CompoundDataPlug.cpp index 78743586179..053314d124e 100644 --- a/src/Gaffer/CompoundDataPlug.cpp +++ b/src/Gaffer/CompoundDataPlug.cpp @@ -91,7 +91,7 @@ bool CompoundDataPlug::acceptsChild( const GraphComponent *potentialChild ) cons PlugPtr CompoundDataPlug::createCounterpart( const std::string &name, Direction direction ) const { CompoundDataPlugPtr result = new CompoundDataPlug( name, direction, getFlags() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } @@ -115,7 +115,7 @@ void CompoundDataPlug::addMembers( const IECore::CompoundData *parameters, bool void CompoundDataPlug::fillCompoundData( IECore::CompoundDataMap &compoundDataMap ) const { std::string name; - for( NameValuePlugIterator it( this ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( this ); !it.done(); ++it ) { IECore::DataPtr data = memberDataAndName( it->get(), name ); if( data ) @@ -128,7 +128,7 @@ void CompoundDataPlug::fillCompoundData( IECore::CompoundDataMap &compoundDataMa IECore::MurmurHash CompoundDataPlug::hash() const { IECore::MurmurHash h; - for( NameValuePlugIterator it( this ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( this ); !it.done(); ++it ) { const NameValuePlug *plug = it->get(); bool active = true; @@ -153,7 +153,7 @@ void CompoundDataPlug::hash( IECore::MurmurHash &h ) const void CompoundDataPlug::fillCompoundObject( IECore::CompoundObject::ObjectMap &compoundObjectMap ) const { std::string name; - for( NameValuePlugIterator it( this ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( this ); !it.done(); ++it ) { IECore::DataPtr data = memberDataAndName( it->get(), name ); if( data ) @@ -190,4 +190,3 @@ IECore::DataPtr CompoundDataPlug::memberDataAndName( const NameValuePlug *parame return PlugAlgo::extractDataFromPlug( valuePlug( parameterPlug ) ); } - diff --git a/src/Gaffer/CompoundNumericPlug.cpp b/src/Gaffer/CompoundNumericPlug.cpp index 88b176a5cfa..699dfbf5a04 100644 --- a/src/Gaffer/CompoundNumericPlug.cpp +++ b/src/Gaffer/CompoundNumericPlug.cpp @@ -295,4 +295,3 @@ template class CompoundNumericPlug; template class CompoundNumericPlug; } - diff --git a/src/Gaffer/Context.cpp b/src/Gaffer/Context.cpp index c0201005959..a6ff74e2618 100644 --- a/src/Gaffer/Context.cpp +++ b/src/Gaffer/Context.cpp @@ -39,6 +39,7 @@ #include "IECore/SimpleTypedData.h" #include "IECore/VectorTypedData.h" +#include "IECore/PathMatcherData.h" #include "boost/lexical_cast.hpp" @@ -108,6 +109,120 @@ Environment g_environment; } // namespace +////////////////////////////////////////////////////////////////////////// +// Context::Value implementation +////////////////////////////////////////////////////////////////////////// + +Context::Value::Value( const IECore::InternedString &name, const IECore::Data *value ) + : Value( typeFunctions( value->typeId() ).constructor( name, value ) ) +{ +} + +Context::Value::Value( IECore::TypeId typeId, const void *value, const IECore::MurmurHash &hash ) + : m_typeId( typeId ), m_value( value ), m_hash( hash ) +{ +} + +bool Context::Value::operator == ( const Value &rhs ) const +{ + if( m_typeId != rhs.m_typeId ) + { + return false; + } + if( m_value == rhs.m_value ) + { + return true; + } + return typeFunctions( m_typeId ).isEqual( *this, rhs ); +} + +bool Context::Value::operator != ( const Value &rhs ) const +{ + return !(*this == rhs); +} + +bool Context::Value::references( const IECore::Data *data ) const +{ + if( m_typeId != data->typeId() ) + { + return false; + } + return typeFunctions( m_typeId ).valueFromData( data ) == m_value; +} + +IECore::DataPtr Context::Value::makeData() const +{ + return typeFunctions( m_typeId ).makeData( *this, nullptr ); +} + +Context::Value Context::Value::copy( IECore::ConstDataPtr &owner ) const +{ + const void *v; + owner = typeFunctions( m_typeId ).makeData( *this, &v ); + return Value( m_typeId, v, m_hash ); +} + +void Context::Value::validate( const IECore::InternedString &name ) const +{ + typeFunctions( m_typeId ).validate( name, *this ); +} + +Context::Value::TypeMap &Context::Value::typeMap() +{ + static TypeMap m_map; + return m_map; +} + +const Context::Value::TypeFunctions &Context::Value::typeFunctions( IECore::TypeId typeId ) +{ + const TypeMap &m = typeMap(); + auto it = m.find( typeId ); + if( it == m.end() ) + { + throw IECore::Exception( + "Context does not support type " + std::string( RunTimeTyped::typeNameFromTypeId( typeId ) ) + ); + } + return it->second; +} + +////////////////////////////////////////////////////////////////////////// +// Type registrations +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +Context::TypeDescription g_floatTypeDescription; +Context::TypeDescription g_intTypeDescription; +Context::TypeDescription g_boolTypeDescription; +Context::TypeDescription g_stringTypeDescription; +Context::TypeDescription g_internedStringTypeDescription; +Context::TypeDescription g_v2iTypeDescription; +Context::TypeDescription g_v3iTypeDescription; +Context::TypeDescription g_v2fTypeDescription; +Context::TypeDescription g_v3fTypeDescription; +Context::TypeDescription g_color3fTypeDescription; +Context::TypeDescription g_color4fTypeDescription; +Context::TypeDescription g_box2iTypeDescription; +Context::TypeDescription g_uint64TypeDescription; +Context::TypeDescription g_internedStringVectorTypeDescription; +Context::TypeDescription g_pathMatcherTypeDescription; +Context::TypeDescription g_box2fTypeDescription; +Context::TypeDescription g_box3iTypeDescription; +Context::TypeDescription g_box3fTypeDescription; +Context::TypeDescription g_floatVectorTypeDescription; +Context::TypeDescription g_intVectorTypeDescription; +Context::TypeDescription g_stringVectorTypeDescription; +Context::TypeDescription g_v2iVectorTypeDescription; +Context::TypeDescription g_v3iVectorTypeDescription; +Context::TypeDescription g_v2fVectorTypeDescription; +Context::TypeDescription g_v3fVectorTypeDescription; +Context::TypeDescription g_color3fVectorTypeDescription; +Context::TypeDescription g_color4fVectorTypeDescription; + +} // namespace + ////////////////////////////////////////////////////////////////////////// // Context implementation ////////////////////////////////////////////////////////////////////////// @@ -122,41 +237,57 @@ Context::Context() set( g_framesPerSecond, 24.0f ); } -Context::Context( const Context &other, Ownership ownership ) - : m_map( other.m_map ), - m_changedSignal( nullptr ), +Context::Context( const Context &other ) + : Context( other, CopyMode::Owning ) +{ +} + +Context::Context( const Context &other, CopyMode mode ) + : m_changedSignal( nullptr ), m_hash( other.m_hash ), m_hashValid( other.m_hashValid ), m_canceller( other.m_canceller ) { - // We used the (shallow) Map copy constructor in our initialiser above - // because it offers a big performance win over iterating and inserting copies - // ourselves. Now we need to go in and tweak our copies based on the ownership. + // Reserving one extra spot before we copy in the existing variables means that we will + // avoid a second allocation in the common case where we set exactly one context + // variable. Perhaps we should reserve two extra spots - though that is some extra memory + // to carry around in cases where we don't add any variables? + m_map.reserve( other.m_map.size() + 1 ); - for( Map::iterator it = m_map.begin(), eIt = m_map.end(); it != eIt; ++it ) + if( mode == CopyMode::NonOwning ) { - it->second.ownership = ownership; - switch( ownership ) + m_map = other.m_map; + } + else + { + // We need ownership of the stored values so that we remain valid even + // if the source context is destroyed. + m_allocMap.reserve( other.m_map.size() + 1 ); + for( auto &i : other.m_map ) { - case Copied : - { - DataPtr valueCopy = it->second.data->copy(); - it->second.data = valueCopy.get(); - it->second.data->addRef(); - break; - } - case Shared : - it->second.data->addRef(); - break; - case Borrowed : - // no need to do anything - break; + auto allocIt = other.m_allocMap.find( i.first ); + if( + allocIt != other.m_allocMap.end() && + i.second.references( allocIt->second.get() ) + ) + { + // The value is already owned by `other`, and is immutable, so we + // can just add our own reference to it to share ownership + // and then call `internalSet()`. + m_allocMap[i.first] = allocIt->second; + internalSet( i.first, i.second ); + } + else + { + // Data not owned by `other`. Take a copy that we own, and call `internalSet()`. + internalSet( i.first, i.second.copy( m_allocMap[i.first] ) ); + } } } } Context::Context( const Context &other, const IECore::Canceller &canceller ) - : Context( other, Copied ) + : Context( other ) { if( m_canceller ) { @@ -166,7 +297,7 @@ Context::Context( const Context &other, const IECore::Canceller &canceller ) } Context::Context( const Context &other, bool omitCanceller ) - : Context( other, Copied ) + : Context( other ) { if( omitCanceller ) { @@ -176,15 +307,31 @@ Context::Context( const Context &other, bool omitCanceller ) Context::~Context() { - for( Map::const_iterator it = m_map.begin(), eIt = m_map.end(); it != eIt; ++it ) + delete m_changedSignal; +} + +void Context::set( const IECore::InternedString &name, const IECore::Data *value ) +{ + // We copy the value so that the client can't invalidate this context by changing it. + ConstDataPtr copy = value->copy(); + if( internalSet( name, Value( name, copy.get() ) ) ) { - if( it->second.ownership != Borrowed ) - { - it->second.data->removeRef(); - } + m_allocMap[name] = copy; } +} - delete m_changedSignal; +IECore::DataPtr Context::getAsData( const IECore::InternedString &name ) const +{ + return internalGet( name ).makeData(); +} + +IECore::DataPtr Context::getAsData( const IECore::InternedString &name, const IECore::DataPtr &defaultValue ) const +{ + if( const Value *value = internalGetIfExists( name ) ) + { + return value->makeData(); + } + return defaultValue; } void Context::remove( const IECore::InternedString &name ) @@ -226,15 +373,6 @@ void Context::removeMatching( const StringAlgo::MatchPattern &pattern ) } } -void Context::changed( const IECore::InternedString &name ) -{ - m_hashValid = false; - if( m_changedSignal ) - { - (*m_changedSignal)( this, name ); - } -} - void Context::names( std::vector &names ) const { for( Map::const_iterator it = m_map.begin(), eIt = m_map.end(); it != eIt; it++ ) @@ -250,7 +388,7 @@ float Context::getFrame() const void Context::setFrame( float frame ) { - set( g_frame, frame ); + set( g_frame, frame ); } float Context::getFramesPerSecond() const @@ -295,42 +433,21 @@ IECore::MurmurHash Context::hash() const return m_hash; } - m_hash = IECore::MurmurHash(); + uint64_t sumH1 = 0, sumH2 = 0; for( Map::const_iterator it = m_map.begin(), eIt = m_map.end(); it != eIt; ++it ) { - /// \todo Perhaps at some point the UI should use a different container for - /// these "not computationally important" values, so we wouldn't have to skip - /// them here. - // Using a hardcoded comparison of the first three characters because - // it's quicker than `string::compare( 0, 3, "ui:" )`. - const std::string &name = it->first.string(); - if( name.size() > 2 && name[0] == 'u' && name[1] == 'i' && name[2] == ':' ) - { - continue; - } - m_hash.append( (uint64_t)&name ); - it->second.data->hash( m_hash ); + sumH1 += it->second.hash().h1(); + sumH2 += it->second.hash().h2(); } + + m_hash = MurmurHash( sumH1, sumH2 ); m_hashValid = true; return m_hash; } bool Context::operator == ( const Context &other ) const { - if( m_map.size() != other.m_map.size() ) - { - return false; - } - Map::const_iterator otherIt = other.m_map.begin(); - for( Map::const_iterator it = m_map.begin(), eIt = m_map.end(); it != eIt; ++it, ++otherIt ) - { - if( it->first != otherIt->first || !( it->second.data->isEqualTo( otherIt->second.data ) ) ) - { - return false; - } - } - - return true; + return m_map == other.m_map; } bool Context::operator != ( const Context &other ) const @@ -365,13 +482,13 @@ Context::Scope::~Scope() } Context::EditableScope::EditableScope( const Context *context ) - : m_context( new Context( *context, Borrowed ) ) + : m_context( new Context( *context, CopyMode::NonOwning ) ) { m_threadState->m_context = m_context.get(); } Context::EditableScope::EditableScope( const ThreadState &threadState ) - : ThreadState::Scope( threadState ), m_context( new Context( *threadState.m_context, Borrowed ) ) + : ThreadState::Scope( threadState ), m_context( new Context( *threadState.m_context, CopyMode::NonOwning ) ) { m_threadState->m_context = m_context.get(); } @@ -380,9 +497,15 @@ Context::EditableScope::~EditableScope() { } +void Context::EditableScope::setAllocated( const IECore::InternedString &name, const IECore::Data *value ) +{ + m_context->set( name, value ); +} + void Context::EditableScope::setFrame( float frame ) { - m_context->setFrame( frame ); + m_frameStorage = frame; + set( g_frame, &m_frameStorage ); } void Context::EditableScope::setFramesPerSecond( float framesPerSecond ) @@ -392,7 +515,12 @@ void Context::EditableScope::setFramesPerSecond( float framesPerSecond ) void Context::EditableScope::setTime( float timeInSeconds ) { - m_context->setTime( timeInSeconds ); + setFrame( timeInSeconds * m_context->getFramesPerSecond() ); +} + +void Context::EditableScope::setFramesPerSecond( const float *framesPerSecond ) +{ + set( g_framesPerSecond, framesPerSecond ); } void Context::EditableScope::remove( const IECore::InternedString &name ) @@ -427,29 +555,31 @@ int Context::SubstitutionProvider::frame() const const std::string &Context::SubstitutionProvider::variable( const boost::string_view &name, bool &recurse ) const { InternedString internedName( name ); - const IECore::Data *d = m_context->get( internedName, nullptr ); - if( d ) + if( const Value *value = m_context->internalGetIfExists( internedName ) ) { - switch( d->typeId() ) + switch( value->typeId() ) { case IECore::StringDataTypeId : recurse = true; - return static_cast( d )->readable(); + return *static_cast( value->rawValue() ); + case IECore::InternedStringDataTypeId : + recurse = true; + return *static_cast( value->rawValue() ); case IECore::FloatDataTypeId : m_formattedString = boost::lexical_cast( - static_cast( d )->readable() + *static_cast( value->rawValue() ) ); return m_formattedString; case IECore::IntDataTypeId : m_formattedString = boost::lexical_cast( - static_cast( d )->readable() + *static_cast( value->rawValue() ) ); return m_formattedString; case IECore::InternedStringVectorDataTypeId : { // This is unashamedly tailored to the needs of GafferScene's `${scene:path}` // variable. We could make this cleaner by adding a mechanism for registering custom // formatters, but that would be overkill for this one use case. - const auto &v = static_cast( d )->readable(); + const auto &v = *static_cast* >( value->rawValue() ); m_formattedString.clear(); if( v.empty() ) { diff --git a/src/Gaffer/ContextMonitor.cpp b/src/Gaffer/ContextMonitor.cpp index 1f4ee7675f6..f6faa902102 100644 --- a/src/Gaffer/ContextMonitor.cpp +++ b/src/Gaffer/ContextMonitor.cpp @@ -82,8 +82,7 @@ ContextMonitor::Statistics & ContextMonitor::Statistics::operator += ( const Con context->names( names ); for( vector::const_iterator it = names.begin(), eIt = names.end(); it != eIt; ++it ) { - const Data *d = context->get( *it ); - m_variables[*it][d->Object::hash()] += 1; + m_variables[*it][context->variableHash( *it )] += 1; } return *this; } diff --git a/src/Gaffer/ContextProcessor.cpp b/src/Gaffer/ContextProcessor.cpp index 68748ac95b9..b00d28926f4 100644 --- a/src/Gaffer/ContextProcessor.cpp +++ b/src/Gaffer/ContextProcessor.cpp @@ -57,10 +57,14 @@ class ContextProcessor::ProcessedScope : public Context::EditableScope ContextAlgo::GlobalScope globalScope( context, processor->inPlug() ); if( processor->enabledPlug()->getValue() ) { - processor->processContext( *this ); + processor->processContext( *this, m_storage ); } } + private : + + IECore::ConstRefCountedPtr m_storage; + }; GAFFER_NODE_DEFINE_TYPE( ContextProcessor ); @@ -165,7 +169,7 @@ void ContextProcessor::affects( const Plug *input, DependencyNode::AffectedPlugs { if( out->children().size() ) { - for( RecursiveOutputPlugIterator it( out ); !it.done(); ++it ) + for( Plug::RecursiveOutputIterator it( out ); !it.done(); ++it ) { if( !(*it)->children().size() ) { diff --git a/src/Gaffer/ContextVariables.cpp b/src/Gaffer/ContextVariables.cpp index d26671fdfb6..d197fc275c4 100644 --- a/src/Gaffer/ContextVariables.cpp +++ b/src/Gaffer/ContextVariables.cpp @@ -39,9 +39,23 @@ #include "Gaffer/Context.h" #include "IECore/SimpleTypedData.h" +#include "IECore/DataAlgo.h" using namespace Gaffer; +namespace { + +struct SetFromReadable +{ + template< class T> + void operator()( const T *data, Gaffer::Context::EditableScope &scope, const IECore::InternedString &name ) + { + scope.set( name, &data->readable() ); + } +}; + +} + GAFFER_NODE_DEFINE_TYPE( ContextVariables ); size_t ContextVariables::g_firstPlugIndex; @@ -52,6 +66,7 @@ ContextVariables::ContextVariables( const std::string &name ) storeIndexOfNextChild( g_firstPlugIndex ); addChild( new CompoundDataPlug( "variables" ) ); addChild( new AtomicCompoundDataPlug( "extraVariables", Plug::In, new IECore::CompoundData ) ); + addChild( new AtomicCompoundDataPlug( "__combinedVariables", Plug::Out, new IECore::CompoundData ) ); } ContextVariables::~ContextVariables() @@ -78,26 +93,74 @@ const AtomicCompoundDataPlug *ContextVariables::extraVariablesPlug() const return getChild( g_firstPlugIndex + 1 ); } +AtomicCompoundDataPlug *ContextVariables::combinedVariablesPlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const AtomicCompoundDataPlug *ContextVariables::combinedVariablesPlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + +void ContextVariables::affects( const Plug *input, DependencyNode::AffectedPlugsContainer &outputs ) const +{ + ContextProcessor::affects( input, outputs ); + + if( variablesPlug()->isAncestorOf( input ) || input == extraVariablesPlug() ) + { + outputs.push_back( combinedVariablesPlug() ); + } +} + + bool ContextVariables::affectsContext( const Plug *input ) const { - return variablesPlug()->isAncestorOf( input ) || input == extraVariablesPlug(); + return input == combinedVariablesPlug(); +} + +void ContextVariables::hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const +{ + ContextProcessor::hash( output, context, h ); + + if( output == combinedVariablesPlug() ) + { + variablesPlug()->hash( h ); + extraVariablesPlug()->hash( h ); + } } -void ContextVariables::processContext( Context::EditableScope &context ) const +void ContextVariables::compute( ValuePlug *output, const Context *context ) const { - std::string name; - for( NameValuePlugIterator it( variablesPlug() ); !it.done(); ++it ) + if( output == combinedVariablesPlug() ) { - IECore::DataPtr data = variablesPlug()->memberDataAndName( it->get(), name ); - if( data ) + IECore::CompoundDataPtr resultData = new IECore::CompoundData( extraVariablesPlug()->getValue()->readable() ); + IECore::CompoundDataMap &result = resultData->writable(); + + std::string name; + for( NameValuePlug::Iterator it( variablesPlug() ); !it.done(); ++it ) { - context.set( name, data.get() ); + IECore::DataPtr data = variablesPlug()->memberDataAndName( it->get(), name ); + if( data ) + { + result.insert( { IECore::InternedString( name ), data } ); + } } + static_cast( output )->setValue( resultData ); + return; } - IECore::ConstCompoundDataPtr extraVariablesData = extraVariablesPlug()->getValue(); - const IECore::CompoundDataMap &extraVariables = extraVariablesData->readable(); - for( IECore::CompoundDataMap::const_iterator it = extraVariables.begin(), eIt = extraVariables.end(); it != eIt; ++it ) + + return ContextProcessor::compute( output, context ); +} + + +void ContextVariables::processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const +{ + SetFromReadable setFromReadable; + IECore::ConstCompoundDataPtr combinedVariables = combinedVariablesPlug()->getValue(); + for( const auto &variable : combinedVariables->readable() ) { - context.set( it->first, it->second.get() ); + IECore::dispatch( variable.second.get(), setFromReadable, context, variable.first ); } + storage = combinedVariables; } diff --git a/src/Gaffer/DeleteContextVariables.cpp b/src/Gaffer/DeleteContextVariables.cpp index 9ae32691c47..4243a485dc4 100644 --- a/src/Gaffer/DeleteContextVariables.cpp +++ b/src/Gaffer/DeleteContextVariables.cpp @@ -72,7 +72,7 @@ bool DeleteContextVariables::affectsContext( const Plug *input ) const return input == variablesPlug(); } -void DeleteContextVariables::processContext( Context::EditableScope &context ) const +void DeleteContextVariables::processContext( Context::EditableScope &context, IECore::ConstRefCountedPtr &storage ) const { context.removeMatching( variablesPlug()->getValue() ); } diff --git a/src/Gaffer/Expression.cpp b/src/Gaffer/Expression.cpp index e0a1c670aca..f943ceeea0e 100644 --- a/src/Gaffer/Expression.cpp +++ b/src/Gaffer/Expression.cpp @@ -280,7 +280,7 @@ void Expression::affects( const Plug *input, AffectedPlugsContainer &outputs ) c } else if( input == executePlug() ) { - for( RecursiveValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::RecursiveIterator it( outPlug() ); !it.done(); ++it ) { if( !(*it)->children().size() ) { @@ -298,7 +298,7 @@ void Expression::hash( const ValuePlug *output, const Context *context, IECore:: { enginePlug()->hash( h ); expressionPlug()->hash( h ); - for( ValuePlugIterator it( inPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( inPlug() ); !it.done(); ++it ) { (*it)->hash( h ); // We must hash the types of the input plugs, because @@ -306,7 +306,7 @@ void Expression::hash( const ValuePlug *output, const Context *context, IECore:: // types may yield a different result from Engine::execute(). h.append( (*it)->typeId() ); } - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { // We also need to hash the types of the output plugs, // because an identical expression with different output @@ -316,15 +316,7 @@ void Expression::hash( const ValuePlug *output, const Context *context, IECore:: for( std::vector::const_iterator it = m_contextNames.begin(); it != m_contextNames.end(); it++ ) { - const IECore::Data *d = context->get( *it, nullptr ); - if( d ) - { - d->hash( h ); - } - else - { - h.append( 0 ); - } + h.append( context->variableHash( *it ) ); } } else if( outPlug()->isAncestorOf( output ) ) @@ -336,6 +328,23 @@ void Expression::hash( const ValuePlug *output, const Context *context, IECore:: } } +Gaffer::ValuePlug::CachePolicy Expression::computeCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( output == executePlug() ) + { + if( m_engine ) + { + return m_engine->executeCachePolicy(); + } + else + { + return ValuePlug::CachePolicy::Legacy; + } + } + return ComputeNode::computeCachePolicy( output ); +} + + void Expression::compute( ValuePlug *output, const Context *context ) const { if( output == executePlug() ) @@ -343,7 +352,7 @@ void Expression::compute( ValuePlug *output, const Context *context ) const if( m_engine ) { std::vector inputs; - for( ValuePlugIterator it( inPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( inPlug() ); !it.done(); ++it ) { inputs.push_back( it->get() ); } @@ -376,7 +385,7 @@ void Expression::compute( ValuePlug *output, const Context *context ) const { ConstObjectVectorPtr values = executePlug()->getValue(); size_t index = 0; - for( ValuePlugIterator it( outPlug() ); !it.done() && *it != outPlugChild; ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done() && *it != outPlugChild; ++it ) { index++; } @@ -465,13 +474,13 @@ std::string Expression::transcribe( const std::string &expression, bool toIntern } std::vector internalPlugs, externalPlugs; - for( ValuePlugIterator it( inPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( inPlug() ); !it.done(); ++it ) { internalPlugs.push_back( it->get() ); externalPlugs.push_back( (*it)->getInput() ); } - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { internalPlugs.push_back( it->get() ); if( !(*it)->outputs().empty() ) diff --git a/src/Gaffer/GraphComponent.cpp b/src/Gaffer/GraphComponent.cpp index 2c9b3e405dd..655a22bf5ed 100644 --- a/src/Gaffer/GraphComponent.cpp +++ b/src/Gaffer/GraphComponent.cpp @@ -49,6 +49,7 @@ #include "boost/regex.hpp" #include +#include using namespace Gaffer; using namespace IECore; @@ -128,6 +129,7 @@ struct GraphComponent::Signals : boost::noncopyable BinarySignal childAddedSignal; BinarySignal childRemovedSignal; BinarySignal parentChangedSignal; + ChildrenReorderedSignal childrenReorderedSignal; // Utility to emit a signal if it has been created, but do nothing // if it hasn't. @@ -431,16 +433,6 @@ void GraphComponent::removeChild( GraphComponentPtr child ) } } -void GraphComponent::clearChildren() -{ - // because our storage is a vector, it's a good bit quicker to remove - // from the back to the front. - for( int i = (int)(m_children.size()) - 1; i >= 0; --i ) - { - removeChild( m_children[i] ); - } -} - void GraphComponent::removeChildInternal( GraphComponentPtr child, bool emitParentChanged ) { if( emitParentChanged ) @@ -481,6 +473,101 @@ const GraphComponent::ChildContainer &GraphComponent::children() const return m_children; } +void GraphComponent::reorderChildren( const ChildContainer &newOrder ) +{ + if( newOrder.size() != m_children.size() ) + { + throw IECore::InvalidArgumentException( + boost::str( boost::format( "Wrong number of children specified (%1% but should be %2%)" ) % newOrder.size() % m_children.size() ) + ); + } + + // Build map from child to index, so we can quickly look up the + // index for a particular child. + + unordered_map indexMap; + for( size_t index = 0; index < m_children.size(); ++index ) + { + indexMap[m_children[index].get()] = index; + } + + // Build list of indices corresponding to `newOrder`, validating + // `newOrder` as we go. + + auto indices = std::make_shared>(); + indices->reserve( m_children.size() ); + for( const auto &child : newOrder ) + { + if( child->parent() != this ) + { + throw IECore::InvalidArgumentException( + boost::str( boost::format( "\"%1%\" is not a child of \"%2%\"" ) % child->fullName() % fullName() ) + ); + } + + auto it = indexMap.find( child.get() ); + if( it == indexMap.end() ) + { + // We removed it from the map already + throw IECore::InvalidArgumentException( + boost::str( boost::format( "Child \"%1%\" is in more than one position" ) % child->getName() ) + ); + } + indices->push_back( it->second ); + indexMap.erase( it ); + } + + assert( indexMap.empty() ); + + /// \todo For most likely reordering operations, indices will consist + /// mostly of sections where each index is 1 greater than the previous. + /// This could be compressed with a form of run-length encoding to limit + /// the amount of data we store in the undo queue. + + // Add an action to do the work. + + Action::enact( + this, + // Do + [this, indices] () { + ChildContainer children; + children.reserve( indices->size() ); + for( auto i : *indices ) + { + children.push_back( m_children[i] ); + } + m_children = children; + childrenReordered( *indices ); + Signals::emitLazily( m_signals.get(), &Signals::childrenReorderedSignal, this, *indices ); + }, + // Undo + [this, indices] () { + ChildContainer children; + children.resize( indices->size() ); + vector signalIndices; + signalIndices.resize( indices->size() ); + for( size_t i = 0; i < indices->size(); ++i ) + { + children[(*indices)[i]] = m_children[i]; + signalIndices[(*indices)[i]] = i; + } + m_children = children; + childrenReordered( signalIndices ); + Signals::emitLazily( m_signals.get(), &Signals::childrenReorderedSignal, this, signalIndices ); + } + ); +} + +void GraphComponent::clearChildren() +{ + // because our storage is a vector, it's a good bit quicker to remove + // from the back to the front. + for( int i = (int)(m_children.size()) - 1; i >= 0; --i ) + { + removeChild( m_children[i] ); + } +} + GraphComponent *GraphComponent::ancestor( IECore::TypeId type ) { GraphComponent *a = m_parent; @@ -563,6 +650,11 @@ GraphComponent::BinarySignal &GraphComponent::parentChangedSignal() return signals()->parentChangedSignal; } +GraphComponent::ChildrenReorderedSignal &GraphComponent::childrenReorderedSignal() +{ + return signals()->childrenReorderedSignal; +} + void GraphComponent::parentChanging( Gaffer::GraphComponent *newParent ) { } @@ -571,6 +663,10 @@ void GraphComponent::parentChanged( Gaffer::GraphComponent *oldParent ) { } +void GraphComponent::childrenReordered( const std::vector &oldIndices ) +{ +} + void GraphComponent::storeIndexOfNextChild( size_t &index ) const { if( index ) diff --git a/src/Gaffer/Loop.cpp b/src/Gaffer/Loop.cpp index cfa839415aa..14879e7f5ac 100644 --- a/src/Gaffer/Loop.cpp +++ b/src/Gaffer/Loop.cpp @@ -197,7 +197,7 @@ void Loop::hash( const ValuePlug *output, const Context *context, IECore::Murmur Context::EditableScope tmpContext( context ); if( index >= 0 ) { - tmpContext.set( indexVariable, index ); + tmpContext.set( indexVariable, &index ); } else { @@ -219,7 +219,7 @@ void Loop::compute( ValuePlug *output, const Context *context ) const Context::EditableScope tmpContext( context ); if( index >= 0 ) { - tmpContext.set( indexVariable, index ); + tmpContext.set( indexVariable, &index ); } else { @@ -287,7 +287,7 @@ bool Loop::setupPlugs() // hash()/compute() because each iteration changes the context and we bottom // out after the specified number of iterations. previousPlug()->setFlags( Plug::AcceptsDependencyCycles, true ); - for( Gaffer::RecursivePlugIterator it( previousPlug() ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveIterator it( previousPlug() ); !it.done(); ++it ) { (*it)->setFlags( Plug::AcceptsDependencyCycles, true ); } @@ -299,7 +299,7 @@ void Loop::addAffectedPlug( const ValuePlug *output, DependencyNode::AffectedPlu { if( output->children().size() ) { - for( RecursiveOutputPlugIterator it( output ); !it.done(); ++it ) + for( Plug::RecursiveOutputIterator it( output ); !it.done(); ++it ) { if( !(*it)->children().size() ) { diff --git a/src/Gaffer/Metadata.cpp b/src/Gaffer/Metadata.cpp index 07356ffeefa..6b3e3ab8798 100644 --- a/src/Gaffer/Metadata.cpp +++ b/src/Gaffer/Metadata.cpp @@ -82,8 +82,8 @@ namespace struct Signals { - Metadata::NodeValueChangedSignal2 nodeSignal; - Metadata::PlugValueChangedSignal2 plugSignal; + Metadata::NodeValueChangedSignal nodeSignal; + Metadata::PlugValueChangedSignal plugSignal; }; using SignalsMap = std::unordered_map>; @@ -112,7 +112,7 @@ Signals *nodeSignals( Node *node, bool createIfMissing ) { return nullptr; } - it = m.emplace( node, new Signals ).first; + it = m.emplace( node, std::make_unique() ).first; } return it->second.get(); } @@ -150,7 +150,7 @@ void emitValueChangedSignals( IECore::TypeId typeId, IECore::InternedString key, } } -void emitMatchingPlugValueChangedSignals( Metadata::PlugValueChangedSignal2 &signal, Plug *plug, const vector &path, const StringAlgo::MatchPatternPath &matchPath, IECore::InternedString key, Metadata::ValueChangedReason reason ) +void emitMatchingPlugValueChangedSignals( Metadata::PlugValueChangedSignal &signal, Plug *plug, const vector &path, const StringAlgo::MatchPatternPath &matchPath, IECore::InternedString key, Metadata::ValueChangedReason reason ) { /// \todo There is scope for pruning the recursion here early if we /// reproduce the logic of StringAlgo::match ourselves. We don't @@ -612,7 +612,7 @@ std::vector Metadata::nodesWithMetadata( GraphComponent *root, IECore::In } else { - for( RecursiveNodeIterator it( root ); !it.done(); ++it ) + for( Node::RecursiveIterator it( root ); !it.done(); ++it ) { if( valueInternal( it->get(), key, instanceOnly ) ) { @@ -840,25 +840,25 @@ Metadata::ValueChangedSignal &Metadata::valueChangedSignal() return *s; } -Metadata::NodeValueChangedSignal2 &Metadata::nodeValueChangedSignal( Node *node ) +Metadata::NodeValueChangedSignal &Metadata::nodeValueChangedSignal( Node *node ) { return nodeSignals( node, /* createIfMissing = */ true )->nodeSignal; } -Metadata::PlugValueChangedSignal2 &Metadata::plugValueChangedSignal( Node *node ) +Metadata::PlugValueChangedSignal &Metadata::plugValueChangedSignal( Node *node ) { return nodeSignals( node, /* createIfMissing = */ true )->plugSignal; } -Metadata::NodeValueChangedSignal &Metadata::nodeValueChangedSignal() +Metadata::LegacyNodeValueChangedSignal &Metadata::nodeValueChangedSignal() { - static NodeValueChangedSignal *s = new NodeValueChangedSignal; + static LegacyNodeValueChangedSignal *s = new LegacyNodeValueChangedSignal; return *s; } -Metadata::PlugValueChangedSignal &Metadata::plugValueChangedSignal() +Metadata::LegacyPlugValueChangedSignal &Metadata::plugValueChangedSignal() { - static PlugValueChangedSignal *s = new PlugValueChangedSignal; + static LegacyPlugValueChangedSignal *s = new LegacyPlugValueChangedSignal; return *s; } diff --git a/src/Gaffer/MetadataAlgo.cpp b/src/Gaffer/MetadataAlgo.cpp index 93d9a67d921..a7e7545dff4 100644 --- a/src/Gaffer/MetadataAlgo.cpp +++ b/src/Gaffer/MetadataAlgo.cpp @@ -45,7 +45,8 @@ #include "IECore/SimpleTypedData.h" -#include +#include "boost/algorithm/string/predicate.hpp" +#include "boost/regex.hpp" using namespace std; using namespace IECore; @@ -59,6 +60,8 @@ InternedString g_bookmarkedName( "bookmarked" ); InternedString g_numericBookmarkBaseName( "numericBookmark" ); IECore::InternedString g_connectionColorKey( "connectionGadget:color" ); IECore::InternedString g_noduleColorKey( "nodule:color" ); +const std::string g_annotationPrefix( "annotation:" ); +const InternedString g_annotations( "annotations" ); void copy( const Gaffer::GraphComponent *src , Gaffer::GraphComponent *dst , IECore::InternedString key , bool overwrite ) { @@ -248,7 +251,7 @@ void bookmarks( const Node *node, std::vector &bookmarks ) { bookmarks.clear(); - for( NodeIterator it( node ); !it.done(); ++it ) + for( Node::Iterator it( node ); !it.done(); ++it ) { if( getBookmarked( it->get() ) ) { @@ -324,6 +327,181 @@ bool numericBookmarkAffectedByChange( const IECore::InternedString &changedKey ) return boost::regex_match( changedKey.string(), expr ); } +// Annotations +// =========== + +Imath::Color3f Annotation::g_defaultColor( 0.05 ); +std::string Annotation::g_defaultText; + +Annotation::Annotation( const std::string &text ) + : textData( new StringData( text ) ), colorData( nullptr ) +{ +} + +Annotation::Annotation( const std::string &text, const Imath::Color3f &color ) + : textData( new StringData( text ) ), colorData( new Color3fData( color ) ) +{ +} + +Annotation::Annotation( const IECore::ConstStringDataPtr &text, const IECore::ConstColor3fDataPtr &color ) + : textData( text ), colorData( color ) +{ +} + +bool Annotation::operator == ( const Annotation &rhs ) +{ + auto dataEqual = [] ( const Data *a, const Data *b ) { + if( a ) + { + return b && b->isEqualTo( a ); + } + else + { + return !b; + } + }; + + return + dataEqual( textData.get(), rhs.textData.get() ) && + dataEqual( colorData.get(), rhs.colorData.get() ) + ; +} + +void addAnnotation( Node *node, const std::string &name, const Annotation &annotation, bool persistent ) +{ + const string prefix = g_annotationPrefix + name + ":"; + if( annotation.textData ) + { + Metadata::registerValue( node, prefix + "text", annotation.textData, persistent ); + } + else + { + throw IECore::Exception( "Annotation must have text" ); + } + + if( annotation.colorData ) + { + Metadata::registerValue( node, prefix + "color", annotation.colorData, persistent ); + } + else + { + Metadata::deregisterValue( node, prefix + "color" ); + } +} + +Annotation getAnnotation( const Node *node, const std::string &name, bool inheritTemplate ) +{ + const string prefix = g_annotationPrefix + name + ":"; + auto text = Metadata::value( node, prefix + "text" ); + if( !text ) + { + return Annotation(); + } + + Annotation result( text, Metadata::value( node, prefix + "color" ) ); + if( !result.colorData && inheritTemplate ) + { + result.colorData = Metadata::value( g_annotations, name + ":color" ); + } + return result; +} + +void removeAnnotation( Node *node, const std::string &name ) +{ + const string prefix = g_annotationPrefix + name + ":"; + vector keys; + Metadata::registeredValues( node, keys ); + + for( const auto &key : keys ) + { + if( boost::starts_with( key.string(), prefix ) ) + { + Metadata::deregisterValue( node, key ); + } + } +} + +void annotations( const Node *node, std::vector &names ) +{ + vector keys; + Metadata::registeredValues( node, keys ); + + for( const auto &key : keys ) + { + if( boost::starts_with( key.string(), g_annotationPrefix ) && boost::ends_with( key.string(), ":text" ) ) + { + names.push_back( key.string().substr( g_annotationPrefix.size(), key.string().size() - g_annotationPrefix.size() - 5 ) ); + } + } +} + +void addAnnotationTemplate( const std::string &name, const Annotation &annotation, bool user ) +{ + if( annotation.textData ) + { + Metadata::registerValue( g_annotations, name + ":text", annotation.textData ); + } + else + { + Metadata::deregisterValue( g_annotations, name + ":text" ); + } + + if( annotation.colorData ) + { + Metadata::registerValue( g_annotations, name + ":color", annotation.colorData ); + } + else + { + Metadata::deregisterValue( g_annotations, name + ":color" ); + } + + Metadata::registerValue( g_annotations, name + ":user", new BoolData( user ) ); +} + +Annotation getAnnotationTemplate( const std::string &name ) +{ + return Annotation( + Metadata::value( g_annotations, name + ":text" ), + Metadata::value( g_annotations, name + ":color" ) + ); +} + +void removeAnnotationTemplate( const std::string &name ) +{ + Metadata::deregisterValue( g_annotations, name + ":text" ); + Metadata::deregisterValue( g_annotations, name + ":color" ); +} + +void annotationTemplates( std::vector &names, bool userOnly ) +{ + vector keys; + Metadata::registeredValues( g_annotations, keys ); + for( const auto &key : keys ) + { + if( boost::ends_with( key.string(), ":text" ) ) + { + const string name = key.string().substr( 0, key.string().size() - 5 ); + if( userOnly ) + { + auto user = Metadata::value( g_annotations, name + ":user" ); + if( !user || !user->readable() ) + { + continue; + } + } + names.push_back( name ); + } + } +} + +bool annotationsAffectedByChange( const IECore::InternedString &changedKey ) +{ + return boost::starts_with( changedKey.c_str(), g_annotationPrefix ); +} + +// Change queries +// ============== + bool affectedByChange( const Plug *plug, IECore::TypeId changedTypeId, const IECore::StringAlgo::MatchPattern &changedPlugPath, const Gaffer::Plug *changedPlug ) { if( changedPlug ) @@ -404,7 +582,7 @@ bool childAffectedByChange( const GraphComponent *parent, IECore::TypeId changed return parent == changedNode->parent(); } - for( NodeIterator it( parent ); !it.done(); ++it ) + for( Node::Iterator it( parent ); !it.done(); ++it ) { if( (*it)->isInstanceOf( changedNodeTypeId ) ) { @@ -476,6 +654,9 @@ bool affectedByChange( const Node *node, IECore::TypeId changedNodeTypeId, const return node->isInstanceOf( changedNodeTypeId ); } +// Copying +// ======= + void copy( const GraphComponent *from, GraphComponent *to, bool persistent ) { copyIf( diff --git a/src/Gaffer/MonitorAlgo.cpp b/src/Gaffer/MonitorAlgo.cpp index ae7b9931a2d..da0b6fa14de 100644 --- a/src/Gaffer/MonitorAlgo.cpp +++ b/src/Gaffer/MonitorAlgo.cpp @@ -37,7 +37,7 @@ #include "Gaffer/MonitorAlgo.h" #include "Gaffer/ContextMonitor.h" -#include "Gaffer/Metadata.h" +#include "Gaffer/MetadataAlgo.h" #include "Gaffer/Node.h" #include "Gaffer/PerformanceMonitor.h" #include "Gaffer/Plug.h" @@ -87,7 +87,7 @@ struct HashCountMetric } const std::string description = "number of hash processes"; - const std::string annotation = "hashCount"; + const std::string annotation = "performanceMonitor:hashCount"; const std::string annotationPrefix = "Hash count : "; }; @@ -103,7 +103,7 @@ struct ComputeCountMetric } const std::string description = "number of compute processes"; - const std::string annotation = "computeCount"; + const std::string annotation = "performanceMonitor:computeCount"; const std::string annotationPrefix = "Compute count : "; }; @@ -119,7 +119,7 @@ struct HashDurationMetric } const std::string description = "time spent in hash processes"; - const std::string annotation = "hashDuration"; + const std::string annotation = "performanceMonitor:hashDuration"; const std::string annotationPrefix = "Hash time : "; }; @@ -135,7 +135,7 @@ struct ComputeDurationMetric } const std::string description = "time spent in compute processes"; - const std::string annotation = "computeDuration"; + const std::string annotation = "performanceMonitor:computeDuration"; const std::string annotationPrefix = "Compute time : "; }; @@ -151,7 +151,7 @@ struct TotalDurationMetric } const std::string description = "sum of time spent in hash and compute processes"; - const std::string annotation = "totalDuration"; + const std::string annotation = "performanceMonitor:totalDuration"; const std::string annotationPrefix = "Time : "; }; @@ -167,7 +167,7 @@ struct PerHashDurationMetric } const std::string description = "time spent per hash process"; - const std::string annotation = "perHashDuration"; + const std::string annotation = "performanceMonitor:perHashDuration"; const std::string annotationPrefix = "Time per hash : "; }; @@ -183,7 +183,7 @@ struct PerComputeDurationMetric } const std::string description = "time spent per compute process"; - const std::string annotation = "perComputeDuration"; + const std::string annotation = "performanceMonitor:perComputeDuration"; const std::string annotationPrefix = "Time per compute : "; }; @@ -199,14 +199,14 @@ struct HashesPerComputeMetric } const std::string description = "number of hash processes per compute process"; - const std::string annotation = "hashesPerCompute"; + const std::string annotation = "performanceMonitor:hashesPerCompute"; const std::string annotationPrefix = "Hashes per compute : "; }; // Utility for invoking a templated functor with a particular metric. template -typename F::ResultType dispatchMetric( const F &f, MonitorAlgo::PerformanceMetric performanceMetric ) +std::result_of_t dispatchMetric( const F &f, MonitorAlgo::PerformanceMetric performanceMetric ) { switch( performanceMetric ) { @@ -231,6 +231,39 @@ typename F::ResultType dispatchMetric( const F &f, MonitorAlgo::PerformanceMetri } } +const std::string g_contextAnnotationName = "contextMonitor"; + +struct AnnotationRegistrations +{ + AnnotationRegistrations() + { + for( int m = Gaffer::MonitorAlgo::First; m <= Gaffer::MonitorAlgo::Last; ++m ) + { + // We don't really need the template values, but by registering a + // template we get included nicely in the UI for filtering + // annotations in the GraphEditor. + dispatchMetric( + [] ( auto metric ) { + MetadataAlgo::addAnnotationTemplate( + metric.annotation, + MetadataAlgo::Annotation( "" ), + /* user = */ false + ); + }, + static_cast( m ) + ); + } + + MetadataAlgo::addAnnotationTemplate( + g_contextAnnotationName, + MetadataAlgo::Annotation( "" ), + /* user = */ false + ); + } +}; + +const AnnotationRegistrations g_annotationRegistrations; + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -376,18 +409,17 @@ double toDouble( const boost::chrono::duration &v ) } template -ConstColor3fDataPtr heat( const T &v, const T &m ) +Color3f heat( const T &v, const T &m ) { const double heatFactor = toDouble( v ) / toDouble( m ); - const Color3f heat = lerp( Color3f( 0 ), Color3f( 0.5, 0, 0 ), heatFactor ); - return new Color3fData( heat ); + return lerp( Color3f( 0 ), Color3f( 0.5, 0, 0 ), heatFactor ); } struct Annotate { - Annotate( Node &root, const PerformanceMonitor::StatisticsMap &statistics ) - : m_root( root ), m_statistics( statistics ) + Annotate( Node &root, const PerformanceMonitor::StatisticsMap &statistics, bool persistent ) + : m_root( root ), m_statistics( statistics ), m_persistent( persistent ) { } @@ -396,20 +428,17 @@ struct Annotate template ResultType operator() ( const Metric &metric ) const { - walk( - m_root, metric, - "annotation:performanceMonitor:" + metric.annotation + ":text", - "annotation:performanceMonitor:" + metric.annotation + ":color" - ); + walk( m_root, metric ); } private : Node &m_root; const PerformanceMonitor::StatisticsMap &m_statistics; + const bool m_persistent; template - PerformanceMonitor::Statistics walk( Node &node, const Metric &metric, const InternedString &textKey, const InternedString &colorKey ) const + PerformanceMonitor::Statistics walk( Node &node, const Metric &metric ) const { using Value = typename Metric::ResultType; using ChildStatistics = std::pair; @@ -417,7 +446,7 @@ struct Annotate // Accumulate the statistics for all plugs belonging to this node. PerformanceMonitor::Statistics result; - for( RecursivePlugIterator plugIt( &node ); !plugIt.done(); ++plugIt ) + for( Plug::RecursiveIterator plugIt( &node ); !plugIt.done(); ++plugIt ) { auto it = m_statistics.find( plugIt->get() ); if( it != m_statistics.end() ) @@ -431,10 +460,10 @@ struct Annotate std::vector childStatistics; Value maxChildValue( 0 ); - for( NodeIterator childNodeIt( &node ); !childNodeIt.done(); ++childNodeIt ) + for( Node::Iterator childNodeIt( &node ); !childNodeIt.done(); ++childNodeIt ) { Node &childNode = **childNodeIt; - const auto cs = walk( childNode, metric, textKey, colorKey ); + const auto cs = walk( childNode, metric ); childStatistics.push_back( ChildStatistics( childNode, cs ) ); maxChildValue = std::max( maxChildValue, metric( cs ) ); } @@ -451,13 +480,15 @@ struct Annotate continue; } - Metadata::registerValue( - &cs.first, textKey, - new StringData( - metric.annotationPrefix + boost::lexical_cast( value ) - ) + MetadataAlgo::addAnnotation( + &cs.first, + metric.annotation, + MetadataAlgo::Annotation( + metric.annotationPrefix + boost::lexical_cast( value ), + heat( value, maxChildValue ) + ), + m_persistent ); - Metadata::registerValue( &cs.first, colorKey, heat( value, maxChildValue ) ); result += cs.second; } @@ -467,10 +498,7 @@ struct Annotate }; -InternedString g_contextAnnotationTextKey = "annotation:contextMonitor:text"; -InternedString g_contextAnnotationColorKey = "annotation:contextMonitor:color"; - -ContextMonitor::Statistics annotateContextWalk( Node &node, const ContextMonitor::StatisticsMap &statistics ) +ContextMonitor::Statistics annotateContextWalk( Node &node, const ContextMonitor::StatisticsMap &statistics, bool persistent ) { using ChildStatistics = std::pair; @@ -478,7 +506,7 @@ ContextMonitor::Statistics annotateContextWalk( Node &node, const ContextMonitor // Accumulate the statistics for all plugs belonging to this node. ContextMonitor::Statistics result; - for( RecursivePlugIterator plugIt( &node ); !plugIt.done(); ++plugIt ) + for( Plug::RecursiveIterator plugIt( &node ); !plugIt.done(); ++plugIt ) { auto it = statistics.find( plugIt->get() ); if( it != statistics.end() ) @@ -492,10 +520,10 @@ ContextMonitor::Statistics annotateContextWalk( Node &node, const ContextMonitor std::vector childStatistics; size_t maxUniqueContexts( 0 ); - for( NodeIterator childNodeIt( &node ); !childNodeIt.done(); ++childNodeIt ) + for( Node::Iterator childNodeIt( &node ); !childNodeIt.done(); ++childNodeIt ) { Node &childNode = **childNodeIt; - const auto cs = annotateContextWalk( childNode, statistics ); + const auto cs = annotateContextWalk( childNode, statistics, persistent ); childStatistics.push_back( ChildStatistics( childNode, cs ) ); maxUniqueContexts = std::max( maxUniqueContexts, cs.numUniqueContexts() ); } @@ -523,11 +551,12 @@ ContextMonitor::Statistics annotateContextWalk( Node &node, const ContextMonitor } } - Metadata::registerValue( - &cs.first, g_contextAnnotationTextKey, - new StringData( text ) + MetadataAlgo::addAnnotation( + &cs.first, + g_contextAnnotationName, + MetadataAlgo::Annotation( text, heat( cs.second.numUniqueContexts(), maxUniqueContexts ) ), + persistent ); - Metadata::registerValue( &cs.first, g_contextAnnotationColorKey, heat( cs.second.numUniqueContexts(), maxUniqueContexts ) ); result += cs.second; } @@ -586,22 +615,49 @@ std::string formatStatistics( const PerformanceMonitor &monitor, PerformanceMetr return dispatchMetric( FormatStatistics( monitor.allStatistics(), maxLines ), metric ); } -void annotate( Node &root, const PerformanceMonitor &monitor ) +void annotate( Node &root, const PerformanceMonitor &monitor, bool persistent ) { for( int m = First; m <= Last; ++m ) { - annotate( root, monitor, static_cast( m ) ); + annotate( root, monitor, static_cast( m ), persistent ); } } -void annotate( Node &root, const PerformanceMonitor &monitor, PerformanceMetric metric ) +void annotate( Node &root, const PerformanceMonitor &monitor, PerformanceMetric metric, bool persistent ) { - dispatchMetric( Annotate( root, monitor.allStatistics() ), metric ); + dispatchMetric( Annotate( root, monitor.allStatistics(), persistent ), metric ); } -void annotate( Node &root, const ContextMonitor &monitor ) +void annotate( Node &root, const ContextMonitor &monitor, bool persistent ) { - annotateContextWalk( root, monitor.allStatistics() ); + annotateContextWalk( root, monitor.allStatistics(), persistent ); +} + +void removePerformanceAnnotations( Node &root ) +{ + for( int m = Gaffer::MonitorAlgo::First; m <= Gaffer::MonitorAlgo::Last; ++m ) + { + dispatchMetric( + [&root] ( auto metric ) { + MetadataAlgo::removeAnnotation( &root, metric.annotation ); + }, + static_cast( m ) + ); + } + + for( const auto &node : Node::Range( root ) ) + { + removePerformanceAnnotations( *node ); + } +} + +void removeContextAnnotations( Node &root ) +{ + MetadataAlgo::removeAnnotation( &root, g_contextAnnotationName ); + for( const auto &node : Node::Range( root ) ) + { + removeContextAnnotations( *node ); + } } } // namespace MonitorAlgo diff --git a/src/Gaffer/NameSwitch.cpp b/src/Gaffer/NameSwitch.cpp index df7a85a8759..ad8893fef18 100644 --- a/src/Gaffer/NameSwitch.cpp +++ b/src/Gaffer/NameSwitch.cpp @@ -161,7 +161,8 @@ void NameSwitch::compute( ValuePlug *output, const Context *context ) const { continue; } - if( StringAlgo::matchMultiple( selector, p->namePlug()->getValue() ) ) + const string name = p->namePlug()->getValue(); + if( !name.empty() && StringAlgo::matchMultiple( selector, name ) ) { outIndex = i; break; @@ -174,4 +175,3 @@ void NameSwitch::compute( ValuePlug *output, const Context *context ) const Switch::compute( output, context ); } - diff --git a/src/Gaffer/Node.cpp b/src/Gaffer/Node.cpp index 8267d531d15..e85da6c48e5 100644 --- a/src/Gaffer/Node.cpp +++ b/src/Gaffer/Node.cpp @@ -69,11 +69,6 @@ Node::UnaryPlugSignal &Node::plugInputChangedSignal() return m_plugInputChangedSignal; } -Node::UnaryPlugSignal &Node::plugFlagsChangedSignal() -{ - return m_plugFlagsChangedSignal; -} - Node::UnaryPlugSignal &Node::plugDirtiedSignal() { return m_plugDirtiedSignal; @@ -148,7 +143,7 @@ void Node::parentChanging( Gaffer::GraphComponent *newParent ) // process to avoid such changes invalidating our // iterators. vector toDisconnect; - for( RecursivePlugIterator it( this ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( this ); !it.done(); ++it ) { if( Plug *input = (*it)->getInput() ) { diff --git a/src/Gaffer/NumericPlug.cpp b/src/Gaffer/NumericPlug.cpp index 938bea8016b..638435274ce 100644 --- a/src/Gaffer/NumericPlug.cpp +++ b/src/Gaffer/NumericPlug.cpp @@ -85,9 +85,11 @@ bool NumericPlug::acceptsInput( const Plug *input ) const } if( input ) { - return input->isInstanceOf( FloatPlug::staticTypeId() ) || - input->isInstanceOf( IntPlug::staticTypeId() ) || - input->isInstanceOf( BoolPlug::staticTypeId() ); + return + input->isInstanceOf( FloatPlug::staticTypeId() ) || + input->isInstanceOf( IntPlug::staticTypeId() ) || + input->isInstanceOf( BoolPlug::staticTypeId() ) + ; } return true; } diff --git a/src/Gaffer/ParallelAlgo.cpp b/src/Gaffer/ParallelAlgo.cpp index 14f3fda3e1a..040daf3536a 100644 --- a/src/Gaffer/ParallelAlgo.cpp +++ b/src/Gaffer/ParallelAlgo.cpp @@ -40,8 +40,6 @@ #include "Gaffer/Context.h" #include "Gaffer/Monitor.h" -#include "boost/make_unique.hpp" - #include #include @@ -102,7 +100,7 @@ GAFFER_API std::unique_ptr ParallelAlgo::callOnBackgroundThread( ContextPtr backgroundContext = new Context( *Context::current() ); Monitor::MonitorSet backgroundMonitors = Monitor::current(); - return boost::make_unique( + return std::make_unique( subject, diff --git a/src/Gaffer/Plug.cpp b/src/Gaffer/Plug.cpp index eb67cd86897..6ea4da76ee1 100644 --- a/src/Gaffer/Plug.cpp +++ b/src/Gaffer/Plug.cpp @@ -90,7 +90,7 @@ class ScopedAssignment : boost::noncopyable bool allDescendantInputsAreNull( const Plug *plug ) { - for( RecursivePlugIterator it( plug ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -119,7 +119,7 @@ Plug::~Plug() setInputInternal( nullptr, false ); for( OutputContainer::iterator it=m_outputs.begin(); it!=m_outputs.end(); ) { - // get the next iterator now, as the call to setInputInternal invalidates + // get the next iterator now, as the call to setInputInternal invalidates // the current iterator. OutputContainer::iterator next = it; next++; (*it)->setInputInternal( nullptr, true ); @@ -220,11 +220,6 @@ void Plug::setFlags( unsigned flags, bool enable ) void Plug::setFlagsInternal( unsigned flags ) { m_flags = flags; - - if( Node *n = node() ) - { - n->plugFlagsChangedSignal()( this ); - } } // The implementation of acceptsInputInternal() checks @@ -360,7 +355,7 @@ bool Plug::acceptsInputInternal( const Plug *input ) const { return false; } - for( PlugIterator it1( this ), it2( input ); !it1.done(); ++it1, ++it2 ) + for( Plug::Iterator it1( this ), it2( input ); !it1.done(); ++it1, ++it2 ) { if( !( *it1 )->acceptsInput( it2->get() ) ) { @@ -422,14 +417,14 @@ void Plug::setInput( PlugPtr input, bool setChildInputs, bool updateParentInput { if( !input ) { - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { (*it)->setInput( nullptr, /* setChildInputs = */ true, /* updateParentInput = */ false ); } } else { - for( PlugIterator it1( this ), it2( input.get() ); !it1.done(); ++it1, ++it2 ) + for( Plug::Iterator it1( this ), it2( input.get() ); !it1.done(); ++it1, ++it2 ) { (*it1)->setInput( *it2, /* setChildInputs = */ true, /* updateParentInput = */ false ); } @@ -554,7 +549,7 @@ void Plug::updateInputFromChildInputs( Plug *checkFirst ) return; } - for( PlugIterator it1( this ), it2( candidateInput ); !it1.done(); ++it1, ++it2 ) + for( Plug::Iterator it1( this ), it2( candidateInput ); !it1.done(); ++it1, ++it2 ) { if( (*it1)->getInput() != it2->get() ) { @@ -583,7 +578,7 @@ const Plug::OutputContainer &Plug::outputs() const PlugPtr Plug::createCounterpart( const std::string &name, Direction direction ) const { PlugPtr result = new Plug( name, direction, getFlags() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } @@ -696,6 +691,41 @@ void Plug::parentChanged( Gaffer::GraphComponent *oldParent ) popDirtyPropagationScope(); } +void Plug::childrenReordered( const std::vector &oldIndices ) +{ + // Reorder the children of our outputs to match our new order. We disable + // undo while we do this, because `childrenReordered()` will be called again + // when the original action is undone anyway. + UndoScope undoDisabler( ancestor(), UndoScope::Disabled ); + for( auto output : m_outputs ) + { + if( output->children().size() != oldIndices.size() ) + { + IECore::msg( + IECore::Msg::Warning, "Plug::childrenReordered", + boost::format( "Not reordering output \"%1%\" because its size doesn't match the input" ) % output->fullName() + ); + continue; + } + GraphComponent::ChildContainer children; children.reserve( oldIndices.size() ); + for( auto i : oldIndices ) + { + children.push_back( output->getChild( i ) ); + } + output->reorderChildren( children ); + } + + // Propagate dirtiness, because some nodes are sensitive + // to the ordering of plugs. + for( const auto &child : RecursiveRange( *this ) ) + { + if( child->children().empty() ) + { + propagateDirtiness( child.get() ); + } + } +} + void Plug::propagateDirtinessForParentChange( Plug *plugToDirty ) { // When a plug is reparented, we need to take into account @@ -703,7 +733,7 @@ void Plug::propagateDirtinessForParentChange( Plug *plugToDirty ) // find them, propagating dirtiness at the leaves. if( plugToDirty->children().size() ) { - for( PlugIterator it( plugToDirty ); !it.done(); ++it ) + for( Plug::Iterator it( plugToDirty ); !it.done(); ++it ) { propagateDirtinessForParentChange( it->get() ); } diff --git a/src/Gaffer/PlugAlgo.cpp b/src/Gaffer/PlugAlgo.cpp index 4f2f798b00b..a7070538864 100644 --- a/src/Gaffer/PlugAlgo.cpp +++ b/src/Gaffer/PlugAlgo.cpp @@ -84,7 +84,7 @@ void replacePlugWalk( Plug *existingPlug, Plug *plug, ConnectionsVector &connect if( plug->children().size() ) { // Recurse - for( PlugIterator it( plug ); !it.done(); ++it ) + for( Plug::Iterator it( plug ); !it.done(); ++it ) { if( Plug *existingChildPlug = existingPlug->getChild( (*it)->getName() ) ) { @@ -621,7 +621,7 @@ bool validatePromotability( const Plug *plug, const Plug *parent, bool throwExce } // Check all the children of this plug too - for( RecursivePlugIterator it( plug ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug ); !it.done(); ++it ) { if( !validatePromotability( it->get(), parent, throwExceptions, /* childPlug = */ true ) ) { @@ -657,7 +657,7 @@ void applyDynamicFlag( Plug *plug ) const Gaffer::TypeId *compoundTypesEnd = compoundTypes + 3; if( find( compoundTypes, compoundTypesEnd, (Gaffer::TypeId)plug->typeId() ) != compoundTypesEnd ) { - for( RecursivePlugIterator it( plug ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug ); !it.done(); ++it ) { (*it)->setFlags( Plug::Dynamic, true ); if( find( compoundTypes, compoundTypesEnd, (Gaffer::TypeId)(*it)->typeId() ) == compoundTypesEnd ) @@ -677,7 +677,7 @@ void setFrom( Plug *dst, const Plug *src ) } else { - for( PlugIterator it( dst ); !it.done(); ++it ) + for( Plug::Iterator it( dst ); !it.done(); ++it ) { Plug *dstChild = it->get(); const Plug *srcChild = src->getChild( dstChild->getName() ); @@ -861,7 +861,7 @@ void unpromote( Plug *plug ) while( plugToRemove->parent() && plugToRemove->parent() != externalNode->userPlug() ) { plugToRemove = plugToRemove->parent(); - for( PlugIterator it( plugToRemove ); !it.done(); ++it ) + for( Plug::Iterator it( plugToRemove ); !it.done(); ++it ) { if( ( (*it)->direction() == Plug::In && (*it)->outputs().size() ) || diff --git a/src/Gaffer/ProcessMessageHandler.cpp b/src/Gaffer/ProcessMessageHandler.cpp index 3c0d11da858..bcce6dad74f 100644 --- a/src/Gaffer/ProcessMessageHandler.cpp +++ b/src/Gaffer/ProcessMessageHandler.cpp @@ -80,15 +80,15 @@ void ProcessMessageHandler::handle( Level level, const string &context, const st ss << "[ plug: '" << p->plug()->fullName() << "'"; - if( auto frame = p->context()->get( g_frame, nullptr ) ) + if( const float *frame = p->context()->getIfExists( g_frame ) ) { - ss << ", frame: " << frame->readable(); + ss << ", frame: " << *frame; } - if( auto path = p->context()->get( g_scenePath, nullptr ) ) + if( auto path = p->context()->getIfExists< std::vector >( g_scenePath ) ) { std::string strPath = std::string("/") + join( - path->readable() | transformed( + *path | transformed( []( const IECore::InternedString &s ) { return s.string(); diff --git a/src/Gaffer/Random.cpp b/src/Gaffer/Random.cpp index 532f78fcaa4..2d1107819d2 100644 --- a/src/Gaffer/Random.cpp +++ b/src/Gaffer/Random.cpp @@ -173,7 +173,7 @@ void Random::affects( const Plug *input, AffectedPlugsContainer &outputs ) const if( input == seedPlug() || input == contextEntryPlug() ) { outputs.push_back( outFloatPlug() ); - for( ValuePlugIterator componentIt( outColorPlug() ); !componentIt.done(); ++componentIt ) + for( ValuePlug::Iterator componentIt( outColorPlug() ); !componentIt.done(); ++componentIt ) { outputs.push_back( componentIt->get() ); } @@ -189,7 +189,7 @@ void Random::affects( const Plug *input, AffectedPlugsContainer &outputs ) const input == valuePlug() ) { - for( ValuePlugIterator componentIt( outColorPlug() ); !componentIt.done(); ++componentIt ) + for( ValuePlug::Iterator componentIt( outColorPlug() ); !componentIt.done(); ++componentIt ) { outputs.push_back( componentIt->get() ); } @@ -277,18 +277,7 @@ void Random::hashSeed( const Context *context, IECore::MurmurHash &h ) const std::string contextEntry = contextEntryPlug()->getValue(); if( contextEntry.size() ) { - const IECore::Data *contextData = nullptr; - try - { - contextData = context->get( contextEntry ); - } - catch( ... ) - { - } - if( contextData ) - { - contextData->hash( h ); - } + h.append( context->variableHash( contextEntry ).h1() ); } } @@ -298,14 +287,13 @@ unsigned long int Random::computeSeed( const Context *context ) const std::string contextEntry = contextEntryPlug()->getValue(); if( contextEntry.size() ) { - const IECore::Data *contextData = nullptr; - try - { - contextData = context->get( contextEntry ); - } - catch( ... ) - { - } + // \todo: It is wasteful to call getAsData, allocating a fresh data here. + // We should be able to just use `seed += context->variableHash( contextEntry ).h1()`, + // however this would yield inconsistent hashes due to variableHash including the + // entry name as the address of an internal string. If we come up with a way to do + // fast consistent hashes of InternedString ( ie. the proposal of storing a hash in + // the InternedString table ) then we should switch this to the less wasteful version + IECore::DataPtr contextData = context->getAsData( contextEntry, nullptr ); if( contextData ) { IECore::MurmurHash hash = contextData->Object::hash(); diff --git a/src/Gaffer/Reference.cpp b/src/Gaffer/Reference.cpp index c82b91f64c8..9d1417db16b 100644 --- a/src/Gaffer/Reference.cpp +++ b/src/Gaffer/Reference.cpp @@ -134,7 +134,7 @@ void transferOutputs( Gaffer::Plug *srcPlug, Gaffer::Plug *dstPlug ) // Recurse - for( PlugIterator it( srcPlug ); !it.done(); ++it ) + for( Plug::Iterator it( srcPlug ); !it.done(); ++it ) { if( Plug *dstChildPlug = dstPlug->getChild( (*it)->getName() ) ) { @@ -378,7 +378,7 @@ class Reference::PlugEdits : public boost::signals::trackable // Recurse - for( PlugIterator it( srcPlug ); !it.done(); ++it ) + for( Plug::Iterator it( srcPlug ); !it.done(); ++it ) { if( Plug *dstChildPlug = dstPlug->getChild( (*it)->getName() ) ) { @@ -485,7 +485,7 @@ void Reference::loadInternal( const std::string &fileName ) // incoming plugs don't get renamed. std::map previousPlugs; - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { Plug *plug = it->get(); if( isReferencePlug( plug ) ) @@ -498,7 +498,7 @@ void Reference::loadInternal( const std::string &fileName ) // We don't export user plugs to references, but old versions of // Gaffer did, so as above, we must get them out of the way during // the load. - for( PlugIterator it( userPlug() ); !it.done(); ++it ) + for( Plug::Iterator it( userPlug() ); !it.done(); ++it ) { Plug *plug = it->get(); if( isReferencePlug( plug ) ) @@ -569,7 +569,7 @@ void Reference::loadInternal( const std::string &fileName ) continue; } - for( RecursivePlugIterator it( plug ); !it.done(); ++it ) + for( Plug::RecursiveIterator it( plug ); !it.done(); ++it ) { (*it)->setFlags( Plug::Dynamic, false ); } diff --git a/src/Gaffer/ScriptNode.cpp b/src/Gaffer/ScriptNode.cpp index 085a16a0de3..ae3fdec0dc7 100644 --- a/src/Gaffer/ScriptNode.cpp +++ b/src/Gaffer/ScriptNode.cpp @@ -622,7 +622,7 @@ void ScriptNode::deleteNodes( Node *parent, const Set *filter, bool reconnect ) DependencyNode *dependencyNode = IECore::runTimeCast( node ); if( reconnect && dependencyNode ) { - for( RecursiveOutputPlugIterator it( node ); !it.done(); ++it ) + for( Plug::RecursiveOutputIterator it( node ); !it.done(); ++it ) { Plug *inPlug = nullptr; try @@ -746,7 +746,7 @@ bool ScriptNode::importFile( const std::string &fileName, Node *parent, bool con bool result = script->load( continueOnError ); StandardSetPtr nodeSet = new StandardSet(); - nodeSet->add( NodeIterator( script.get() ), NodeIterator( script->children().end(), script->children().end() ) ); + nodeSet->add( Node::Iterator( script.get() ), Node::Iterator( script->children().end(), script->children().end() ) ); const std::string nodeSerialisation = script->serialise( script.get(), nodeSet.get() ); result |= execute( nodeSerialisation, parent, continueOnError ); @@ -797,6 +797,29 @@ const Context *ScriptNode::context() const return m_context.get(); } +void ScriptNode::updateContextVariables() +{ + // Get contents of `variablesPlug()` and remove any previously transferred + // variables that no longer exist. + IECore::CompoundDataMap values; + variablesPlug()->fillCompoundData( values ); + for( auto name : m_currentVariables ) + { + if( values.find( name ) == values.end() ) + { + context()->remove( name ); + } + } + + // Transfer current variables and remember what we've done. + m_currentVariables.clear(); + for( const auto &variable : values ) + { + context()->set( variable.first, variable.second.get() ); + m_currentVariables.insert( variable.first ); + } +} + void ScriptNode::plugSet( Plug *plug ) { if( plug == frameStartPlug() ) @@ -819,12 +842,7 @@ void ScriptNode::plugSet( Plug *plug ) } else if( plug == variablesPlug() ) { - IECore::CompoundDataMap values; - variablesPlug()->fillCompoundData( values ); - for( IECore::CompoundDataMap::const_iterator it = values.begin(), eIt = values.end(); it != eIt; ++it ) - { - context()->set( it->first, it->second.get() ); - } + updateContextVariables(); } else if( plug == fileNamePlug() ) { diff --git a/src/Gaffer/ShufflePlug.cpp b/src/Gaffer/ShufflePlug.cpp index b6dc2e44f51..48a2dcaf28f 100644 --- a/src/Gaffer/ShufflePlug.cpp +++ b/src/Gaffer/ShufflePlug.cpp @@ -193,10 +193,9 @@ bool ShufflesPlug::acceptsInput( const Plug *input ) const Gaffer::PlugPtr ShufflesPlug::createCounterpart( const std::string &name, Direction direction ) const { PlugPtr result = new ShufflesPlug( name, direction, getFlags() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } return result; } - diff --git a/src/Gaffer/Spreadsheet.cpp b/src/Gaffer/Spreadsheet.cpp index 0bc52e93716..272cc4d74e1 100644 --- a/src/Gaffer/Spreadsheet.cpp +++ b/src/Gaffer/Spreadsheet.cpp @@ -103,6 +103,10 @@ class RowsMap : public IECore::Data } const std::string name = row->namePlug()->getValue(); + if( name.empty() ) + { + continue; + } activeRowNames.push_back( name ); const bool hasWildcards = StringAlgo::hasWildcards( name ); @@ -279,9 +283,9 @@ class RowsMapScope : boost::noncopyable, public Context::SubstitutionProvider { // Special case for `scene:path`, which users will expect to use PathMatcher // style matching rather than `StringAlgo::match()`. - if( auto path = context->get( g_scenePath, nullptr ) ) + if( auto path = context->getIfExists< std::vector >( g_scenePath ) ) { - m_selector = &path->readable(); + m_selector = path; } else { @@ -1081,7 +1085,7 @@ void Spreadsheet::compute( ValuePlug *output, const Context *context ) const } const string name = rowPlug->namePlug()->getValue(); - if( result->members().count( name ) ) + if( name.empty() || result->members().count( name ) ) { continue; } diff --git a/src/Gaffer/StandardSet.cpp b/src/Gaffer/StandardSet.cpp index 77afd3e985d..3bbc9f715b5 100644 --- a/src/Gaffer/StandardSet.cpp +++ b/src/Gaffer/StandardSet.cpp @@ -193,4 +193,3 @@ void StandardSet::parentChanged( GraphComponent *member ) remove( member ); } } - diff --git a/src/Gaffer/Switch.cpp b/src/Gaffer/Switch.cpp index a7044ddc78d..17df8dbbfde 100644 --- a/src/Gaffer/Switch.cpp +++ b/src/Gaffer/Switch.cpp @@ -172,7 +172,7 @@ void Switch::affects( const Plug *input, DependencyNode::AffectedPlugsContainer { if( out->children().size() ) { - for( RecursiveOutputPlugIterator it( out ); !it.done(); ++it ) + for( Plug::RecursiveOutputIterator it( out ); !it.done(); ++it ) { if( !(*it)->children().size() ) { diff --git a/src/Gaffer/TimeWarp.cpp b/src/Gaffer/TimeWarp.cpp index 85a8a0eabfb..50d4fdb859c 100644 --- a/src/Gaffer/TimeWarp.cpp +++ b/src/Gaffer/TimeWarp.cpp @@ -82,7 +82,7 @@ bool TimeWarp::affectsContext( const Plug *input ) const return input == speedPlug() || input == offsetPlug(); } -void TimeWarp::processContext( Context::EditableScope &scope ) const +void TimeWarp::processContext( Context::EditableScope &scope, IECore::ConstRefCountedPtr &storage ) const { scope.setFrame( scope.context()->getFrame() * speedPlug()->getValue() + offsetPlug()->getValue() diff --git a/src/Gaffer/TypedPlug.cpp b/src/Gaffer/TypedPlug.cpp index df3ec41cc99..c6080c33050 100644 --- a/src/Gaffer/TypedPlug.cpp +++ b/src/Gaffer/TypedPlug.cpp @@ -62,10 +62,11 @@ bool BoolPlug::acceptsInput( const Plug *input ) const } if( input ) { - return input->isInstanceOf( staticTypeId() ) || - input->isInstanceOf( IntPlug::staticTypeId() ) || - input->isInstanceOf( FloatPlug::staticTypeId() ) || - input->isInstanceOf( StringPlug::staticTypeId() ) + return + input->isInstanceOf( staticTypeId() ) || + input->isInstanceOf( IntPlug::staticTypeId() ) || + input->isInstanceOf( FloatPlug::staticTypeId() ) || + input->isInstanceOf( StringPlug::staticTypeId() ) ; } return true; diff --git a/src/Gaffer/ValuePlug.cpp b/src/Gaffer/ValuePlug.cpp index 9f877ec865e..532468572a6 100644 --- a/src/Gaffer/ValuePlug.cpp +++ b/src/Gaffer/ValuePlug.cpp @@ -41,7 +41,6 @@ #include "Gaffer/ComputeNode.h" #include "Gaffer/Context.h" #include "Gaffer/Private/IECorePreview/LRUCache.h" -#include "Gaffer/Private/IECorePreview/ParallelAlgo.h" #include "Gaffer/Process.h" #include "IECore/MessageHandler.h" @@ -83,61 +82,6 @@ inline const ValuePlug *sourcePlug( const ValuePlug *p ) const IECore::MurmurHash g_nullHash; -#if TBB_INTERFACE_VERSION < 10003 - -// A bug in TBB means that `TaskMutex::execute( f )` cannot guarantee that `f` is -// called on the current thread (see TaskMutex for details). This means that -// `LRUCache::get()` may end up invoking the cache getter on a different thread, -// but only when `CachePolicy==TaskCollaboration`. We use this horrible little -// workaround to ensure that we transfer over the correct ThreadState into the -// getter. -class ThreadStateFixer -{ - - public : - - ThreadStateFixer( ValuePlug::CachePolicy cachePolicy ) - : m_threadState( cachePolicy == ValuePlug::CachePolicy::TaskCollaboration ? &ThreadState::current() : nullptr ) - { - } - - struct Scope : public ThreadState::Scope - { - Scope( const ThreadStateFixer &f ) - : ThreadState::Scope( *f.m_threadState ) - { - } - }; - - private : - - const ThreadState *m_threadState; - -}; - -#else - -// No-op version of ThreadStateFixer for use with modern TBB. All official -// Gaffer builds use this code path because GafferHQ/dependencies is following -// VFXPlatform reasonably closely. -struct ThreadStateFixer -{ - - ThreadStateFixer( ValuePlug::CachePolicy cachePolicy ) - { - } - - struct Scope - { - Scope( const ThreadStateFixer &f ) - { - } - }; - -}; - -#endif - // We only use the lower half of possible dirty count values. // The upper half is reserved for a debug "checked" mode where we compute // alternate values that are invalidated whenever any plug changes, in @@ -212,15 +156,13 @@ struct HashProcessKey : public HashCacheKey : HashCacheKey( plug, context, dirtyCount ), destinationPlug( destinationPlug ), computeNode( computeNode ), - cachePolicy( cachePolicy ), - threadStateFixer( cachePolicy ) + cachePolicy( cachePolicy ) { } const ValuePlug *destinationPlug; const ComputeNode *computeNode; const ValuePlug::CachePolicy cachePolicy; - const ThreadStateFixer threadStateFixer; }; // Avoids LRUCache overhead for non-collaborative policies. @@ -456,14 +398,13 @@ class ValuePlug::HashProcess : public Process { case CachePolicy::TaskCollaboration : { - ThreadStateFixer::Scope scope( key.threadStateFixer ); HashProcess process( key ); result = process.m_result; break; } case CachePolicy::TaskIsolation : { - IECorePreview::ParallelAlgo::isolate( + tbb::this_task_arena::isolate( [&result, &key] { HashProcess process( key ); result = process.m_result; @@ -552,7 +493,6 @@ struct ComputeProcessKey destinationPlug( destinationPlug ), computeNode( computeNode ), cachePolicy( cachePolicy ), - threadStateFixer( cachePolicy ), m_hash( precomputedHash ? *precomputedHash : IECore::MurmurHash() ) { } @@ -561,7 +501,6 @@ struct ComputeProcessKey const ValuePlug *destinationPlug; const ComputeNode *computeNode; const ValuePlug::CachePolicy cachePolicy; - const ThreadStateFixer threadStateFixer; operator const IECore::MurmurHash &() const { @@ -745,14 +684,13 @@ class ValuePlug::ComputeProcess : public Process } case CachePolicy::TaskCollaboration : { - ThreadStateFixer::Scope scope( key.threadStateFixer ); ComputeProcess process( key ); result = process.m_result; break; } case CachePolicy::TaskIsolation : { - IECorePreview::ParallelAlgo::isolate( + tbb::this_task_arena::isolate( [&result, &key] { ComputeProcess process( key ); result = process.m_result; @@ -945,7 +883,7 @@ void ValuePlug::setInput( PlugPtr input ) PlugPtr ValuePlug::createCounterpart( const std::string &name, Direction direction ) const { PlugPtr result = new ValuePlug( name, direction, getFlags() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } @@ -969,7 +907,7 @@ bool ValuePlug::settable() const { return false; } - for( ValuePlugIterator it( this ); !it.done(); ++it ) + for( ValuePlug::Iterator it( this ); !it.done(); ++it ) { if( !(*it)->settable() ) { @@ -1012,7 +950,7 @@ void ValuePlug::setToDefault() } else { - for( ValuePlugIterator it( this ); !it.done(); ++it ) + for( ValuePlug::Iterator it( this ); !it.done(); ++it ) { (*it)->setToDefault(); } @@ -1038,7 +976,7 @@ bool ValuePlug::isSetToDefault() const } else { - for( ValuePlugIterator it( this ); !it.done(); ++it ) + for( ValuePlug::Iterator it( this ); !it.done(); ++it ) { if( !(*it)->isSetToDefault() ) { @@ -1085,7 +1023,7 @@ IECore::MurmurHash ValuePlug::defaultHash() const else { IECore::MurmurHash h; - for( ValuePlugIterator it( this ); !it.done(); ++it ) + for( ValuePlug::Iterator it( this ); !it.done(); ++it ) { h.append( (*it)->defaultHash() ); } @@ -1101,7 +1039,7 @@ IECore::MurmurHash ValuePlug::hash() const // being used as a parent for other ValuePlugs. So // return the combined hashes of our children. IECore::MurmurHash result; - for( ValuePlugIterator it( this ); !it.done(); ++it ) + for( ValuePlug::Iterator it( this ); !it.done(); ++it ) { (*it)->hash( result ); } diff --git a/src/GafferAppleseed/AppleseedLight.cpp b/src/GafferAppleseed/AppleseedLight.cpp index e26ca1e58e5..9a6a1c2eba5 100644 --- a/src/GafferAppleseed/AppleseedLight.cpp +++ b/src/GafferAppleseed/AppleseedLight.cpp @@ -107,7 +107,7 @@ void AppleseedLight::loadShader( const std::string &shaderName ) void AppleseedLight::hashLight( const Gaffer::Context *context, IECore::MurmurHash &h ) const { - for( ValuePlugIterator it( parametersPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( parametersPlug() ); !it.done(); ++it ) { (*it)->hash( h ); } @@ -117,7 +117,7 @@ void AppleseedLight::hashLight( const Gaffer::Context *context, IECore::MurmurHa IECoreScene::ConstShaderNetworkPtr AppleseedLight::computeLight( const Gaffer::Context *context ) const { IECoreScene::ShaderPtr shader = new IECoreScene::Shader( modelPlug()->getValue(), "as:light" ); - for( InputValuePlugIterator it( parametersPlug() ); !it.done(); ++it ) + for( ValuePlug::InputIterator it( parametersPlug() ); !it.done(); ++it ) { shader->parameters()[(*it)->getName()] = PlugAlgo::extractDataFromPlug( it->get() ); } diff --git a/src/GafferAppleseed/AppleseedShaderAdaptor.cpp b/src/GafferAppleseed/AppleseedShaderAdaptor.cpp index 3cd9dba2b2d..4ad39e6372b 100644 --- a/src/GafferAppleseed/AppleseedShaderAdaptor.cpp +++ b/src/GafferAppleseed/AppleseedShaderAdaptor.cpp @@ -36,7 +36,6 @@ #include "GafferAppleseed/AppleseedShaderAdaptor.h" -#include "GafferScene/RendererAlgo.h" #include "GafferScene/SceneProcessor.h" #include "Gaffer/Context.h" diff --git a/src/GafferAppleseed/IECoreAppleseedPreview/Renderer.cpp b/src/GafferAppleseed/IECoreAppleseedPreview/Renderer.cpp index c17d52de4d4..ddf7e3d5330 100644 --- a/src/GafferAppleseed/IECoreAppleseedPreview/Renderer.cpp +++ b/src/GafferAppleseed/IECoreAppleseedPreview/Renderer.cpp @@ -93,8 +93,7 @@ #include "boost/thread.hpp" #include "tbb/atomic.h" -#include "tbb/concurrent_unordered_map.h" - +#include "tbb/concurrent_hash_map.h" namespace asf = foundation; namespace asr = renderer; @@ -2677,7 +2676,7 @@ class AppleseedRenderer final : public AppleseedRendererBase } } return; - } + } // general case. const IECore::Data *dataValue = IECore::runTimeCast( value ); diff --git a/src/GafferArnold/ArnoldAtmosphere.cpp b/src/GafferArnold/ArnoldAtmosphere.cpp index f7bc9358798..2a2028516c5 100644 --- a/src/GafferArnold/ArnoldAtmosphere.cpp +++ b/src/GafferArnold/ArnoldAtmosphere.cpp @@ -63,4 +63,3 @@ std::string ArnoldAtmosphere::computeOptionName( const Gaffer::Context *context { return "ai:atmosphere"; } - diff --git a/src/GafferArnold/ArnoldBackground.cpp b/src/GafferArnold/ArnoldBackground.cpp index 5f944343a7a..0b4fa036145 100644 --- a/src/GafferArnold/ArnoldBackground.cpp +++ b/src/GafferArnold/ArnoldBackground.cpp @@ -63,4 +63,3 @@ std::string ArnoldBackground::computeOptionName( const Gaffer::Context *context { return "ai:background"; } - diff --git a/src/GafferArnold/ArnoldMeshLight.cpp b/src/GafferArnold/ArnoldMeshLight.cpp index 54cc852d05c..bb511dcad81 100644 --- a/src/GafferArnold/ArnoldMeshLight.cpp +++ b/src/GafferArnold/ArnoldMeshLight.cpp @@ -67,7 +67,7 @@ ArnoldMeshLight::ArnoldMeshLight( const std::string &name ) ArnoldAttributesPtr attributes = new ArnoldAttributes( "__attributes" ); attributes->inPlug()->setInput( inPlug() ); attributes->filterPlug()->setInput( filterPlug() ); - for( NameValuePlugIterator it( attributes->attributesPlug() ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( attributes->attributesPlug() ); !it.done(); ++it ) { if( boost::ends_with( (*it)->getName().string(), "Visibility" ) && (*it)->getName() != "cameraVisibility" ) { @@ -91,7 +91,7 @@ ArnoldMeshLight::ArnoldMeshLight( const std::string &name ) PlugPtr parametersPlug = shader->parametersPlug()->createCounterpart( "parameters", Plug::In ); addChild( parametersPlug ); - for( PlugIterator srcIt( parametersPlug.get() ), dstIt( shader->parametersPlug() ); !srcIt.done(); ++srcIt, ++dstIt ) + for( Plug::Iterator srcIt( parametersPlug.get() ), dstIt( shader->parametersPlug() ); !srcIt.done(); ++srcIt, ++dstIt ) { (*dstIt)->setInput( *srcIt ); // We don't need the parameters to be dynamic, because we create the diff --git a/src/GafferArnold/IECoreArnoldPreview/ProceduralAlgo.cpp b/src/GafferArnold/IECoreArnoldPreview/ProceduralAlgo.cpp index f26cc45f20a..4ac7e05cef1 100644 --- a/src/GafferArnold/IECoreArnoldPreview/ProceduralAlgo.cpp +++ b/src/GafferArnold/IECoreArnoldPreview/ProceduralAlgo.cpp @@ -78,4 +78,3 @@ AtNode *convert( const IECoreScene::ExternalProcedural *procedural, const std::s } // namespace ProceduralAlgo } // namespace IECoreArnoldPreview - diff --git a/src/GafferArnold/IECoreArnoldPreview/Renderer.cpp b/src/GafferArnold/IECoreArnoldPreview/Renderer.cpp index 1fd4a63241d..bca5dd0b342 100644 --- a/src/GafferArnold/IECoreArnoldPreview/Renderer.cpp +++ b/src/GafferArnold/IECoreArnoldPreview/Renderer.cpp @@ -40,8 +40,6 @@ #include "GafferScene/Private/IECoreScenePreview/Procedural.h" -#include "Gaffer/Private/IECorePreview/ParallelAlgo.h" - #include "IECoreArnold/CameraAlgo.h" #include "IECoreArnold/NodeAlgo.h" #include "IECoreArnold/ParameterAlgo.h" @@ -206,29 +204,6 @@ std::string formatHeaderParameter( const std::string name, const IECore::Data *d } } -bool aiVersionLessThan( int arch, int major, int minor, int patch ) -{ - // The Arnold API has an `AiCheckAPIVersion()` function that sounds - // like exactly what we need, but it doesn't support comparing for - // patch versions. Instead we're forced to parse the version string - // ourselves. - - const char *arnoldVersionString = AiGetVersion( nullptr, nullptr, nullptr, nullptr ); - int arnoldVersion[4]; - for( int i = 0; i < 4; ++i ) - { - arnoldVersion[i] = strtol( arnoldVersionString, const_cast( &arnoldVersionString ), 10 ); - ++arnoldVersionString; - } - - auto version = { arch, major, minor, patch }; - - return std::lexicographical_compare( - begin( arnoldVersion ), end( arnoldVersion ), - version.begin(), version.end() - ); -} - void substituteShaderIfNecessary( IECoreScene::ConstShaderNetworkPtr &shaderNetwork, const IECore::CompoundObject *attributes ) { if( !shaderNetwork ) @@ -1739,6 +1714,32 @@ class Instance public : + AtNode *node() + { + return m_ginstance.get() ? m_ginstance.get() : m_node.get(); + } + + void nodesCreated( vector &nodes ) const + { + if( m_ginstance ) + { + nodes.push_back( m_ginstance.get() ); + } + else + { + // Technically the node was created in `InstanceCache.get()` + // rather than by us directly, but we are the sole owner and + // this is the most natural place to report the creation. + nodes.push_back( m_node.get() ); + } + } + + private : + + // Constructors are private as they are only intended for use in + // `InstanceCache::get()`. See comment in `nodesCreated()`. + friend class InstanceCache; + // Non-instanced Instance( const SharedAtNodePtr &node ) : m_node( node ) @@ -1760,21 +1761,6 @@ class Instance } } - AtNode *node() - { - return m_ginstance.get() ? m_ginstance.get() : m_node.get(); - } - - void nodesCreated( vector &nodes ) const - { - if( m_ginstance ) - { - nodes.push_back( m_ginstance.get() ); - } - } - - private : - SharedAtNodePtr m_node; SharedAtNodePtr m_ginstance; @@ -1798,7 +1784,7 @@ class InstanceCache : public IECore::RefCounted { const ArnoldAttributes *arnoldAttributes = static_cast( attributes ); - if( !canInstance( object, arnoldAttributes ) ) + if( !arnoldAttributes->canInstanceGeometry( object ) ) { return Instance( convert( object, arnoldAttributes, nodeName ) ); } @@ -1831,7 +1817,7 @@ class InstanceCache : public IECore::RefCounted { const ArnoldAttributes *arnoldAttributes = static_cast( attributes ); - if( !canInstance( samples.front(), arnoldAttributes ) ) + if( !arnoldAttributes->canInstanceGeometry( samples.front() ) ) { return Instance( convert( samples, times, arnoldAttributes, nodeName ) ); } @@ -1901,22 +1887,6 @@ class InstanceCache : public IECore::RefCounted private : - bool canInstance( const IECore::Object *object, const ArnoldAttributes *attributes ) const - { - if( IECore::runTimeCast( object ) && m_nodeDeleter == AiNodeDestroy ) - { - if( aiVersionLessThan( 5, 0, 1, 4 ) ) - { - // Work around Arnold bug whereby deleting an instanced procedural - // can lead to crashes. This unfortunately means that we don't get - // to do instancing of procedurals during interactive renders, but - // we can at least do it during batch renders. - return false; - } - } - return attributes->canInstanceGeometry( object ); - } - SharedAtNodePtr convert( const IECore::Object *object, const ArnoldAttributes *attributes, const std::string &nodeName ) { if( !object ) @@ -2724,7 +2694,7 @@ AtNode *convertProcedural( IECoreScenePreview::ConstProceduralPtr procedural, co AiNodeSetPtr( node, g_funcPtrArnoldString, (void *)procFunc ); ProceduralRendererPtr renderer = new ProceduralRenderer( node, attributes->allAttributes() ); - IECorePreview::ParallelAlgo::isolate( + tbb::this_task_arena::isolate( // Isolate in case procedural spawns TBB tasks, because // `convertProcedural()` is called behind a lock in // `InstanceCache.get()`. diff --git a/src/GafferArnold/ParameterHandler.cpp b/src/GafferArnold/ParameterHandler.cpp index 84b1995bb87..434b17aa95d 100644 --- a/src/GafferArnold/ParameterHandler.cpp +++ b/src/GafferArnold/ParameterHandler.cpp @@ -198,14 +198,12 @@ Gaffer::Plug *setupColorPlug( const AtNodeEntry *node, const AtParamEntry *param bool defaultOverridden = false; if( std::is_same< ValueType, Color4f >::value ) { - #if AI_VERSION_ARCH_NUM >= 6 || ( AI_VERSION_ARCH_NUM == 5 && AI_VERSION_MAJOR_NUM >= 3 ) AtRGBA metadataDefault; if( AiMetaDataGetRGBA( node, name, g_gafferDefaultArnoldString, &metadataDefault ) ) { memcpy( (void *)&defaultValue, &metadataDefault.r, sizeof( ValueType ) ); defaultOverridden = true; } - #endif } else { diff --git a/src/GafferArnoldModule/GafferArnoldModule.cpp b/src/GafferArnoldModule/GafferArnoldModule.cpp index 8366f1859c6..c17021a0c2c 100644 --- a/src/GafferArnoldModule/GafferArnoldModule.cpp +++ b/src/GafferArnoldModule/GafferArnoldModule.cpp @@ -73,7 +73,7 @@ void loadColorManagerWrapper( ArnoldColorManager &c, const std::string &name, bo class ArnoldColorManagerSerialiser : public GafferBindings::NodeSerialiser { - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const GafferBindings::Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, GafferBindings::Serialisation &serialisation ) const override { std::string result = GafferBindings::NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); diff --git a/src/GafferArnoldUI/ArnoldLightVisualiser.cpp b/src/GafferArnoldUI/ArnoldLightVisualiser.cpp index 90cb5d913a5..f5199b28b83 100644 --- a/src/GafferArnoldUI/ArnoldLightVisualiser.cpp +++ b/src/GafferArnoldUI/ArnoldLightVisualiser.cpp @@ -292,4 +292,3 @@ IECore::DataPtr ArnoldLightVisualiser::surfaceTexture( const IECoreScene::Shader } } - diff --git a/src/GafferArnoldUI/LightBlockerVisualiser.cpp b/src/GafferArnoldUI/LightBlockerVisualiser.cpp index e7eb2301375..d0c07b80a56 100644 --- a/src/GafferArnoldUI/LightBlockerVisualiser.cpp +++ b/src/GafferArnoldUI/LightBlockerVisualiser.cpp @@ -183,7 +183,7 @@ class LightBlockerVisualiser : public LightFilterVisualiser LightBlockerVisualiser(); ~LightBlockerVisualiser() override; - virtual Visualisations visualise( const IECore::InternedString &attributeName, const IECoreScene::ShaderNetwork *filterShaderNetwork, const IECoreScene::ShaderNetwork *lightShaderNetwork, const IECore::CompoundObject *attributes, IECoreGL::ConstStatePtr &state ) const override; + Visualisations visualise( const IECore::InternedString &attributeName, const IECoreScene::ShaderNetwork *filterShaderNetwork, const IECoreScene::ShaderNetwork *lightShaderNetwork, const IECore::CompoundObject *attributes, IECoreGL::ConstStatePtr &state ) const override; protected : diff --git a/src/GafferBindings/MetadataBinding.cpp b/src/GafferBindings/MetadataBinding.cpp index d9b1ac7df81..66ebbbaa02f 100644 --- a/src/GafferBindings/MetadataBinding.cpp +++ b/src/GafferBindings/MetadataBinding.cpp @@ -40,6 +40,7 @@ #include "GafferBindings/DataBinding.h" #include "GafferBindings/Serialisation.h" +#include "GafferBindings/ValuePlugBinding.h" #include "Gaffer/Metadata.h" #include "Gaffer/MetadataAlgo.h" @@ -62,16 +63,7 @@ using namespace GafferBindings; namespace GafferBindings { -void metadataModuleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules ) -{ - /// \todo Derive from the registered values so we can support - /// datatypes from other modules. - modules.insert( "imath" ); - modules.insert( "IECore" ); - modules.insert( "Gaffer" ); -} - -std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, const std::string &identifier ) +std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) { std::vector keys; Metadata::registeredValues( graphComponent, keys, /* instanceOnly = */ true, /* persistentOnly = */ true ); @@ -97,8 +89,8 @@ std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, ConstDataPtr value = Metadata::value( graphComponent, *it ); object pythonValue = dataToPython( value.get(), /* copy = */ false ); - object repr = boost::python::import( "IECore" ).attr( "repr" ); - std::string stringValue = extract( repr( pythonValue ) ); + /// \todo `valueRepr()` probably belongs somewhere more central. Maybe on Serialisation itself? + const std::string stringValue = ValuePlugSerialiser::valueRepr( pythonValue, &serialisation ); // \todo: To clean this up we might add a registerSerialisation( key, // functionReturningSerialiser ) method. Once there's a second use case @@ -123,6 +115,10 @@ std::string metadataSerialisation( const Gaffer::GraphComponent *graphComponent, } } + if( result.size() ) + { + serialisation.addModule( "Gaffer" ); + } return result; } diff --git a/src/GafferBindings/NodeBinding.cpp b/src/GafferBindings/NodeBinding.cpp index a7f8b806499..186158c5c08 100644 --- a/src/GafferBindings/NodeBinding.cpp +++ b/src/GafferBindings/NodeBinding.cpp @@ -51,13 +51,12 @@ using namespace GafferBindings; void NodeSerialiser::moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const { Serialiser::moduleDependencies( graphComponent, modules, serialisation ); - metadataModuleDependencies( static_cast( graphComponent ), modules ); } -std::string NodeSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string NodeSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { return Serialiser::postHierarchy( graphComponent, identifier, serialisation ) + - metadataSerialisation( static_cast( graphComponent ), identifier ); + metadataSerialisation( static_cast( graphComponent ), identifier, serialisation ); } bool NodeSerialiser::childNeedsSerialisation( const Gaffer::GraphComponent *child, const Serialisation &serialisation ) const diff --git a/src/GafferBindings/PlugBinding.cpp b/src/GafferBindings/PlugBinding.cpp index 1423ec062a6..3aa5fc2c5f5 100644 --- a/src/GafferBindings/PlugBinding.cpp +++ b/src/GafferBindings/PlugBinding.cpp @@ -157,15 +157,14 @@ const IECore::InternedString g_includeParentPlugMetadata( "plugSerialiser:includ void PlugSerialiser::moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const { Serialiser::moduleDependencies( graphComponent, modules, serialisation ); - metadataModuleDependencies( static_cast( graphComponent ), modules ); } -std::string PlugSerialiser::constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const +std::string PlugSerialiser::constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const { return repr( static_cast( graphComponent ) ); } -std::string PlugSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string PlugSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { const Plug *plug = static_cast( graphComponent ); @@ -187,7 +186,7 @@ std::string PlugSerialiser::postHierarchy( const Gaffer::GraphComponent *graphCo } if( shouldSerialiseMetadata ) { - result += metadataSerialisation( plug, identifier ); + result += metadataSerialisation( plug, identifier, serialisation ); } return result; @@ -298,4 +297,3 @@ std::string PlugSerialiser::repr( const Plug *plug, unsigned flagsMask ) return result; } - diff --git a/src/GafferBindings/Serialisation.cpp b/src/GafferBindings/Serialisation.cpp index e590b0c04e4..a90e52c6d08 100644 --- a/src/GafferBindings/Serialisation.cpp +++ b/src/GafferBindings/Serialisation.cpp @@ -60,6 +60,8 @@ #include "boost/python/suite/indexing/container_utils.hpp" #include "boost/tokenizer.hpp" +#include + using namespace IECore; using namespace Gaffer; using namespace GafferBindings; @@ -87,6 +89,52 @@ bool keyedByIndex( const GraphComponent *parent ) ; } +std::string modulePathInternal( const boost::python::object &o ) +{ + if( !PyObject_HasAttrString( o.ptr(), "__module__" ) ) + { + return ""; + } + std::string modulePath = extract( o.attr( "__module__" ) ); + std::string objectName; + if( PyType_Check( o.ptr() ) ) + { + objectName = extract( o.attr( "__name__" ) ); + } + else + { + objectName = extract( o.attr( "__class__" ).attr( "__name__" ) ); + } + + typedef boost::tokenizer > Tokenizer; + std::string sanitisedModulePath; + Tokenizer tokens( modulePath, boost::char_separator( "." ) ); + + for( Tokenizer::iterator tIt=tokens.begin(); tIt!=tokens.end(); tIt++ ) + { + if( tIt->compare( 0, 1, "_" )==0 ) + { + // assume that module path components starting with _ are bogus, and are used only to bring + // binary components into a namespace. + continue; + } + Tokenizer::iterator next = tIt; next++; + if( next==tokens.end() && *tIt == objectName ) + { + // if the last module name is the same as the class name then assume this is just the file the + // class has been implemented in. + continue; + } + if( sanitisedModulePath.size() ) + { + sanitisedModulePath += "."; + } + sanitisedModulePath += *tIt; + } + + return sanitisedModulePath; +} + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -104,13 +152,11 @@ Serialisation::Serialisation( const Gaffer::GraphComponent *parent, const std::s { if( const Node *node = runTimeCast( parent ) ) { - metadataModuleDependencies( node, m_modules ); - m_postScript += metadataSerialisation( node, parentName ); + m_postScript += metadataSerialisation( node, parentName, *this ); } else if( const Plug *plug = runTimeCast( parent ) ) { - metadataModuleDependencies( plug, m_modules ); - m_postScript += metadataSerialisation( plug, parentName ); + m_postScript += metadataSerialisation( plug, parentName, *this ); } } } @@ -167,50 +213,20 @@ std::string Serialisation::modulePath( const IECore::RefCounted *object ) return modulePath( o ); } -std::string Serialisation::modulePath( boost::python::object &o ) +std::string Serialisation::modulePath( const boost::python::object &o ) { - if( !PyObject_HasAttrString( o.ptr(), "__module__" ) ) - { - return ""; - } - std::string modulePath = extract( o.attr( "__module__" ) ); - std::string objectName; - if( PyType_Check( o.ptr() ) ) + // Querying the module path is expensive and done frequently, so we cache + // results. The cache is not thread-safe, but since we are dealing with + // python objects, we know this thread must have the GIL, and therefore no + // other thread can access the cache concurrently. + static std::unordered_map g_cache; + auto inserted = g_cache.insert( { o.ptr()->ob_type, std::string() } ); + if( inserted.second ) { - objectName = extract( o.attr( "__name__" ) ); + Py_INCREF( o.ptr()->ob_type ); + inserted.first->second = modulePathInternal( o ); } - else - { - objectName = extract( o.attr( "__class__" ).attr( "__name__" ) ); - } - - typedef boost::tokenizer > Tokenizer; - std::string sanitisedModulePath; - Tokenizer tokens( modulePath, boost::char_separator( "." ) ); - - for( Tokenizer::iterator tIt=tokens.begin(); tIt!=tokens.end(); tIt++ ) - { - if( tIt->compare( 0, 1, "_" )==0 ) - { - // assume that module path components starting with _ are bogus, and are used only to bring - // binary components into a namespace. - continue; - } - Tokenizer::iterator next = tIt; next++; - if( next==tokens.end() && *tIt == objectName ) - { - // if the last module name is the same as the class name then assume this is just the file the - // class has been implemented in. - continue; - } - if( sanitisedModulePath.size() ) - { - sanitisedModulePath += "."; - } - sanitisedModulePath += *tIt; - } - - return sanitisedModulePath; + return inserted.first->second; } std::string Serialisation::classPath( const IECore::RefCounted *object ) @@ -219,7 +235,7 @@ std::string Serialisation::classPath( const IECore::RefCounted *object ) return classPath( o ); } -std::string Serialisation::classPath( boost::python::object &object ) +std::string Serialisation::classPath( const boost::python::object &object ) { std::string result = modulePath( object ); if( result.size() ) @@ -409,6 +425,11 @@ std::string Serialisation::childIdentifier( const std::string &parentIdentifier, return result; } +void Serialisation::addModule( const std::string &moduleName ) +{ + m_modules.insert( moduleName ); +} + void Serialisation::registerSerialiser( IECore::TypeId targetType, SerialiserPtr serialiser ) { serialiserMap()[targetType] = serialiser; @@ -511,24 +532,24 @@ void Serialisation::Serialiser::moduleDependencies( const Gaffer::GraphComponent } } -std::string Serialisation::Serialiser::constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const +std::string Serialisation::Serialiser::constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const { object o( GraphComponentPtr( const_cast( graphComponent ) ) ); std::string r = extract( o.attr( "__repr__" )() ); return r; } -std::string Serialisation::Serialiser::postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string Serialisation::Serialiser::postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { return ""; } -std::string Serialisation::Serialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string Serialisation::Serialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { return ""; } -std::string Serialisation::Serialiser::postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string Serialisation::Serialiser::postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { return ""; } @@ -542,4 +563,3 @@ bool Serialisation::Serialiser::childNeedsConstruction( const Gaffer::GraphCompo { return false; } - diff --git a/src/GafferBindings/ValuePlugBinding.cpp b/src/GafferBindings/ValuePlugBinding.cpp index 8f7e3463751..4961b0cc7df 100644 --- a/src/GafferBindings/ValuePlugBinding.cpp +++ b/src/GafferBindings/ValuePlugBinding.cpp @@ -53,6 +53,8 @@ #include "boost/algorithm/string/replace.hpp" #include "boost/format.hpp" +#include + using namespace std; using namespace boost::python; using namespace GafferBindings; @@ -63,7 +65,7 @@ namespace const IECore::InternedString g_omitParentNodePlugValues( "valuePlugSerialiser:omitParentNodePlugValues" ); -std::string valueSerialisationWalk( const Gaffer::ValuePlug *plug, const std::string &identifier, const Serialisation &serialisation, bool &canCondense ) +std::string valueSerialisationWalk( const Gaffer::ValuePlug *plug, const std::string &identifier, Serialisation &serialisation, bool &canCondense ) { // There's nothing to do if the plug isn't serialisable. if( !plug->getFlags( Plug::Serialisable ) ) @@ -77,7 +79,7 @@ std::string valueSerialisationWalk( const Gaffer::ValuePlug *plug, const std::st string childSerialisations; bool canCondenseChildren = true; - for( ValuePlugIterator childIt( plug ); !childIt.done(); ++childIt ) + for( ValuePlug::Iterator childIt( plug ); !childIt.done(); ++childIt ) { const std::string childIdentifier = serialisation.childIdentifier( identifier, childIt.base() ); childSerialisations += valueSerialisationWalk( childIt->get(), childIdentifier, serialisation, canCondenseChildren ); @@ -118,30 +120,25 @@ std::string valueSerialisationWalk( const Gaffer::ValuePlug *plug, const std::st // Emit the `setValue()` call for this plug. - object pythonValue = pythonPlug.attr( "getValue" )(); - - if( PyObject_HasAttrString( pythonPlug.ptr(), "defaultValue" ) ) + if( plug->isSetToDefault() ) { - object pythonDefaultValue = pythonPlug.attr( "defaultValue" )(); - if( pythonValue == pythonDefaultValue ) - { - return ""; - } + return ""; } - return identifier + ".setValue( " + ValuePlugSerialiser::valueRepr( pythonValue ) + " )\n"; + object pythonValue = pythonPlug.attr( "getValue" )(); + return identifier + ".setValue( " + ValuePlugSerialiser::valueRepr( pythonValue, &serialisation ) + " )\n"; } -std::string compoundObjectRepr( const IECore::CompoundObject *o ) +std::string compoundObjectRepr( const IECore::CompoundObject &o, Serialisation *serialisation ) { std::string items; - for( const auto &e : o->members() ) + for( const auto &e : o.members() ) { if( items.size() ) { items += ", "; } - items += "'" + e.first.string() + "' : " + ValuePlugSerialiser::valueRepr( object( e.second ) ); + items += "'" + e.first.string() + "' : " + ValuePlugSerialiser::valueRepr( object( e.second ), serialisation ); } if( items.empty() ) @@ -156,7 +153,7 @@ std::string compoundObjectRepr( const IECore::CompoundObject *o ) } // namespace -std::string ValuePlugSerialiser::repr( const Gaffer::ValuePlug *plug, const std::string &extraArguments, const Serialisation *serialisation ) +std::string ValuePlugSerialiser::repr( const Gaffer::ValuePlug *plug, const std::string &extraArguments, Serialisation *serialisation ) { std::string result = Serialisation::classPath( plug ) + "( \"" + plug->getName().string() + "\", "; @@ -169,7 +166,7 @@ std::string ValuePlugSerialiser::repr( const Gaffer::ValuePlug *plug, const std: if( PyObject_HasAttrString( pythonPlug.ptr(), "defaultValue" ) ) { object pythonDefaultValue = pythonPlug.attr( "defaultValue" )(); - const std::string defaultValue = valueRepr( pythonDefaultValue ); + const std::string defaultValue = valueRepr( pythonDefaultValue, serialisation ); if( defaultValue.size() ) { result += "defaultValue = " + defaultValue + ", "; @@ -221,29 +218,12 @@ std::string ValuePlugSerialiser::repr( const Gaffer::ValuePlug *plug, const std: } -void ValuePlugSerialiser::moduleDependencies( const Gaffer::GraphComponent *graphComponent, std::set &modules, const Serialisation &serialisation ) const -{ - PlugSerialiser::moduleDependencies( graphComponent, modules, serialisation ); - - const ValuePlug *valuePlug = static_cast ( graphComponent ); - object pythonPlug( ValuePlugPtr( const_cast( valuePlug ) ) ); - if( PyObject_HasAttrString( pythonPlug.ptr(), "defaultValue" ) ) - { - object pythonDefaultValue = pythonPlug.attr( "defaultValue" )(); - std::string module = Serialisation::modulePath( pythonDefaultValue ); - if( module.size() ) - { - modules.insert( module ); - } - } -} - -std::string ValuePlugSerialiser::constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const +std::string ValuePlugSerialiser::constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const { return repr( static_cast( graphComponent ), "", &serialisation ); } -std::string ValuePlugSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const +std::string ValuePlugSerialiser::postHierarchy( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const { std::string result = PlugSerialiser::postHierarchy( graphComponent, identifier, serialisation ); @@ -262,26 +242,37 @@ std::string ValuePlugSerialiser::postHierarchy( const Gaffer::GraphComponent *gr return result; } -std::string ValuePlugSerialiser::valueRepr( const boost::python::object &value ) +std::string ValuePlugSerialiser::valueRepr( const boost::python::object &value, Serialisation *serialisation ) { // CompoundObject may contain objects which can only be serialised // via `objectToBase64()`, so we need to override the standard Cortex // serialiser. - boost::python::extract compoundObjectExtractor( value ); + boost::python::extract compoundObjectExtractor( value ); if( compoundObjectExtractor.check() ) { - auto co = compoundObjectExtractor(); - return compoundObjectRepr( co.get() ); + return compoundObjectRepr( compoundObjectExtractor(), serialisation ); } // We use IECore.repr() because it correctly prefixes the imath // types with the module name, and also works around problems - // when round-tripping empty Box2fs. - object repr = boost::python::import( "IECore" ).attr( "repr" ); - std::string result = extract( repr( value ) ); + // when round-tripping empty Box2fs. Accessing it via `import` + // is slow so we do it only once and store in `g_repr`. We + // deliberately "leak" this value as otherwise it will be cleaned + // up during static destruction, _after_ Python has already shut + // down. + static object *g_repr = new boost::python::object( boost::python::import( "IECore" ).attr( "repr" ) ); + std::string result = extract( (*g_repr)( value ) ); if( result.size() && result[0] != '<' ) { + if( serialisation ) + { + const std::string module = Serialisation::modulePath( value ); + if( module.size() ) + { + serialisation->addModule( module ); + } + } return result; } @@ -298,4 +289,4 @@ std::string ValuePlugSerialiser::valueRepr( const boost::python::object &value ) } return ""; -} \ No newline at end of file +} diff --git a/src/GafferCortex/ParameterHandler.cpp b/src/GafferCortex/ParameterHandler.cpp index 355792a4977..9eb7c423ab3 100644 --- a/src/GafferCortex/ParameterHandler.cpp +++ b/src/GafferCortex/ParameterHandler.cpp @@ -59,7 +59,7 @@ IECore::MurmurHash ParameterHandler::hash() const { IECore::MurmurHash result; const Gaffer::Plug *p = plug(); - for( Gaffer::RecursiveValuePlugIterator it( p ); !it.done(); ++it ) + for( Gaffer::ValuePlug::RecursiveIterator it( p ); !it.done(); ++it ) { result.append( (*it)->relativeName( p ) ); (*it)->hash( result ); diff --git a/src/GafferCortexModule/ParameterisedHolderBinding.cpp b/src/GafferCortexModule/ParameterisedHolderBinding.cpp index 4c11661941f..35006e05f14 100644 --- a/src/GafferCortexModule/ParameterisedHolderBinding.cpp +++ b/src/GafferCortexModule/ParameterisedHolderBinding.cpp @@ -68,7 +68,7 @@ template class ParameterisedHolderSerialiser : public NodeSerialiser { - std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { const T *parameterisedHolder = static_cast( graphComponent ); diff --git a/src/GafferDelight/IECoreDelightPreview/CameraAlgo.cpp b/src/GafferDelight/IECoreDelightPreview/CameraAlgo.cpp index 8c3fa4b2b53..22047ea3d9d 100644 --- a/src/GafferDelight/IECoreDelightPreview/CameraAlgo.cpp +++ b/src/GafferDelight/IECoreDelightPreview/CameraAlgo.cpp @@ -69,7 +69,7 @@ bool convert( const IECoreScene::Camera *camera, NSIContext_t context, const cha { parameters.add( { "fov", &fov, NSITypeFloat, 0, 1, 0 } ); if( camera->getFStop() > 0.0f ) - { + { parameters.add( { "depthoffield.enable", &dofEnable, NSITypeInteger, 0, 1, 0 } ); parameters.add( { "depthoffield.fstop", &fStop, NSITypeDouble, 0, 1, 0 } ); parameters.add( { "depthoffield.focallength", &focalLength, NSITypeDouble, 0, 1, 0 } ); diff --git a/src/GafferDispatch/Dispatcher.cpp b/src/GafferDispatch/Dispatcher.cpp index 5229ff4205d..44888ee6c53 100644 --- a/src/GafferDispatch/Dispatcher.cpp +++ b/src/GafferDispatch/Dispatcher.cpp @@ -265,9 +265,8 @@ void Dispatcher::createJobDirectory( const Gaffer::ScriptNode *script, Gaffer::C context->set( g_scriptFileNameContextEntry, scriptFileName.string() ); } -/* - * Static functions - */ +// Static functions +// ================ Dispatcher::PreDispatchSignal &Dispatcher::preDispatchSignal() { @@ -286,18 +285,7 @@ Dispatcher::PostDispatchSignal &Dispatcher::postDispatchSignal() void Dispatcher::setupPlugs( Plug *parentPlug ) { - if ( const TaskNode *node = parentPlug->ancestor() ) - { - /// \todo: this will always return true until we sort out issue #915. - /// But since requiresSequenceExecution() could feasibly return different - /// values in different contexts, perhaps the conditional is bogus - /// anyway, and if anything we should just grey out the plug in the UI? - if( !node->taskPlug()->requiresSequenceExecution() ) - { - parentPlug->addChild( new IntPlug( g_batchSize, Plug::In, 1 ) ); - } - } - + parentPlug->addChild( new IntPlug( g_batchSize, Plug::In, 1 ) ); parentPlug->addChild( new BoolPlug( g_immediatePlugName, Plug::In, false ) ); const CreatorMap &m = creators(); @@ -533,15 +521,16 @@ class Dispatcher::Batcher TaskBatchPtr acquireBatch( const TaskNode::Task &task ) { + // Several plugs will be evaluated that may vary by context, + // so we need to be in the correct context for this task + // \todo should we be removing `frame` from the context? + Context::Scope scopedTaskContext( task.context() ); + // See if we've previously visited this task, and therefore // have placed it in a batch already, which we can return // unchanged. The `taskHash` is used as the unique identity of // the task. - MurmurHash taskHash; - { - Context::Scope scopedTaskContext( task.context() ); - taskHash = task.plug()->hash(); - } + MurmurHash taskHash = task.plug()->hash(); const bool taskIsNoOp = taskHash == IECore::MurmurHash(); if( taskIsNoOp ) { @@ -611,10 +600,6 @@ class Dispatcher::Batcher const BoolPlug *immediatePlug = dispatcherPlug( task )->getChild( g_immediatePlugName ); if( immediatePlug && immediatePlug->getValue() ) { - /// \todo Should we be scoping a context for this, to allow the plug to - /// have expressions on it? If so, should we be doing the same before - /// calling requiresSequenceExecution()? Or should we instead require that - /// they always be constant? batch->blindData()->writable()[g_immediateBlindDataName] = g_trueBoolData; } @@ -657,8 +642,7 @@ class Dispatcher::Batcher continue; } - result.append( *it ); - context->get( *it )->hash( result ); + result.append( context->variableHash( *it ) ); } return result; } diff --git a/src/GafferDispatch/TaskNode.cpp b/src/GafferDispatch/TaskNode.cpp index 4b3c9df8465..f96539d39a4 100644 --- a/src/GafferDispatch/TaskNode.cpp +++ b/src/GafferDispatch/TaskNode.cpp @@ -366,7 +366,7 @@ bool TaskNode::affectsTask( const Plug *input ) const void TaskNode::preTasks( const Context *context, Tasks &tasks ) const { - for( TaskPlugIterator cIt( preTasksPlug() ); !cIt.done(); ++cIt ) + for( TaskPlug::Iterator cIt( preTasksPlug() ); !cIt.done(); ++cIt ) { tasks.push_back( Task( *cIt, context ) ); } @@ -374,7 +374,7 @@ void TaskNode::preTasks( const Context *context, Tasks &tasks ) const void TaskNode::postTasks( const Context *context, Tasks &tasks ) const { - for( TaskPlugIterator cIt( postTasksPlug() ); !cIt.done(); ++cIt ) + for( TaskPlug::Iterator cIt( postTasksPlug() ); !cIt.done(); ++cIt ) { tasks.push_back( Task( *cIt, context ) ); } diff --git a/src/GafferImage/Catalogue.cpp b/src/GafferImage/Catalogue.cpp index 7621396e57a..f1fa645a444 100644 --- a/src/GafferImage/Catalogue.cpp +++ b/src/GafferImage/Catalogue.cpp @@ -193,7 +193,7 @@ class Catalogue::InternalImage : public ImageNode removeDisplays(); size_t numDisplays = 0; - for( DisplayIterator it( other ); !it.done(); ++it ) + for( Display::Iterator it( other ); !it.done(); ++it ) { Display *display = it->get(); DisplayPtr displayCopy = new Display; @@ -354,7 +354,7 @@ class Catalogue::InternalImage : public ImageNode void removeDisplays() { vector toDelete; - for( DisplayIterator it( this ); !it.done(); ++it ) + for( Display::Iterator it( this ); !it.done(); ++it ) { toDelete.push_back( it->get() ); } @@ -428,7 +428,7 @@ class Catalogue::InternalImage : public ImageNode InternalImagePtr imageCopy = new InternalImage; size_t i = 0; - for( DisplayIterator it( client ); !it.done(); ++it ) + for( Display::Iterator it( client ); !it.done(); ++it ) { Display *display = it->get(); DisplayPtr displayCopy = new Display; @@ -958,7 +958,7 @@ void Catalogue::imageRemoved( GraphComponent *graphComponent ) Image *image = static_cast( graphComponent ); // This causes the image to disconnect from - // the switch automatically. + // the switch automatically. removeChild( imageNode( image ) ); // So now we go through and shuffle everything down // to fill the hole in the switch inputs. diff --git a/src/GafferImage/ChannelDataProcessor.cpp b/src/GafferImage/ChannelDataProcessor.cpp index 17d63474a16..1eede2ab851 100644 --- a/src/GafferImage/ChannelDataProcessor.cpp +++ b/src/GafferImage/ChannelDataProcessor.cpp @@ -141,22 +141,22 @@ void ChannelDataProcessor::hashChannelData( const GafferImage::ImagePlug *output IECore::ConstStringVectorDataPtr channelNamesData; bool unpremult = false; bool repremultByProcessedAlpha = false; - if( m_hasUnpremultPlug && channelName != "A" ) + if( m_hasUnpremultPlug && channelName != ImageAlgo::channelNameA ) { ImagePlug::GlobalScope globalScope( context ); unpremult = processUnpremultipliedPlug()->getValue(); if( unpremult ) { channelNamesData = inPlug()->channelNamesPlug()->getValue(); - repremultByProcessedAlpha = channelEnabled( "A" ); + repremultByProcessedAlpha = channelEnabled( ImageAlgo::channelNameA ); } } h.append( unpremult ); - if( unpremult && ImageAlgo::channelExists( channelNamesData->readable(), "A" ) ) + if( unpremult && ImageAlgo::channelExists( channelNamesData->readable(), ImageAlgo::channelNameA ) ) { ImagePlug::ChannelDataScope s( context ); - s.setChannelName( "A" ); + s.setChannelName( &ImageAlgo::channelNameA ); inPlug()->channelDataPlug()->hash( h ); if( repremultByProcessedAlpha ) { @@ -173,23 +173,23 @@ IECore::ConstFloatVectorDataPtr ChannelDataProcessor::computeChannelData( const IECore::ConstStringVectorDataPtr channelNamesData; bool unpremult = false; bool repremultByProcessedAlpha = false; - if( m_hasUnpremultPlug && channelName != "A" ) + if( m_hasUnpremultPlug && channelName != ImageAlgo::channelNameA ) { ImagePlug::GlobalScope globalScope( context ); unpremult = processUnpremultipliedPlug()->getValue(); if( unpremult ) { channelNamesData = inPlug()->channelNamesPlug()->getValue(); - repremultByProcessedAlpha = channelEnabled( "A" ); + repremultByProcessedAlpha = channelEnabled( ImageAlgo::channelNameA ); } } IECore::ConstFloatVectorDataPtr alphaData; IECore::ConstFloatVectorDataPtr postAlphaData; - if( unpremult && ImageAlgo::channelExists( channelNamesData->readable(), "A" ) ) + if( unpremult && ImageAlgo::channelExists( channelNamesData->readable(), ImageAlgo::channelNameA ) ) { ImagePlug::ChannelDataScope s( context ); - s.setChannelName( "A" ); + s.setChannelName( &ImageAlgo::channelNameA ); alphaData = inPlug()->channelDataPlug()->getValue(); if( repremultByProcessedAlpha ) { diff --git a/src/GafferImage/Clamp.cpp b/src/GafferImage/Clamp.cpp index 1d33b6d209b..f971692c3e7 100644 --- a/src/GafferImage/Clamp.cpp +++ b/src/GafferImage/Clamp.cpp @@ -155,8 +155,10 @@ bool Clamp::enabled() const return false; } - if( minEnabledPlug()->getValue() == false && - maxEnabledPlug()->getValue() == false ) + if( + minEnabledPlug()->getValue() == false && + maxEnabledPlug()->getValue() == false + ) { return false; } diff --git a/src/GafferImage/CollectImages.cpp b/src/GafferImage/CollectImages.cpp index 22900634d55..64632a6afc9 100644 --- a/src/GafferImage/CollectImages.cpp +++ b/src/GafferImage/CollectImages.cpp @@ -205,7 +205,7 @@ void CollectImages::hashFormat( const GafferImage::ImagePlug *parent, const Gaff if( rootLayersData->readable().size() ) { Context::EditableScope editScope( context ); - editScope.set( layerVariablePlug()->getValue(), rootLayersData->readable()[0] ); + editScope.set( layerVariablePlug()->getValue(), &( rootLayersData->readable()[0] ) ); h = inPlug()->formatPlug()->hash(); } else @@ -221,7 +221,7 @@ GafferImage::Format CollectImages::computeFormat( const Gaffer::Context *context if( rootLayersData->readable().size() ) { Context::EditableScope editScope( context ); - editScope.set( layerVariablePlug()->getValue(), rootLayersData->readable()[0] ); + editScope.set( layerVariablePlug()->getValue(), &( rootLayersData->readable()[0] ) ); return inPlug()->formatPlug()->getValue(); } else @@ -241,7 +241,7 @@ void CollectImages::hashDeep( const GafferImage::ImagePlug *parent, const Gaffer Context::EditableScope editScope( context ); for( const auto &i : rootLayersData->readable() ) { - editScope.set( layerVariable, i ); + editScope.set( layerVariable, &i ); inPlug()->deepPlug()->hash( h ); } } @@ -256,7 +256,7 @@ bool CollectImages::computeDeep( const Gaffer::Context *context, const ImagePlug Context::EditableScope editScope( context ); for( const auto &i : rootLayersData->readable() ) { - editScope.set( layerVariable, i ); + editScope.set( layerVariable, &i ); bool curDeep = inPlug()->deepPlug()->getValue(); if( outDeep == -1 ) { @@ -288,7 +288,7 @@ void CollectImages::hashSampleOffsets( const GafferImage::ImagePlug *parent, con Context::EditableScope editScope( context ); for( const auto &i : rootLayersData->readable() ) { - editScope.set( layerVariable, i ); + editScope.set( layerVariable, &i ); inPlug()->sampleOffsetsPlug()->hash( h ); } } @@ -307,7 +307,7 @@ IECore::ConstIntVectorDataPtr CollectImages::computeSampleOffsets( const Imath:: Context::EditableScope editScope( context ); for( const auto &i : rootLayersData->readable() ) { - editScope.set( layerVariable, i ); + editScope.set( layerVariable, &i ); IECore::ConstIntVectorDataPtr curSampleOffsetsData = inPlug()->sampleOffsetsPlug()->getValue(); if( !outSampleOffsetsData ) { @@ -330,7 +330,7 @@ void CollectImages::hashMetadata( const GafferImage::ImagePlug *parent, const Ga if( rootLayersData->readable().size() ) { Context::EditableScope editScope( context ); - editScope.set( layerVariablePlug()->getValue(), rootLayersData->readable()[0] ); + editScope.set( layerVariablePlug()->getValue(), &( rootLayersData->readable()[0] ) ); h = inPlug()->metadataPlug()->hash(); } else @@ -346,7 +346,7 @@ IECore::ConstCompoundDataPtr CollectImages::computeMetadata( const Gaffer::Conte if( rootLayersData->readable().size() ) { Context::EditableScope editScope( context ); - editScope.set( layerVariablePlug()->getValue(), rootLayersData->readable()[0] ); + editScope.set( layerVariablePlug()->getValue(), &( rootLayersData->readable()[0] ) ); return inPlug()->metadataPlug()->getValue(); } else @@ -372,11 +372,11 @@ void CollectImages::hashDataWindow( const GafferImage::ImagePlug *output, const } Context::EditableScope editScope( context ); - editScope.set( layerVariable, rootLayers[0] ); + editScope.set( layerVariable, &( rootLayers[0] ) ); inPlug()->deepPlug()->hash( h ); for( unsigned int i = 0; i < rootLayers.size(); i++ ) { - editScope.set( layerVariable, rootLayers[i] ); + editScope.set( layerVariable, &( rootLayers[i] ) ); inPlug()->dataWindowPlug()->hash( h ); } } @@ -396,11 +396,11 @@ Imath::Box2i CollectImages::computeDataWindow( const Gaffer::Context *context, c } Context::EditableScope editScope( context ); - editScope.set( layerVariable, rootLayers[0] ); + editScope.set( layerVariable, &( rootLayers[0] ) ); bool deep = inPlug()->deepPlug()->getValue(); for( unsigned int i = 0; i < rootLayers.size(); i++ ) { - editScope.set( layerVariable, rootLayers[i] ); + editScope.set( layerVariable, &( rootLayers[i] ) ); Box2i curDataWindow = inPlug()->dataWindowPlug()->getValue(); if( i == 0 || !deep ) { @@ -437,7 +437,7 @@ void CollectImages::hashChannelNames( const GafferImage::ImagePlug *output, cons Context::EditableScope editScope( context ); for( unsigned int i = 0; i < rootLayers.size(); i++ ) { - editScope.set( layerVariable, rootLayers[i] ); + editScope.set( layerVariable, &( rootLayers[i] ) ); inPlug()->channelNamesPlug()->hash( h ); } } @@ -455,7 +455,7 @@ IECore::ConstStringVectorDataPtr CollectImages::computeChannelNames( const Gaffe Context::EditableScope editScope( context ); for( unsigned int i = 0; i < rootLayers.size(); i++ ) { - editScope.set( layerVariable, rootLayers[i] ); + editScope.set( layerVariable, &( rootLayers[i] ) ); ConstStringVectorDataPtr layerChannelsData = inPlug()->channelNamesPlug()->getValue(); const std::vector &layerChannels = layerChannelsData->readable(); @@ -496,8 +496,8 @@ void CollectImages::hashChannelData( const GafferImage::ImagePlug *parent, const sourceLayerAndChannel( channelName, rootLayers, srcLayer, srcChannel ); Context::EditableScope editScope( context ); - editScope.set( ImagePlug::channelNameContextName, srcChannel ); - editScope.set( layerVariable, srcLayer ); + editScope.set( ImagePlug::channelNameContextName, &srcChannel ); + editScope.set( layerVariable, &srcLayer ); const V2i tileOrigin = context->get( ImagePlug::tileOriginContextName ); const Box2i tileBound( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ); @@ -543,7 +543,7 @@ IECore::ConstFloatVectorDataPtr CollectImages::computeChannelData( const std::st sourceLayerAndChannel( channelName, rootLayers, srcLayer, srcChannel ); Context::EditableScope editScope( context ); - editScope.set( layerVariable, srcLayer ); + editScope.set( layerVariable, &srcLayer ); // First use this EditableScope as a global scope editScope.remove( ImagePlug::channelNameContextName ); @@ -559,8 +559,8 @@ IECore::ConstFloatVectorDataPtr CollectImages::computeChannelData( const std::st } // Then set up the scope to evaluate the input channel data - editScope.set( ImagePlug::channelNameContextName, srcChannel ); - editScope.set( ImagePlug::tileOriginContextName, tileOrigin ); + editScope.set( ImagePlug::channelNameContextName, &srcChannel ); + editScope.set( ImagePlug::tileOriginContextName, &tileOrigin ); ConstFloatVectorDataPtr inputData = inPlug()->channelDataPlug()->getValue(); @@ -588,4 +588,3 @@ IECore::ConstFloatVectorDataPtr CollectImages::computeChannelData( const std::st return resultData; } } - diff --git a/src/GafferImage/ColorProcessor.cpp b/src/GafferImage/ColorProcessor.cpp index 38ba510a069..401ea096562 100644 --- a/src/GafferImage/ColorProcessor.cpp +++ b/src/GafferImage/ColorProcessor.cpp @@ -167,9 +167,9 @@ void ColorProcessor::compute( Gaffer::ValuePlug *output, const Gaffer::Context * { ImagePlug::ChannelDataScope channelDataScope( context ); - if( unpremult && ImageAlgo::channelExists( channelNames, "A" ) ) + if( unpremult && ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameA ) ) { - channelDataScope.setChannelName( "A" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameA ); alpha = inPlug()->channelDataPlug()->getValue(); } @@ -179,7 +179,7 @@ void ColorProcessor::compute( Gaffer::ValuePlug *output, const Gaffer::Context * string channelName = ImageAlgo::channelName( layerName, baseName ); if( ImageAlgo::channelExists( channelNames, channelName ) ) { - channelDataScope.setChannelName( channelName ); + channelDataScope.setChannelName( &channelName ); rgb[i] = inPlug()->channelDataPlug()->getValue()->copy(); samples = rgb[i]->readable().size(); @@ -290,7 +290,8 @@ void ColorProcessor::hashChannelData( const GafferImage::ImagePlug *output, cons h.append( baseName ); { Context::EditableScope layerScope( context ); - layerScope.set( g_layerNameKey, ImageAlgo::layerName( channel ) ); + std::string layerNameStr = ImageAlgo::layerName( channel ); + layerScope.set( g_layerNameKey, &layerNameStr ); colorDataPlug()->hash( h ); } } @@ -313,7 +314,8 @@ IECore::ConstFloatVectorDataPtr ColorProcessor::computeChannelData( const std::s ConstObjectVectorPtr colorData; { Context::EditableScope layerScope( context ); - layerScope.set( g_layerNameKey, ImageAlgo::layerName( channel ) ); + std::string layerNameStr = ImageAlgo::layerName( channel ); + layerScope.set( g_layerNameKey, &layerNameStr ); colorData = boost::static_pointer_cast( colorDataPlug()->getValue() ); } return boost::static_pointer_cast( colorData->members()[ImageAlgo::colorIndex( baseName)] ); @@ -343,7 +345,7 @@ void ColorProcessor::hashColorData( const Gaffer::Context *context, IECore::Murm string channelName = ImageAlgo::channelName( layerName, baseName ); if( ImageAlgo::channelExists( channelNames, channelName ) ) { - channelDataScope.setChannelName( channelName ); + channelDataScope.setChannelName( &channelName ); inPlug()->channelDataPlug()->hash( h ); } else @@ -352,9 +354,9 @@ void ColorProcessor::hashColorData( const Gaffer::Context *context, IECore::Murm } } - if( unpremult && ImageAlgo::channelExists( channelNames, "A" ) ) + if( unpremult && ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameA ) ) { - channelDataScope.setChannelName( "A" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameA ); inPlug()->channelDataPlug()->hash( h ); } } diff --git a/src/GafferImage/CopyChannels.cpp b/src/GafferImage/CopyChannels.cpp index b1ce809766a..47bf29a922c 100644 --- a/src/GafferImage/CopyChannels.cpp +++ b/src/GafferImage/CopyChannels.cpp @@ -163,7 +163,7 @@ void CopyChannels::hash( const Gaffer::ValuePlug *output, const Gaffer::Context if( output == mappingPlug() ) { - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput() ) { @@ -186,7 +186,7 @@ void CopyChannels::compute( Gaffer::ValuePlug *output, const Gaffer::Context *co result->members()["__channelNames"] = channelNamesData; vector &channelNames = channelNamesData->writable(); size_t i = 0; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++i, ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++i, ++it ) { /// \todo We need this check because an unconnected input /// has a default channelNames value of [ "R", "G", "B" ], @@ -223,7 +223,7 @@ void CopyChannels::hashDataWindow( const GafferImage::ImagePlug *output, const G { FlatImageProcessor::hashDataWindow( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->dataWindowPlug()->hash( h ); } @@ -232,7 +232,7 @@ void CopyChannels::hashDataWindow( const GafferImage::ImagePlug *output, const G Imath::Box2i CopyChannels::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const { Imath::Box2i dataWindow; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { dataWindow.extendBy( (*it)->dataWindowPlug()->getValue() ); } @@ -353,4 +353,3 @@ IECore::ConstFloatVectorDataPtr CopyChannels::computeChannelData( const std::str return ImagePlug::blackTile(); } } - diff --git a/src/GafferImage/DeepHoldout.cpp b/src/GafferImage/DeepHoldout.cpp index 8fb588b6f22..f506d1b2ce7 100644 --- a/src/GafferImage/DeepHoldout.cpp +++ b/src/GafferImage/DeepHoldout.cpp @@ -51,6 +51,12 @@ using namespace IECore; using namespace Gaffer; using namespace GafferImage; +namespace +{ + std::string g_premultipliedAverageZName( "__premultipliedAverageZ" ); + std::string g_holdoutAlphaName( "__holdoutAlpha" ); +} + GAFFER_NODE_DEFINE_TYPE( DeepHoldout ); size_t DeepHoldout::g_firstPlugIndex = 0; @@ -168,8 +174,8 @@ IECore::ConstStringVectorDataPtr DeepHoldout::computeChannelNames( const Gaffer: StringVectorDataPtr resultData = inChannelNamesData->copy(); std::vector &result = resultData->writable(); result.reserve( result.size() + 2 ); - result.push_back( "__premultipliedAverageZ" ); - result.push_back( "__holdoutAlpha" ); + result.push_back( g_premultipliedAverageZName ); + result.push_back( g_holdoutAlphaName ); return resultData; } else @@ -195,30 +201,30 @@ void DeepHoldout::hashChannelData( const GafferImage::ImagePlug *output, const G if( output == intermediateInPlug() ) { const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - if( channelName == "__premultipliedAverageZ" ) + if( channelName == g_premultipliedAverageZName ) { ImageProcessor::hashChannelData( output, context, h ); ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); inPlug()->channelDataPlug()->hash( h ); ConstStringVectorDataPtr channelNames = inPlug()->channelNames(); - if( ImageAlgo::channelExists( channelNames->readable(), "ZBack" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameZBack ) ) { - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); inPlug()->channelDataPlug()->hash( h ); } - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); inPlug()->channelDataPlug()->hash( h ); } } - else if( channelName == "__holdoutAlpha" ) + else if( channelName == g_holdoutAlphaName ) { ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); return inPlug()->channelDataPlug()->hash( h ); } else @@ -229,7 +235,7 @@ void DeepHoldout::hashChannelData( const GafferImage::ImagePlug *output, const G else { const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - if( !( channelName == "A" || channelName == "Z" ) ) + if( !( channelName == ImageAlgo::channelNameA || channelName == ImageAlgo::channelNameZ ) ) { h = flattenedPlug()->channelDataPlug()->hash(); return; @@ -238,16 +244,16 @@ void DeepHoldout::hashChannelData( const GafferImage::ImagePlug *output, const G ImageProcessor::hashChannelData( output, context, h ); ImagePlug::ChannelDataScope channelScope( context ); - if( channelName == "A" ) + if( channelName == ImageAlgo::channelNameA ) { - channelScope.setChannelName( "__holdoutAlpha" ); + channelScope.setChannelName( &g_holdoutAlphaName ); flattenedPlug()->channelDataPlug()->hash( h ); } - else if( channelName == "Z" ) + else if( channelName == ImageAlgo::channelNameZ ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); outPlug()->channelDataPlug()->hash( h ); - channelScope.setChannelName( "__premultipliedAverageZ" ); + channelScope.setChannelName( &g_premultipliedAverageZName ); flattenedPlug()->channelDataPlug()->hash( h ); } } @@ -258,19 +264,19 @@ IECore::ConstFloatVectorDataPtr DeepHoldout::computeChannelData( const std::stri if( parent == intermediateInPlug() ) { const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - if( channelName == "__premultipliedAverageZ" ) + if( channelName == g_premultipliedAverageZName ) { ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); FloatVectorDataPtr resultData = inPlug()->channelDataPlug()->getValue()->copy(); std::vector &result = resultData->writable(); ConstStringVectorDataPtr channelNames = inPlug()->channelNames(); - if( ImageAlgo::channelExists( channelNames->readable(), "ZBack" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameZBack ) ) { // If we have a ZBack channel, find the average depth of each sample - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); ConstFloatVectorDataPtr zBackData = inPlug()->channelDataPlug()->getValue(); const std::vector &zBack = zBackData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) @@ -278,9 +284,9 @@ IECore::ConstFloatVectorDataPtr DeepHoldout::computeChannelData( const std::stri result[i] = result[i] * 0.5f + zBack[i] * 0.5f; } } - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr alphaData = inPlug()->channelDataPlug()->getValue(); const std::vector &alpha = alphaData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) @@ -290,10 +296,10 @@ IECore::ConstFloatVectorDataPtr DeepHoldout::computeChannelData( const std::stri } return resultData; } - else if( channelName == "__holdoutAlpha" ) + else if( channelName == g_holdoutAlphaName ) { ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); return inPlug()->channelDataPlug()->getValue(); } else @@ -303,29 +309,29 @@ IECore::ConstFloatVectorDataPtr DeepHoldout::computeChannelData( const std::stri } else { - if( !( channelName == "A" || channelName == "Z" ) ) + if( !( channelName == ImageAlgo::channelNameA || channelName == ImageAlgo::channelNameZ ) ) { return flattenedPlug()->channelDataPlug()->getValue(); } ImagePlug::ChannelDataScope channelScope( context ); - if( channelName == "A" ) + if( channelName == ImageAlgo::channelNameA ) { - channelScope.setChannelName( "__holdoutAlpha" ); + channelScope.setChannelName( &g_holdoutAlphaName ); return flattenedPlug()->channelDataPlug()->getValue(); } - channelScope.setChannelName( "__premultipliedAverageZ" ); + channelScope.setChannelName( &g_premultipliedAverageZName ); FloatVectorDataPtr resultData = flattenedPlug()->channelDataPlug()->getValue()->copy(); std::vector &result = resultData->writable(); ConstStringVectorDataPtr channelNames = inPlug()->channelNames(); - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { // For consistency with other uses of Z, once we've got a properly filtered and merged Z, // we can unpremult it again - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr alphaData = outPlug()->channelDataPlug()->getValue(); const std::vector &alpha = alphaData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) @@ -354,4 +360,3 @@ IECore::ConstIntVectorDataPtr DeepHoldout::computeSampleOffsets( const Imath::V2 { return ImagePlug::flatTileSampleOffsets(); } - diff --git a/src/GafferImage/DeepMerge.cpp b/src/GafferImage/DeepMerge.cpp index 22a2a270c1b..aac3852f502 100644 --- a/src/GafferImage/DeepMerge.cpp +++ b/src/GafferImage/DeepMerge.cpp @@ -131,7 +131,7 @@ void DeepMerge::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co V2i tileOrigin = context->get( ImagePlug::tileOriginContextName ); const Box2i tileBound( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -261,7 +261,7 @@ void DeepMerge::hashDataWindow( const GafferImage::ImagePlug *output, const Gaff { ImageProcessor::hashDataWindow( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->dataWindowPlug()->hash( h ); } @@ -270,7 +270,7 @@ void DeepMerge::hashDataWindow( const GafferImage::ImagePlug *output, const Gaff Imath::Box2i DeepMerge::computeDataWindow( const Gaffer::Context *context, const ImagePlug *parent ) const { Imath::Box2i dataWindow; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { // We don't need to check that the plug is connected here as unconnected plugs don't have data windows. dataWindow.extendBy( (*it)->dataWindowPlug()->getValue() ); @@ -283,7 +283,7 @@ void DeepMerge::hashChannelNames( const GafferImage::ImagePlug *output, const Ga { ImageProcessor::hashChannelNames( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -297,7 +297,7 @@ IECore::ConstStringVectorDataPtr DeepMerge::computeChannelNames( const Gaffer::C IECore::StringVectorDataPtr outChannelStrVectorData( new IECore::StringVectorData() ); std::vector &outChannels( outChannelStrVectorData->writable() ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -337,7 +337,7 @@ void DeepMerge::hashChannelData( const GafferImage::ImagePlug *output, const Gaf } - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput() ) { @@ -393,8 +393,8 @@ IECore::ConstFloatVectorDataPtr DeepMerge::computeChannelData( const std::string } // In a per-tile and per-channel context, get a ptr to the channel data of each input - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( channelName ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &channelName ); std::vector< ConstFloatVectorDataPtr > channelDatas( numInputs ); std::vector< const float* > channelPtrs( numInputs, nullptr ); for( int j = 0; j < numInputs; j++ ) @@ -410,9 +410,9 @@ IECore::ConstFloatVectorDataPtr DeepMerge::computeChannelData( const std::string { // Special case to copy Z to ZBack when combining images where some have ZBack // and some don't - reusedScope.setChannelName( "Z" ); + reusedScope.setChannelName( &ImageAlgo::channelNameZ ); channelDatas[j] = inP->channelDataPlug()->getValue(); - reusedScope.setChannelName( channelName ); + reusedScope.setChannelName( &channelName ); } else { @@ -477,4 +477,3 @@ bool DeepMerge::computeDeep( const Gaffer::Context *context, const ImagePlug *pa { return true; } - diff --git a/src/GafferImage/DeepRecolor.cpp b/src/GafferImage/DeepRecolor.cpp index ecc7f76ec45..756a8ca4711 100644 --- a/src/GafferImage/DeepRecolor.cpp +++ b/src/GafferImage/DeepRecolor.cpp @@ -128,8 +128,8 @@ void DeepRecolor::hashChannelData( const GafferImage::ImagePlug *output, const G !ImageAlgo::channelExists( colorSourceChannelNames->readable(), channelName ) ) { - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( channelName ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &channelName ); h = inPlug()->channelDataPlug()->hash(); return; } @@ -142,11 +142,11 @@ void DeepRecolor::hashChannelData( const GafferImage::ImagePlug *output, const G const Imath::Box2i colorSourceDataWindow = colorSourcePlug()->dataWindowPlug()->getValue(); ConstStringVectorDataPtr inChannelNames = inPlug()->channelNamesPlug()->getValue(); - reusedScope.setTileOrigin( tileOrigin ); + reusedScope.setTileOrigin( &tileOrigin ); inPlug()->sampleOffsetsPlug()->hash( h ); - reusedScope.setChannelName( "A" ); - if( ImageAlgo::channelExists( inChannelNames->readable(), "A" ) ) + reusedScope.setChannelName( &ImageAlgo::channelNameA ); + if( ImageAlgo::channelExists( inChannelNames->readable(), ImageAlgo::channelNameA ) ) { inPlug()->channelDataPlug()->hash( h ); } @@ -155,7 +155,7 @@ void DeepRecolor::hashChannelData( const GafferImage::ImagePlug *output, const G h.append( true ); } - if( ImageAlgo::channelExists( colorSourceChannelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( colorSourceChannelNames->readable(), ImageAlgo::channelNameA ) ) { colorSourcePlug()->channelDataPlug()->hash( h ); } @@ -164,7 +164,7 @@ void DeepRecolor::hashChannelData( const GafferImage::ImagePlug *output, const G h.append( true ); } - reusedScope.setChannelName( channelName ); + reusedScope.setChannelName( &channelName ); colorSourcePlug()->channelDataPlug()->hash( h ); @@ -189,8 +189,8 @@ IECore::ConstFloatVectorDataPtr DeepRecolor::computeChannelData( const std::stri !ImageAlgo::channelExists( colorSourceChannelNames->readable(), channelName ) ) { - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( channelName ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &channelName ); return inPlug()->channelDataPlug()->getValue(); } @@ -202,7 +202,7 @@ IECore::ConstFloatVectorDataPtr DeepRecolor::computeChannelData( const std::stri const Imath::Box2i colorSourceDataWindow = colorSourcePlug()->dataWindowPlug()->getValue(); ConstStringVectorDataPtr inChannelNames = inPlug()->channelNamesPlug()->getValue(); - reusedScope.setTileOrigin( tileOrigin ); + reusedScope.setTileOrigin( &tileOrigin ); ConstIntVectorDataPtr sampleOffsetsData = inPlug()->sampleOffsetsPlug()->getValue(); const std::vector &sampleOffsets = sampleOffsetsData->readable(); @@ -220,11 +220,11 @@ IECore::ConstFloatVectorDataPtr DeepRecolor::computeChannelData( const std::stri return resultData; } - reusedScope.setChannelName( "A" ); + reusedScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr deepAlphaData; - if( ImageAlgo::channelExists( inChannelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( inChannelNames->readable(), ImageAlgo::channelNameA ) ) { - if( useColorSourceAlpha && channelName != "A" ) + if( useColorSourceAlpha && channelName != ImageAlgo::channelNameA ) { deepAlphaData = outPlug()->channelDataPlug()->getValue(); } @@ -239,7 +239,7 @@ IECore::ConstFloatVectorDataPtr DeepRecolor::computeChannelData( const std::stri } ConstFloatVectorDataPtr colorSourceAlphaData; - if( ImageAlgo::channelExists( colorSourceChannelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( colorSourceChannelNames->readable(), ImageAlgo::channelNameA ) ) { colorSourceAlphaData = colorSourcePlug()->channelDataPlug()->getValue(); } @@ -332,7 +332,7 @@ IECore::ConstFloatVectorDataPtr DeepRecolor::computeChannelData( const std::stri } else { - reusedScope.setChannelName( channelName ); + reusedScope.setChannelName( &channelName ); ConstFloatVectorDataPtr colorSourceChannelData = colorSourcePlug()->channelDataPlug()->getValue(); const std::vector &colorSourceChannel = colorSourceChannelData->readable(); diff --git a/src/GafferImage/DeepSampleCounts.cpp b/src/GafferImage/DeepSampleCounts.cpp index 7f09ae13a22..0e074d5389b 100644 --- a/src/GafferImage/DeepSampleCounts.cpp +++ b/src/GafferImage/DeepSampleCounts.cpp @@ -117,7 +117,7 @@ IECore::ConstFloatVectorDataPtr DeepSampleCounts::computeChannelData( const std: return ImagePlug::whiteTile(); } - scope.setTileOrigin( tileOrigin ); + scope.setTileOrigin( &tileOrigin ); ConstIntVectorDataPtr sampleOffsetsData = inPlug()->sampleOffsetsPlug()->getValue(); FloatVectorDataPtr resultData = new FloatVectorData(); diff --git a/src/GafferImage/DeepSampler.cpp b/src/GafferImage/DeepSampler.cpp index cf41c6404ce..40c013e2685 100644 --- a/src/GafferImage/DeepSampler.cpp +++ b/src/GafferImage/DeepSampler.cpp @@ -125,13 +125,13 @@ void DeepSampler::hash( const Gaffer::ValuePlug *output, const Gaffer::Context * V2i tileOrigin = ImagePlug::tileOrigin( pixel ); ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setTileOrigin( tileOrigin ); + channelScope.setTileOrigin( &tileOrigin ); imagePlug()->sampleOffsetsPlug()->hash( h ); for( const auto &i : channelNames->readable() ) { - channelScope.setChannelName( i ); + channelScope.setChannelName( &i ); imagePlug()->channelDataPlug()->hash( h ); } } @@ -154,7 +154,7 @@ void DeepSampler::compute( Gaffer::ValuePlug *output, const Gaffer::Context *con ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setTileOrigin( tileOrigin ); + channelScope.setTileOrigin( &tileOrigin ); ConstIntVectorDataPtr sampleOffsetsData = imagePlug()->sampleOffsetsPlug()->getValue(); int pixelIndex = ImagePlug::pixelIndex( pixel, tileOrigin ); @@ -166,7 +166,7 @@ void DeepSampler::compute( Gaffer::ValuePlug *output, const Gaffer::Context *con { for( const auto &i : channelNames->readable() ) { - channelScope.setChannelName( i ); + channelScope.setChannelName( &i ); ConstFloatVectorDataPtr channelData = imagePlug()->channelDataPlug()->getValue(); FloatVectorDataPtr pixelChannelData = new FloatVectorData(); diff --git a/src/GafferImage/DeepState.cpp b/src/GafferImage/DeepState.cpp index a6ce7f75a34..3080a0c9003 100644 --- a/src/GafferImage/DeepState.cpp +++ b/src/GafferImage/DeepState.cpp @@ -948,27 +948,27 @@ void DeepState::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co const std::vector &channelNames = channelNamesData->readable(); ImagePlug::ChannelDataScope channelScope( context ); - if( ImageAlgo::channelExists( channelNames, "Z" ) ) + if( ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameZ ) ) { - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); inPlug()->channelDataPlug()->hash( h ); } else { h.append( false ); } - if( ImageAlgo::channelExists( channelNames, "ZBack" ) ) + if( ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameZBack ) ) { - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); inPlug()->channelDataPlug()->hash( h ); } else { h.append( false ); } - if( ImageAlgo::channelExists( channelNames, "A" ) ) + if( ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameA ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); inPlug()->channelDataPlug()->hash( h ); } else @@ -1008,20 +1008,20 @@ void DeepState::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte CompoundObjectPtr result = new CompoundObject; ImagePlug::ChannelDataScope channelScope( Context::current() ); - bool hasZ = ImageAlgo::channelExists( channelNames, "Z" ); + bool hasZ = ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameZ ); ConstFloatVectorDataPtr zData; if( hasZ ) { - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); zData = inPlug()->channelDataPlug()->getValue(); } ConstFloatVectorDataPtr zBackData; - bool hasZBack = ImageAlgo::channelExists( channelNames, "ZBack" ); + bool hasZBack = ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameZBack ); if( hasZBack ) { - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); zBackData = inPlug()->channelDataPlug()->getValue(); } else @@ -1052,10 +1052,10 @@ void DeepState::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte FloatVectorDataPtr sampleWeightsData = new FloatVectorData(); std::vector &sampleWeights = sampleWeightsData->writable(); - if( ImageAlgo::channelExists( channelNames, "A" ) ) + if( ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameA ) ) { ImagePlug::ChannelDataScope channelScope( Context::current() ); - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr alphaData = inPlug()->channelDataPlug()->getValue(); sampleWeights.resize( sampleOffsetsData->readable().back() ); @@ -1134,9 +1134,9 @@ void DeepState::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte } ConstFloatVectorDataPtr alphaData; - if( ImageAlgo::channelExists( channelNames, "A" ) ) + if( ImageAlgo::channelExists( channelNames, ImageAlgo::channelNameA ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); alphaData = inPlug()->channelDataPlug()->getValue(); } else diff --git a/src/GafferImage/DeepToFlat.cpp b/src/GafferImage/DeepToFlat.cpp index c842427dd7d..12ca61db104 100644 --- a/src/GafferImage/DeepToFlat.cpp +++ b/src/GafferImage/DeepToFlat.cpp @@ -49,6 +49,12 @@ using namespace IECore; using namespace Gaffer; using namespace GafferImage; +namespace +{ + std::string g_premultipliedAverageZName( "__premultipliedAverageZ" ); +} + + GAFFER_NODE_DEFINE_TYPE( DeepToFlat ); size_t DeepToFlat::g_firstPlugIndex = 0; @@ -155,21 +161,21 @@ void DeepToFlat::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *c } const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - if( channelName == "__premultipliedAverageZ" ) + if( channelName == g_premultipliedAverageZName ) { ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); inPlug()->channelDataPlug()->hash( h ); ConstStringVectorDataPtr channelNames = inPlug()->channelNames(); - if( ImageAlgo::channelExists( channelNames->readable(), "ZBack" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameZBack ) ) { - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); inPlug()->channelDataPlug()->hash( h ); } - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); inPlug()->channelDataPlug()->hash( h ); } } @@ -189,19 +195,19 @@ void DeepToFlat::compute( Gaffer::ValuePlug *output, const Gaffer::Context *cont } const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - if( channelName == "__premultipliedAverageZ" ) + if( channelName == g_premultipliedAverageZName ) { ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "Z" ); + channelScope.setChannelName( &ImageAlgo::channelNameZ ); FloatVectorDataPtr resultData = inPlug()->channelDataPlug()->getValue()->copy(); std::vector &result = resultData->writable(); ConstStringVectorDataPtr channelNames = inPlug()->channelNames(); - if( ImageAlgo::channelExists( channelNames->readable(), "ZBack" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameZBack ) ) { // If we have a ZBack channel, find the average depth of each sample - channelScope.setChannelName( "ZBack" ); + channelScope.setChannelName( &ImageAlgo::channelNameZBack ); ConstFloatVectorDataPtr zBackData = inPlug()->channelDataPlug()->getValue(); const std::vector &zBack = zBackData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) @@ -211,7 +217,7 @@ void DeepToFlat::compute( Gaffer::ValuePlug *output, const Gaffer::Context *cont } if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) { - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr alphaData = inPlug()->channelDataPlug()->getValue(); const std::vector &alpha = alphaData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) @@ -286,7 +292,7 @@ void DeepToFlat::hashChannelData( const GafferImage::ImagePlug *output, const Ga ImageProcessor::hashChannelData( output, context, h ); ImagePlug::ChannelDataScope channelScope( context ); - channelScope.setChannelName( "__premultipliedAverageZ" ); + channelScope.setChannelName( &g_premultipliedAverageZName ); flattenedChannelDataPlug()->hash( h ); } @@ -314,7 +320,7 @@ IECore::ConstFloatVectorDataPtr DeepToFlat::computeChannelData( const std::strin // But in this case, we know that the internal DeepState node will just do the filtering of whatever // it gets by querying this channel from its input channelData, and that will just hit our // intermediateChannelData plug, which has a special case to handle this - channelScope.setChannelName( "__premultipliedAverageZ" ); + channelScope.setChannelName( &g_premultipliedAverageZName ); FloatVectorDataPtr resultData = flattenedChannelDataPlug()->getValue()->copy(); std::vector &result = resultData->writable(); @@ -323,7 +329,7 @@ IECore::ConstFloatVectorDataPtr DeepToFlat::computeChannelData( const std::strin { // For consistency with other uses of Z, once we've got a properly filtered and merged Z, // we can unpremult it again - channelScope.setChannelName( "A" ); + channelScope.setChannelName( &ImageAlgo::channelNameA ); ConstFloatVectorDataPtr alphaData = flattenedChannelDataPlug()->getValue(); const std::vector &alpha = alphaData->readable(); for( unsigned int i = 0; i < result.size(); i++ ) diff --git a/src/GafferImage/Display.cpp b/src/GafferImage/Display.cpp index dba08d82238..462de67bcfa 100644 --- a/src/GafferImage/Display.cpp +++ b/src/GafferImage/Display.cpp @@ -444,7 +444,7 @@ void Display::affects( const Gaffer::Plug *input, AffectedPlugsContainer &output if( input == driverCountPlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } diff --git a/src/GafferImage/FilterAlgo.cpp b/src/GafferImage/FilterAlgo.cpp index 85619bfd4b0..e95509b31c6 100644 --- a/src/GafferImage/FilterAlgo.cpp +++ b/src/GafferImage/FilterAlgo.cpp @@ -73,8 +73,7 @@ class SmoothGaussian2D : public OIIO::Filter2D float operator()( float x, float y ) const override { - return gauss1d( x * m_radiusInverse.x ) * - gauss1d( y * m_radiusInverse.y ); + return gauss1d( x * m_radiusInverse.x ) * gauss1d( y * m_radiusInverse.y ); } bool separable() const override @@ -129,8 +128,7 @@ class FilterCubicSimple2D : public OIIO::Filter2D float operator()( float x, float y ) const override { - return cubicSimple( x * m_wrad_inv ) - * cubicSimple( y * m_hrad_inv ); + return cubicSimple( x * m_wrad_inv ) * cubicSimple( y * m_hrad_inv ); } bool separable() const override diff --git a/src/GafferImage/FlatImageProcessor.cpp b/src/GafferImage/FlatImageProcessor.cpp index 45835f4939f..92083225904 100644 --- a/src/GafferImage/FlatImageProcessor.cpp +++ b/src/GafferImage/FlatImageProcessor.cpp @@ -89,7 +89,7 @@ void FlatImageProcessor::hashDeep( const GafferImage::ImagePlug *parent, const G ImageProcessor::hashDeep( parent, context, h ); if( inPlugs() ) { - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { // We ignore unconnected inputs when determining the hash - this is the correct // behaviour for merge, and hopefully any other deep nodes that use inPlugs() @@ -112,7 +112,7 @@ bool FlatImageProcessor::computeDeep( const Gaffer::Context *context, const Imag const ImagePlug *badInput = nullptr; if( inPlugs() ) { - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->deepPlug()->getValue() ) { diff --git a/src/GafferImage/FlatToDeep.cpp b/src/GafferImage/FlatToDeep.cpp index e84e2d1c990..243db55c643 100644 --- a/src/GafferImage/FlatToDeep.cpp +++ b/src/GafferImage/FlatToDeep.cpp @@ -228,8 +228,8 @@ void FlatToDeep::hashChannelData( const GafferImage::ImagePlug *output, const Ga ) ); } - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zChannel ); h = inPlug()->channelDataPlug()->hash(); } } @@ -256,16 +256,16 @@ void FlatToDeep::hashChannelData( const GafferImage::ImagePlug *output, const Ga + zBackChannel + "\" found." ) ); } - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zBackChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zBackChannel ); h = inPlug()->channelDataPlug()->hash(); } else { thicknessPlug()->hash( h ); - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zChannel ); outPlug()->channelDataPlug()->hash(h); } } @@ -311,8 +311,8 @@ IECore::ConstFloatVectorDataPtr FlatToDeep::computeChannelData( const std::strin ) ); } - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zChannel ); return inPlug()->channelDataPlug()->getValue(); } } @@ -346,8 +346,8 @@ IECore::ConstFloatVectorDataPtr FlatToDeep::computeChannelData( const std::strin ) ); } - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zBackChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zBackChannel ); return inPlug()->channelDataPlug()->getValue(); } else @@ -355,8 +355,8 @@ IECore::ConstFloatVectorDataPtr FlatToDeep::computeChannelData( const std::strin // Compute ZBack by combining incoming Z with thickness float thickness = thicknessPlug()->getValue(); - reusedScope.setTileOrigin( tileOrigin ); - reusedScope.setChannelName( zChannel ); + reusedScope.setTileOrigin( &tileOrigin ); + reusedScope.setChannelName( &zChannel ); FloatVectorDataPtr resultData = outPlug()->channelDataPlug()->getValue()->copy(); vector &result = resultData->writable(); for( float &i : result ) @@ -388,4 +388,3 @@ IECore::ConstIntVectorDataPtr FlatToDeep::computeSampleOffsets( const Imath::V2i { return ImagePlug::flatTileSampleOffsets(); } - diff --git a/src/GafferImage/Format.cpp b/src/GafferImage/Format.cpp index 1cef4913d47..2a9e78ba752 100644 --- a/src/GafferImage/Format.cpp +++ b/src/GafferImage/Format.cpp @@ -55,21 +55,21 @@ FormatMap &formatMap() std::ostream &GafferImage::operator << ( std::ostream &os, GafferImage::Format const &format ) { - if( format.getDisplayWindow().min == Imath::V2i( 0 ) ) - { + if( format.getDisplayWindow().min == Imath::V2i( 0 ) ) + { os << format.getDisplayWindow().max.x << "x" << format.getDisplayWindow().max.y; - } - else - { + } + else + { os << format.getDisplayWindow().min << " - " << format.getDisplayWindow().max; - } + } - if( format.getPixelAspect() != 1.0 ) - { + if( format.getPixelAspect() != 1.0 ) + { os << ", " << format.getPixelAspect(); - } + } - return os; + return os; } void Format::registerFormat( const std::string &name, const Format &format ) diff --git a/src/GafferImage/FormatData.cpp b/src/GafferImage/FormatData.cpp index a202a834be3..6a71f149382 100644 --- a/src/GafferImage/FormatData.cpp +++ b/src/GafferImage/FormatData.cpp @@ -38,11 +38,20 @@ #include "GafferImage/TypeIds.h" +#include "Gaffer/Context.h" + #include "IECore/TypedData.h" #include "IECore/TypedData.inl" using namespace Imath; +namespace +{ + +Gaffer::Context::TypeDescription g_formatDataTypeDescription; + +} // namespace + namespace IECore { diff --git a/src/GafferImage/Grade.cpp b/src/GafferImage/Grade.cpp index 69f333f560b..fbde49d837a 100644 --- a/src/GafferImage/Grade.cpp +++ b/src/GafferImage/Grade.cpp @@ -200,14 +200,15 @@ void Grade::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs // Process the children of the compound plugs. for( unsigned int i = 0; i < 4; ++i ) { - if( input == blackPointPlug()->getChild(i) || - input == whitePointPlug()->getChild(i) || - input == liftPlug()->getChild(i) || - input == gainPlug()->getChild(i) || - input == multiplyPlug()->getChild(i) || - input == offsetPlug()->getChild(i) || - input == gammaPlug()->getChild(i) - ) + if( + input == blackPointPlug()->getChild(i) || + input == whitePointPlug()->getChild(i) || + input == liftPlug()->getChild(i) || + input == gainPlug()->getChild(i) || + input == multiplyPlug()->getChild(i) || + input == offsetPlug()->getChild(i) || + input == gammaPlug()->getChild(i) + ) { outputs.push_back( outPlug()->channelDataPlug() ); return; @@ -215,10 +216,11 @@ void Grade::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs } // Process all other plugs. - if( input == inPlug()->channelDataPlug() || - input == blackClampPlug() || - input == whiteClampPlug() - ) + if( + input == inPlug()->channelDataPlug() || + input == blackClampPlug() || + input == whiteClampPlug() + ) { outputs.push_back( outPlug()->channelDataPlug() ); return; diff --git a/src/GafferImage/ImageAlgo.cpp b/src/GafferImage/ImageAlgo.cpp index 705a2cb77ed..d102ff6c2a8 100644 --- a/src/GafferImage/ImageAlgo.cpp +++ b/src/GafferImage/ImageAlgo.cpp @@ -137,6 +137,13 @@ std::vector GafferImage::ImageAlgo::layerNames( const std::vectordeepPlug()->getValue() ) @@ -292,7 +299,7 @@ IECore::ConstCompoundObjectPtr GafferImage::ImageAlgo::tiles( const ImagePlug *i ImagePlug::ChannelDataScope channelDataScope( Gaffer::Context::current() ); for( unsigned int i = 0; i < channelNames.size(); i++ ) { - channelDataScope.setChannelName( channelNames[i] ); + channelDataScope.setChannelName( &channelNames[i] ); channelDataResults[i]->members()[tileI] = const_cast( imageP->channelDataPlug()->getValue().get() ); @@ -340,6 +347,3 @@ void GafferImage::ImageAlgo::throwIfSampleOffsetsMismatch( const IECore::IntVect } } } - - - diff --git a/src/GafferImage/ImageMetadata.cpp b/src/GafferImage/ImageMetadata.cpp index 864b50fd157..44b0f49b3e0 100644 --- a/src/GafferImage/ImageMetadata.cpp +++ b/src/GafferImage/ImageMetadata.cpp @@ -98,7 +98,7 @@ IECore::ConstCompoundDataPtr ImageMetadata::computeProcessedMetadata( const Gaff result->writable() = inputMetadata->readable(); std::string name; - for ( NameValuePlugIterator it( p ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( p ); !it.done(); ++it ) { IECore::DataPtr d = p->memberDataAndName( it->get(), name ); if ( d ) diff --git a/src/GafferImage/ImageNode.cpp b/src/GafferImage/ImageNode.cpp index be9ed6df6c2..dbeec5ea70e 100644 --- a/src/GafferImage/ImageNode.cpp +++ b/src/GafferImage/ImageNode.cpp @@ -311,7 +311,7 @@ void ImageNode::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outp if( input == enabledPlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { if( (*it)->getInput() ) { diff --git a/src/GafferImage/ImagePlug.cpp b/src/GafferImage/ImagePlug.cpp index 1ccd2eff743..544a452d473 100644 --- a/src/GafferImage/ImagePlug.cpp +++ b/src/GafferImage/ImagePlug.cpp @@ -309,10 +309,20 @@ ImagePlug::ChannelDataScope::ChannelDataScope( const Gaffer::ThreadState &thread void ImagePlug::ChannelDataScope::setTileOrigin( const V2i &tileOrigin ) { - set( tileOriginContextName, tileOrigin ); + setAllocated( tileOriginContextName, tileOrigin ); } void ImagePlug::ChannelDataScope::setChannelName( const std::string &channelName ) +{ + setAllocated( channelNameContextName, channelName ); +} + +void ImagePlug::ChannelDataScope::setTileOrigin( const V2i *tileOrigin ) +{ + set( tileOriginContextName, tileOrigin ); +} + +void ImagePlug::ChannelDataScope::setChannelName( const std::string *channelName ) { set( channelNameContextName, channelName ); } @@ -320,8 +330,8 @@ void ImagePlug::ChannelDataScope::setChannelName( const std::string &channelName IECore::ConstFloatVectorDataPtr ImagePlug::channelData( const std::string &channelName, const Imath::V2i &tile ) const { ChannelDataScope channelDataScope( Context::current() ); - channelDataScope.setChannelName( channelName ); - channelDataScope.setTileOrigin( tile ); + channelDataScope.setChannelName( &channelName ); + channelDataScope.setTileOrigin( &tile ); return channelDataPlug()->getValue(); } @@ -329,8 +339,8 @@ IECore::ConstFloatVectorDataPtr ImagePlug::channelData( const std::string &chann IECore::MurmurHash ImagePlug::channelDataHash( const std::string &channelName, const Imath::V2i &tile ) const { ChannelDataScope channelDataScope( Context::current() ); - channelDataScope.setChannelName( channelName ); - channelDataScope.setTileOrigin( tile ); + channelDataScope.setChannelName( &channelName ); + channelDataScope.setTileOrigin( &tile ); return channelDataPlug()->hash(); } @@ -397,13 +407,13 @@ IECore::MurmurHash ImagePlug::deepHash() const IECore::ConstIntVectorDataPtr ImagePlug::sampleOffsets( const Imath::V2i &tile ) const { ChannelDataScope channelDataScope( Context::current() ); - channelDataScope.setTileOrigin( tile ); + channelDataScope.setTileOrigin( &tile ); return sampleOffsetsPlug()->getValue(); } IECore::MurmurHash ImagePlug::sampleOffsetsHash( const Imath::V2i &tile ) const { ChannelDataScope channelDataScope( Context::current() ); - channelDataScope.setTileOrigin( tile ); + channelDataScope.setTileOrigin( &tile ); return sampleOffsetsPlug()->hash(); } diff --git a/src/GafferImage/ImageReader.cpp b/src/GafferImage/ImageReader.cpp index ebd62bbc676..579d980e04d 100644 --- a/src/GafferImage/ImageReader.cpp +++ b/src/GafferImage/ImageReader.cpp @@ -341,7 +341,7 @@ void ImageReader::affects( const Plug *input, AffectedPlugsContainer &outputs ) input == endModePlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } diff --git a/src/GafferImage/ImageSampler.cpp b/src/GafferImage/ImageSampler.cpp index f014e145d1a..1b67d442098 100644 --- a/src/GafferImage/ImageSampler.cpp +++ b/src/GafferImage/ImageSampler.cpp @@ -154,7 +154,7 @@ void ImageSampler::affects( const Gaffer::Plug *input, AffectedPlugsContainer &o input->parent() == pixelPlug() ) { - for( ValuePlugIterator componentIt( colorPlug() ); !componentIt.done(); ++componentIt ) + for( ValuePlug::Iterator componentIt( colorPlug() ); !componentIt.done(); ++componentIt ) { outputs.push_back( componentIt->get() ); } diff --git a/src/GafferImage/ImageStats.cpp b/src/GafferImage/ImageStats.cpp index d7539ca5703..11d518fafeb 100644 --- a/src/GafferImage/ImageStats.cpp +++ b/src/GafferImage/ImageStats.cpp @@ -290,7 +290,7 @@ void ImageStats::hash( const ValuePlug *output, const Context *context, IECore:: h.append( statIndex ); ImagePlug::ChannelDataScope s( context ); - s.setChannelName( channelName ); + s.setChannelName( &channelName ); allStatsPlug()->hash( h ); return; } @@ -366,7 +366,7 @@ void ImageStats::compute( ValuePlug *output, const Context *context ) const int statIndex = ( parent == averagePlug() ) ? 2 : ( parent == maxPlug() ); ImagePlug::ChannelDataScope s( context ); - s.setChannelName( channelName ); + s.setChannelName( &channelName ); Imath::V3d stats = boost::static_pointer_cast( allStatsPlug()->getValue() )->readable(); static_cast( output )->setValue( stats[ statIndex ] ); return; diff --git a/src/GafferImage/ImageTransform.cpp b/src/GafferImage/ImageTransform.cpp index b56fdb08bc8..12fef454350 100644 --- a/src/GafferImage/ImageTransform.cpp +++ b/src/GafferImage/ImageTransform.cpp @@ -108,7 +108,7 @@ class ImageTransform::ChainingScope : boost::noncopyable public : ChainingScope( const Gaffer::Context *context, const ImageTransform *imageTransform ) - : m_chained( context->get( chainedContextName, false ) ) + : m_chained( context->get( chainedContextName, false ) ), m_true( true ) { if( !m_chained ) { @@ -117,7 +117,7 @@ class ImageTransform::ChainingScope : boost::noncopyable // We're the bottom of a chain. Tell the upstream // nodes they've been chained. m_scope.emplace( context ); - m_scope->set( chainedContextName, true ); + m_scope->set( chainedContextName, &m_true ); } } else @@ -156,6 +156,7 @@ class ImageTransform::ChainingScope : boost::noncopyable // an EditableScope when we don't need one. boost::optional m_scope; bool m_chained; + bool m_true; }; diff --git a/src/GafferImage/ImageWriter.cpp b/src/GafferImage/ImageWriter.cpp index ca4527f7fa6..2e36ee3b126 100644 --- a/src/GafferImage/ImageWriter.cpp +++ b/src/GafferImage/ImageWriter.cpp @@ -1535,7 +1535,8 @@ void ImageWriter::execute() const // `colorSpaceNode()`. Context::EditableScope colorSpaceScope( Context::current() ); - colorSpaceScope.set( "__imageWriter:colorSpace", colorSpace() ); + std::string colorSpaceStr = colorSpace(); + colorSpaceScope.set( "__imageWriter:colorSpace", &colorSpaceStr ); // Create an OIIO::ImageOutput diff --git a/src/GafferImage/Merge.cpp b/src/GafferImage/Merge.cpp index 1a0be0b9123..d42366dd56e 100644 --- a/src/GafferImage/Merge.cpp +++ b/src/GafferImage/Merge.cpp @@ -147,22 +147,22 @@ struct OpMax template< class Functor, typename... Args > typename Functor::ReturnType dispatchOperation( Merge::Operation op, Functor &&functor, Args&&... args ) { - switch( op ) - { - case Merge::Add : return functor.template operator()( std::forward( args )... ); - case Merge::Atop : return functor.template operator()( std::forward( args )... ); - case Merge::Divide : return functor.template operator()( std::forward( args )... ); - case Merge::In : return functor.template operator()( std::forward( args )... ); - case Merge::Out : return functor.template operator()( std::forward( args )... ); - case Merge::Mask : return functor.template operator()( std::forward( args )... ); - case Merge::Matte : return functor.template operator()( std::forward( args )... ); - case Merge::Multiply : return functor.template operator()( std::forward( args )... ); - case Merge::Over : return functor.template operator()( std::forward( args )... ); - case Merge::Subtract : return functor.template operator()( std::forward( args )... ); - case Merge::Difference : return functor.template operator()( std::forward( args )... ); - case Merge::Under : return functor.template operator()( std::forward( args )... ); - case Merge::Min : return functor.template operator()( std::forward( args )... ); - case Merge::Max : return functor.template operator()( std::forward( args )... ); + switch( op ) + { + case Merge::Add : return functor.template operator()( std::forward( args )... ); + case Merge::Atop : return functor.template operator()( std::forward( args )... ); + case Merge::Divide : return functor.template operator()( std::forward( args )... ); + case Merge::In : return functor.template operator()( std::forward( args )... ); + case Merge::Out : return functor.template operator()( std::forward( args )... ); + case Merge::Mask : return functor.template operator()( std::forward( args )... ); + case Merge::Matte : return functor.template operator()( std::forward( args )... ); + case Merge::Multiply : return functor.template operator()( std::forward( args )... ); + case Merge::Over : return functor.template operator()( std::forward( args )... ); + case Merge::Subtract : return functor.template operator()( std::forward( args )... ); + case Merge::Difference : return functor.template operator()( std::forward( args )... ); + case Merge::Under : return functor.template operator()( std::forward( args )... ); + case Merge::Min : return functor.template operator()( std::forward( args )... ); + case Merge::Max : return functor.template operator()( std::forward( args )... ); default: throw InvalidArgumentException( boost::str( boost::format( "Invalid Merge Operation : %1%" ) % op ) ); } @@ -664,7 +664,7 @@ void Merge::hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer:: FlatImageProcessor::hashDataWindow( output, context, h ); operationPlug()->hash( h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -678,7 +678,7 @@ Imath::Box2i Merge::computeDataWindow( const Gaffer::Context *context, const Ima Imath::Box2i dataWindow; Operation op = (Operation)operationPlug()->getValue(); bool first = true; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -694,7 +694,7 @@ void Merge::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer { FlatImageProcessor::hashChannelNames( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -708,7 +708,7 @@ IECore::ConstStringVectorDataPtr Merge::computeChannelNames( const Gaffer::Conte IECore::StringVectorDataPtr outChannelStrVectorData( new IECore::StringVectorData() ); std::vector &outChannels( outChannelStrVectorData->writable() ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -756,7 +756,7 @@ void Merge::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer: finalTileDataWindowLocal = boxIntersection( fullBound, finalDataWindowLocal ); } - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput() ) { @@ -859,7 +859,7 @@ IECore::ConstFloatVectorDataPtr Merge::computeChannelData( const std::string &ch bool partialBound = false; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( !(*it)->getInput() ) { diff --git a/src/GafferImage/Mix.cpp b/src/GafferImage/Mix.cpp index c9efdc53467..3e3b20bf28c 100644 --- a/src/GafferImage/Mix.cpp +++ b/src/GafferImage/Mix.cpp @@ -148,7 +148,7 @@ void Mix::hashDataWindow( const GafferImage::ImagePlug *output, const Gaffer::Co ImageProcessor::hashDataWindow( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->dataWindowPlug()->hash( h ); } @@ -167,7 +167,7 @@ Imath::Box2i Mix::computeDataWindow( const Gaffer::Context *context, const Image } Imath::Box2i dataWindow; - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { // We don't need to check that the plug is connected here as unconnected plugs don't have data windows. dataWindow.extendBy( (*it)->dataWindowPlug()->getValue() ); @@ -192,7 +192,7 @@ void Mix::hashChannelNames( const GafferImage::ImagePlug *output, const Gaffer:: ImageProcessor::hashChannelNames( output, context, h ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -216,7 +216,7 @@ IECore::ConstStringVectorDataPtr Mix::computeChannelNames( const Gaffer::Context IECore::StringVectorDataPtr outChannelStrVectorData( new IECore::StringVectorData() ); std::vector &outChannels( outChannelStrVectorData->writable() ); - for( ImagePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ImagePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -307,7 +307,7 @@ void Mix::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::C maskPlug()->deepPlug()->hash( h ); // The sample offsets need to be accessed in a context with tileOrigin, but without the channel name - c.setTileOrigin( tileOrigin ); + c.setTileOrigin( &tileOrigin ); outPlug()->sampleOffsetsPlug()->hash( h ); maskPlug()->sampleOffsetsPlug()->hash( h ); } @@ -388,7 +388,7 @@ IECore::ConstFloatVectorDataPtr Mix::computeChannelData( const std::string &chan } // The sample offsets need to be accessed in a context with tileOrigin, but without the channel name - c.setTileOrigin( tileOrigin ); + c.setTileOrigin( &tileOrigin ); if( deep ) { diff --git a/src/GafferImage/Offset.cpp b/src/GafferImage/Offset.cpp index c8d26063c63..06b44f421e5 100644 --- a/src/GafferImage/Offset.cpp +++ b/src/GafferImage/Offset.cpp @@ -149,7 +149,8 @@ void Offset::hashChannelData( const GafferImage::ImagePlug *parent, const Gaffer const V2i tileOrigin = context->get( ImagePlug::tileOriginContextName ); if( offset.x % ImagePlug::tileSize() == 0 && offset.y % ImagePlug::tileSize() == 0 ) { - offsetScope.setTileOrigin( tileOrigin - offset ); + V2i offsetOrigin = tileOrigin - offset; + offsetScope.setTileOrigin( &offsetOrigin ); h = inPlug()->channelDataPlug()->hash(); } else @@ -173,7 +174,7 @@ void Offset::hashChannelData( const GafferImage::ImagePlug *parent, const Gaffer { for( inTileOrigin.x = ImagePlug::tileOrigin( inBound.min ).x; inTileOrigin.x < inBound.max.x; inTileOrigin.x += ImagePlug::tileSize() ) { - offsetScope.setTileOrigin( inTileOrigin ); + offsetScope.setTileOrigin( &inTileOrigin ); inPlug()->channelDataPlug()->hash( h ); } } @@ -183,7 +184,7 @@ void Offset::hashChannelData( const GafferImage::ImagePlug *parent, const Gaffer // For performance reasons, instead of including hashing sampleOffsets for every input tile, we // just include the output sample offsets, which already depends on the input sample offsets and // the deep plug - offsetScope.setTileOrigin( tileOrigin ); + offsetScope.setTileOrigin( &tileOrigin ); offsetScope.remove( ImagePlug::channelNameContextName ); outPlug()->sampleOffsetsPlug()->hash( h ); } @@ -196,7 +197,8 @@ IECore::ConstFloatVectorDataPtr Offset::computeChannelData( const std::string &c const V2i offset = offsetPlug()->getValue(); if( offset.x % ImagePlug::tileSize() == 0 && offset.y % ImagePlug::tileSize() == 0 ) { - offsetScope.setTileOrigin( tileOrigin - offset ); + V2i offsetOrigin = tileOrigin - offset; + offsetScope.setTileOrigin( &offsetOrigin ); return inPlug()->channelDataPlug()->getValue(); } @@ -219,7 +221,7 @@ IECore::ConstFloatVectorDataPtr Offset::computeChannelData( const std::string &c { offsetScope.remove( ImagePlug::channelNameContextName ); outSampleOffsetsData = outPlug()->sampleOffsetsPlug()->getValue(); - offsetScope.setChannelName( channelName ); + offsetScope.setChannelName( &channelName ); outData->writable().resize( outSampleOffsetsData->readable().back() ); } @@ -230,7 +232,7 @@ IECore::ConstFloatVectorDataPtr Offset::computeChannelData( const std::string &c { for( inTileOrigin.x = ImagePlug::tileOrigin( inBound.min ).x; inTileOrigin.x < inBound.max.x; inTileOrigin.x += ImagePlug::tileSize() ) { - offsetScope.setTileOrigin( inTileOrigin ); + offsetScope.setTileOrigin( &inTileOrigin ); ConstFloatVectorDataPtr inData = inPlug()->channelDataPlug()->getValue(); const float *in = &inData->readable().front(); @@ -260,7 +262,7 @@ IECore::ConstFloatVectorDataPtr Offset::computeChannelData( const std::string &c { offsetScope.remove( ImagePlug::channelNameContextName ); ConstIntVectorDataPtr inSampleOffsetsData = inPlug()->sampleOffsetsPlug()->getValue(); - offsetScope.setChannelName( channelName ); + offsetScope.setChannelName( &channelName ); const std::vector &inSampleOffsets = inSampleOffsetsData->readable(); const std::vector &outSampleOffsets = outSampleOffsetsData->readable(); for( V2i inScanlineOrigin = inRegion.min; inScanlineOrigin.y < inRegion.max.y; inScanlineOrigin.y++ ) @@ -302,7 +304,8 @@ void Offset::hashSampleOffsets( const GafferImage::ImagePlug *parent, const Gaff const V2i tileOrigin = context->get( ImagePlug::tileOriginContextName ); if( offset.x % ImagePlug::tileSize() == 0 && offset.y % ImagePlug::tileSize() == 0 ) { - offsetScope.setTileOrigin( tileOrigin - offset ); + V2i offsetOrigin = tileOrigin - offset; + offsetScope.setTileOrigin( &offsetOrigin ); h = inPlug()->sampleOffsetsPlug()->hash(); } else @@ -328,7 +331,7 @@ void Offset::hashSampleOffsets( const GafferImage::ImagePlug *parent, const Gaff { for( inTileOrigin.x = ImagePlug::tileOrigin( inBound.min ).x; inTileOrigin.x < inBound.max.x; inTileOrigin.x += ImagePlug::tileSize() ) { - offsetScope.setTileOrigin( inTileOrigin ); + offsetScope.setTileOrigin( &inTileOrigin ); inPlug()->sampleOffsetsPlug()->hash( h ); } } @@ -349,7 +352,8 @@ IECore::ConstIntVectorDataPtr Offset::computeSampleOffsets( const Imath::V2i &ti const V2i offset = offsetPlug()->getValue(); if( ( offset.x % ImagePlug::tileSize() == 0 && offset.y % ImagePlug::tileSize() == 0 ) ) { - offsetScope.setTileOrigin( tileOrigin - offset ); + V2i offsetOrigin = tileOrigin - offset; + offsetScope.setTileOrigin( &offsetOrigin ); return inPlug()->sampleOffsetsPlug()->getValue(); } @@ -370,7 +374,7 @@ IECore::ConstIntVectorDataPtr Offset::computeSampleOffsets( const Imath::V2i &ti { for( inTileOrigin.x = ImagePlug::tileOrigin( inBound.min ).x; inTileOrigin.x < inBound.max.x; inTileOrigin.x += ImagePlug::tileSize() ) { - offsetScope.setTileOrigin( inTileOrigin ); + offsetScope.setTileOrigin( &inTileOrigin ); ConstIntVectorDataPtr inData = inPlug()->sampleOffsetsPlug()->getValue(); const std::vector &in = inData->readable(); diff --git a/src/GafferImage/OpenColorIOTransform.cpp b/src/GafferImage/OpenColorIOTransform.cpp index e1513c38ddf..ec688cbfc61 100644 --- a/src/GafferImage/OpenColorIOTransform.cpp +++ b/src/GafferImage/OpenColorIOTransform.cpp @@ -211,7 +211,7 @@ OpenColorIO::ConstContextRcPtr OpenColorIOTransform::ocioContext(OpenColorIO::Co std::string name; std::string value; - for( NameValuePlugIterator it( p ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( p ); !it.done(); ++it ) { IECore::DataPtr d = p->memberDataAndName( it->get(), name ); if( d ) diff --git a/src/GafferImage/OpenImageIOReader.cpp b/src/GafferImage/OpenImageIOReader.cpp index bf32bd98633..c33e599aa79 100644 --- a/src/GafferImage/OpenImageIOReader.cpp +++ b/src/GafferImage/OpenImageIOReader.cpp @@ -728,10 +728,10 @@ FilePtr retrieveFile( std::string &fileName, OpenImageIOReader::MissingFrameMode } // setup a context with the new frame - ContextPtr holdContext = new Context( *context, Context::Shared ); - holdContext->setFrame( *fIt ); + Context::EditableScope holdScope( context ); + holdScope.setFrame( *fIt ); - return retrieveFile( fileName, OpenImageIOReader::Error, node, holdContext.get() ); + return retrieveFile( fileName, OpenImageIOReader::Error, node, holdScope.context() ); } // if we got here, there was no suitable file sequence @@ -879,7 +879,7 @@ void OpenImageIOReader::affects( const Gaffer::Plug *input, AffectedPlugsContain if( input == fileNamePlug() || input == refreshCountPlug() || input == missingFrameModePlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } @@ -1180,7 +1180,7 @@ IECore::ConstIntVectorDataPtr OpenImageIOReader::computeSampleOffsets( const Ima std::string channelName(""); // TODO - should have better interface for selecting sampleOffsets file->findTile( channelName, tileOrigin, tileBatchIndex, subIndex ); - c.set( g_tileBatchIndexContextName, tileBatchIndex ); + c.set( g_tileBatchIndexContextName, &tileBatchIndex ); ConstObjectVectorPtr tileBatch = tileBatchPlug()->getValue(); @@ -1229,7 +1229,7 @@ IECore::ConstFloatVectorDataPtr OpenImageIOReader::computeChannelData( const std int subIndex; file->findTile( channelName, tileOrigin, tileBatchIndex, subIndex ); - c.set( g_tileBatchIndexContextName, tileBatchIndex ); + c.set( g_tileBatchIndexContextName, &tileBatchIndex ); ConstObjectVectorPtr tileBatch = tileBatchPlug()->getValue(); ConstObjectPtr curTileChannel; diff --git a/src/GafferImage/Premultiply.cpp b/src/GafferImage/Premultiply.cpp index c49450a538c..4d98f64a063 100644 --- a/src/GafferImage/Premultiply.cpp +++ b/src/GafferImage/Premultiply.cpp @@ -74,8 +74,9 @@ void Premultiply::affects( const Gaffer::Plug *input, AffectedPlugsContainer &ou { ChannelDataProcessor::affects( input, outputs ); - if( input == inPlug()->channelDataPlug() || - input == alphaChannelPlug() ) + if( + input == inPlug()->channelDataPlug() || + input == alphaChannelPlug() ) { outputs.push_back( outPlug()->channelDataPlug() ); } @@ -90,7 +91,7 @@ void Premultiply::hashChannelData( const GafferImage::ImagePlug *output, const G inPlug()->channelDataPlug()->hash( h ); ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( alphaChannel ); + channelDataScope.setChannelName( &alphaChannel ); inPlug()->channelDataPlug()->hash( h ); } @@ -119,7 +120,7 @@ void Premultiply::processChannelData( const Gaffer::Context *context, const Imag } ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( alphaChannel ); + channelDataScope.setChannelName( &alphaChannel ); ConstFloatVectorDataPtr aData = inPlug()->channelDataPlug()->getValue(); const std::vector &a = aData->readable(); diff --git a/src/GafferImage/RankFilter.cpp b/src/GafferImage/RankFilter.cpp index 44d99c232e5..8a0b8750de4 100644 --- a/src/GafferImage/RankFilter.cpp +++ b/src/GafferImage/RankFilter.cpp @@ -342,7 +342,7 @@ void RankFilter::hashChannelData( const GafferImage::ImagePlug *parent, const Ga if( masterChannel != "" ) { ImagePlug::ChannelDataScope pixelOffsetsScope( context ); - pixelOffsetsScope.setChannelName( masterChannel ); + pixelOffsetsScope.setChannelName( &masterChannel ); pixelOffsetsPlug()->hash( h ); } @@ -377,7 +377,7 @@ IECore::ConstFloatVectorDataPtr RankFilter::computeChannelData( const std::strin ConstV2iVectorDataPtr pixelOffsets; { ImagePlug::ChannelDataScope pixelOffsetsScope( context ); - pixelOffsetsScope.setChannelName( masterChannel ); + pixelOffsetsScope.setChannelName( &masterChannel ); pixelOffsets = pixelOffsetsPlug()->getValue(); } diff --git a/src/GafferImage/Shuffle.cpp b/src/GafferImage/Shuffle.cpp index 2229cb492da..922176f40ef 100644 --- a/src/GafferImage/Shuffle.cpp +++ b/src/GafferImage/Shuffle.cpp @@ -155,7 +155,7 @@ void Shuffle::hashChannelNames( const GafferImage::ImagePlug *parent, const Gaff { ImageProcessor::hashChannelNames( parent, context, h ); inPlug()->channelNamesPlug()->hash( h ); - for( ChannelPlugIterator it( channelsPlug() ); !it.done(); ++it ) + for( ChannelPlug::Iterator it( channelsPlug() ); !it.done(); ++it ) { (*it)->outPlug()->hash( h ); } @@ -165,7 +165,7 @@ IECore::ConstStringVectorDataPtr Shuffle::computeChannelNames( const Gaffer::Con { StringVectorDataPtr resultData = inPlug()->channelNamesPlug()->getValue()->copy(); vector &result = resultData->writable(); - for( ChannelPlugIterator it( channelsPlug() ); !it.done(); ++it ) + for( ChannelPlug::Iterator it( channelsPlug() ); !it.done(); ++it ) { string channelName = (*it)->outPlug()->getValue(); if( channelName != "" && find( result.begin(), result.end(), channelName ) == result.end() ) @@ -197,7 +197,7 @@ void Shuffle::hashChannelData( const GafferImage::ImagePlug *parent, const Gaffe } else { - channelDataScope.setTileOrigin( tileOrigin ); + channelDataScope.setTileOrigin( &tileOrigin ); inPlug()->sampleOffsetsPlug()->hash( h ); h.append( c == "__white" ); } @@ -205,7 +205,7 @@ void Shuffle::hashChannelData( const GafferImage::ImagePlug *parent, const Gaffe else { ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( c ); + channelDataScope.setChannelName( &c ); h = inPlug()->channelDataPlug()->hash(); } } @@ -227,7 +227,7 @@ IECore::ConstFloatVectorDataPtr Shuffle::computeChannelData( const std::string & } else { - channelDataScope.setTileOrigin( tileOrigin ); + channelDataScope.setTileOrigin( &tileOrigin ); ConstIntVectorDataPtr sampleOffsets = inPlug()->sampleOffsetsPlug()->getValue(); FloatVectorDataPtr result = new FloatVectorData(); @@ -242,7 +242,7 @@ IECore::ConstFloatVectorDataPtr Shuffle::computeChannelData( const std::string & else { ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( c ); + channelDataScope.setChannelName( &c ); return inPlug()->channelDataPlug()->getValue(); } } @@ -250,7 +250,7 @@ IECore::ConstFloatVectorDataPtr Shuffle::computeChannelData( const std::string & std::string Shuffle::inChannelName( const std::string &outChannelName ) const { ImagePlug::GlobalScope s( Context::current() ); - for( ChannelPlugIterator it( channelsPlug() ); !it.done(); ++it ) + for( ChannelPlug::Iterator it( channelsPlug() ); !it.done(); ++it ) { if( (*it)->outPlug()->getValue() == outChannelName ) { diff --git a/src/GafferImage/Unpremultiply.cpp b/src/GafferImage/Unpremultiply.cpp index 0fa78f0ac23..3a271ce52e0 100644 --- a/src/GafferImage/Unpremultiply.cpp +++ b/src/GafferImage/Unpremultiply.cpp @@ -74,8 +74,10 @@ void Unpremultiply::affects( const Gaffer::Plug *input, AffectedPlugsContainer & { ChannelDataProcessor::affects( input, outputs ); - if( input == inPlug()->channelDataPlug() || - input == alphaChannelPlug() ) + if( + input == inPlug()->channelDataPlug() || + input == alphaChannelPlug() + ) { outputs.push_back( outPlug()->channelDataPlug() ); } @@ -90,7 +92,7 @@ void Unpremultiply::hashChannelData( const GafferImage::ImagePlug *output, const inPlug()->channelDataPlug()->hash( h ); ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( alphaChannel ); + channelDataScope.setChannelName( &alphaChannel ); inPlug()->channelDataPlug()->hash( h ); } @@ -119,7 +121,7 @@ void Unpremultiply::processChannelData( const Gaffer::Context *context, const Im } ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setChannelName( alphaChannel ); + channelDataScope.setChannelName( &alphaChannel ); ConstFloatVectorDataPtr aData = inPlug()->channelDataPlug()->getValue(); const std::vector &a = aData->readable(); diff --git a/src/GafferImage/VectorWarp.cpp b/src/GafferImage/VectorWarp.cpp index 87f96f93ffd..310e9b66f87 100644 --- a/src/GafferImage/VectorWarp.cpp +++ b/src/GafferImage/VectorWarp.cpp @@ -215,21 +215,21 @@ void VectorWarp::hashEngine( const Imath::V2i &tileOrigin, const Gaffer::Context ImagePlug::ChannelDataScope channelDataScope( context ); - if( ImageAlgo::channelExists( channelNames->readable(), "R" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameR ) ) { - channelDataScope.setChannelName( "R" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameR ); vectorPlug()->channelDataPlug()->hash( h ); } - if( ImageAlgo::channelExists( channelNames->readable(), "G" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameG ) ) { - channelDataScope.setChannelName( "G" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameG ); vectorPlug()->channelDataPlug()->hash( h ); } - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { - channelDataScope.setChannelName( "A" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameA ); vectorPlug()->channelDataPlug()->hash( h ); } @@ -256,23 +256,23 @@ const Warp::Engine *VectorWarp::computeEngine( const Imath::V2i &tileOrigin, con ImagePlug::ChannelDataScope channelDataScope( context ); ConstFloatVectorDataPtr xData = ImagePlug::blackTile(); - if( ImageAlgo::channelExists( channelNames->readable(), "R" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameR ) ) { - channelDataScope.setChannelName( "R" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameR ); xData = vectorPlug()->channelDataPlug()->getValue(); } ConstFloatVectorDataPtr yData = ImagePlug::blackTile(); - if( ImageAlgo::channelExists( channelNames->readable(), "G" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameG ) ) { - channelDataScope.setChannelName( "G" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameG ); yData = vectorPlug()->channelDataPlug()->getValue(); } ConstFloatVectorDataPtr aData = ImagePlug::whiteTile(); - if( ImageAlgo::channelExists( channelNames->readable(), "A" ) ) + if( ImageAlgo::channelExists( channelNames->readable(), ImageAlgo::channelNameA ) ) { - channelDataScope.setChannelName( "A" ); + channelDataScope.setChannelName( &ImageAlgo::channelNameA ); aData = vectorPlug()->channelDataPlug()->getValue(); } @@ -309,4 +309,3 @@ bool VectorWarp::computeDeep( const Gaffer::Context *context, const ImagePlug *p } return false; } - diff --git a/src/GafferImage/Warp.cpp b/src/GafferImage/Warp.cpp index b9fd7a3031d..9e168fe2640 100644 --- a/src/GafferImage/Warp.cpp +++ b/src/GafferImage/Warp.cpp @@ -67,7 +67,7 @@ namespace { if( BufferAlgo::intersects( dataWindow, Box2i( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ) ) ) { - tileScope.setTileOrigin( tileOrigin ); + tileScope.setTileOrigin( &tileOrigin ); plug->hash( h ); } } @@ -76,7 +76,7 @@ namespace { if( BufferAlgo::intersects( dataWindow, Box2i( tileOrigin, tileOrigin + V2i( ImagePlug::tileSize() ) ) ) ) { - tileScope.setTileOrigin( tileOrigin ); + tileScope.setTileOrigin( &tileOrigin ); return plug->getValue(); } else @@ -704,4 +704,3 @@ void Warp::hashEngine( const Imath::V2i &tileOrigin, const Gaffer::Context *cont { FlatImageProcessor::hash( enginePlug(), context, h ); } - diff --git a/src/GafferImageTest/ContextSanitiser.cpp b/src/GafferImageTest/ContextSanitiser.cpp index 5ebbf354a5f..b1e631ce118 100644 --- a/src/GafferImageTest/ContextSanitiser.cpp +++ b/src/GafferImageTest/ContextSanitiser.cpp @@ -73,18 +73,18 @@ void ContextSanitiser::processStarted( const Gaffer::Process *process ) { if( process->plug() == image->sampleOffsetsPlug() ) { - if( process->context()->get( ImagePlug::channelNameContextName, nullptr ) ) + if( process->context()->getIfExists( ImagePlug::channelNameContextName ) ) { warn( *process, ImagePlug::channelNameContextName ); } } else if( process->plug() != image->channelDataPlug() ) { - if( process->context()->get( ImagePlug::channelNameContextName, nullptr ) ) + if( process->context()->getIfExists( ImagePlug::channelNameContextName ) ) { warn( *process, ImagePlug::channelNameContextName ); } - if( process->context()->get( ImagePlug::tileOriginContextName, nullptr ) ) + if( process->context()->getIfExists( ImagePlug::tileOriginContextName ) ) { warn( *process, ImagePlug::tileOriginContextName ); } diff --git a/src/GafferImageTestModule/GafferImageTestModule.cpp b/src/GafferImageTestModule/GafferImageTestModule.cpp index 6f7a4ff38a4..d7d3674f4e9 100644 --- a/src/GafferImageTestModule/GafferImageTestModule.cpp +++ b/src/GafferImageTestModule/GafferImageTestModule.cpp @@ -38,8 +38,11 @@ #include "GafferImageTest/ContextSanitiser.h" +#include "GafferTest/ContextTest.h" + #include "GafferImage/ImageAlgo.h" #include "GafferImage/ImagePlug.h" +#include "GafferImage/Format.h" #include "Gaffer/Node.h" @@ -99,6 +102,14 @@ boost::signals::connection connectProcessTilesToPlugDirtiedSignal( GafferImage:: return const_cast( node )->plugDirtiedSignal().connect( boost::bind( &processTilesOnDirty, ::_1, image ) ); } +void testEditableScopeForFormat() +{ + GafferTest::testEditableScopeTyped( + Format( Imath::Box2i( Imath::V2i( 1, 2 ), Imath::V2i( 1, 2 ) ), 1 ), + Format( Imath::Box2i( Imath::V2i( 3, 5 ), Imath::V2i( 1920, 1080 ) ), 1.6 ) + ); +} + } // namespace BOOST_PYTHON_MODULE( _GafferImageTest ) @@ -109,4 +120,5 @@ BOOST_PYTHON_MODULE( _GafferImageTest ) def( "processTiles", &processTilesWrapper ); def( "connectProcessTilesToPlugDirtiedSignal", &connectProcessTilesToPlugDirtiedSignal ); + def( "testEditableScopeForFormat", &testEditableScopeForFormat ); } diff --git a/src/GafferImageUI/ImageGadget.cpp b/src/GafferImageUI/ImageGadget.cpp index fdef37be241..4a226f06a59 100644 --- a/src/GafferImageUI/ImageGadget.cpp +++ b/src/GafferImageUI/ImageGadget.cpp @@ -194,7 +194,7 @@ class ImageGadget::TileShader : public IECore::RefCounted } } - ~TileShader() + ~TileShader() override { if( m_lut3dTextureID ) { @@ -1073,7 +1073,7 @@ void ImageGadget::updateTiles() ImagePlug::ChannelDataScope channelScope( Context::current() ); for( auto &channelName : channelsToCompute ) { - channelScope.setChannelName( channelName ); + channelScope.setChannelName( &channelName ); Tile &tile = m_tiles[TileIndex(tileOrigin, channelName)]; updates.push_back( tile.computeUpdate( image ) ); } diff --git a/src/GafferImageUI/ImageView.cpp b/src/GafferImageUI/ImageView.cpp index 34bc4aeb91a..1ac5613e1c8 100644 --- a/src/GafferImageUI/ImageView.cpp +++ b/src/GafferImageUI/ImageView.cpp @@ -45,15 +45,19 @@ #include "GafferImage/Grade.h" #include "GafferImage/ImagePlug.h" #include "GafferImage/ImageSampler.h" +#include "GafferImage/ImageStats.h" #include "GafferUI/Gadget.h" #include "GafferUI/Pointer.h" #include "GafferUI/StandardStyle.h" #include "GafferUI/Style.h" +#include "Gaffer/ArrayPlug.h" #include "Gaffer/Context.h" #include "Gaffer/DeleteContextVariables.h" #include "Gaffer/StringPlug.h" +#include "Gaffer/BoxPlug.h" +#include "Gaffer/Metadata.h" #include "IECoreGL/IECoreGL.h" #include "IECoreGL/Shader.h" @@ -62,6 +66,7 @@ #include "IECoreGL/TextureLoader.h" #include "IECoreGL/ToGLTextureConverter.h" +#include "IECore/NullObject.h" #include "IECore/BoxAlgo.h" #include "IECore/BoxOps.h" #include "IECore/FastFloat.h" @@ -191,6 +196,8 @@ class ImageView::ChannelChooser : public boost::signals::trackable namespace { +IECore::InternedString g_hoveredKey( "__hovered" ); + class V2fContextVariable : public Gaffer::ComputeNode { @@ -244,6 +251,7 @@ class V2fContextVariable : public Gaffer::ComputeNode ComputeNode::hash( output, context, h ); if( output->parent() == outPlug() ) { + // TODO - replace with context->variableHash in Gaffer 0.60 const std::string name = namePlug()->getValue(); h.append( context->get( name, V2f( 0 ) ) ); } @@ -275,8 +283,902 @@ GAFFER_NODE_DEFINE_TYPE( V2fContextVariable ) IE_CORE_DECLAREPTR( V2fContextVariable ) +class Box2iContextVariable : public Gaffer::ComputeNode +{ + + public : + + Box2iContextVariable( const std::string &name = "Box2iContextVariable" ) + : ComputeNode( name ) + { + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new StringPlug( "name" ) ); + addChild( new Box2iPlug( "out", Plug::Out ) ); + } + + GAFFER_NODE_DECLARE_TYPE( Box2iContextVariable, Box2iContextVariableTypeId, ComputeNode ); + + StringPlug *namePlug() + { + return getChild( g_firstPlugIndex ); + } + + const StringPlug *namePlug() const + { + return getChild( g_firstPlugIndex ); + } + + Box2iPlug *outPlug() + { + return getChild( g_firstPlugIndex + 1 ); + } + + const Box2iPlug *outPlug() const + { + return getChild( g_firstPlugIndex + 1 ); + } + + void affects( const Plug *input, AffectedPlugsContainer &outputs ) const override + { + ComputeNode::affects( input, outputs ); + + if( input == namePlug() ) + { + outputs.push_back( outPlug()->minPlug()->getChild( 0 ) ); + outputs.push_back( outPlug()->minPlug()->getChild( 1 ) ); + outputs.push_back( outPlug()->maxPlug()->getChild( 0 ) ); + outputs.push_back( outPlug()->maxPlug()->getChild( 1 ) ); + } + } + + protected : + + void hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const override + { + ComputeNode::hash( output, context, h ); + const GraphComponent *parent = output->parent(); + if( parent->parent() == outPlug() ) + { + // TODO - replace with context->variableHash in Gaffer 0.60 + const std::string name = namePlug()->getValue(); + h.append( context->get( name, Box2i() ) ); + } + } + + void compute( ValuePlug *output, const Context *context ) const override + { + const GraphComponent *parent = output->parent(); + if( parent->parent() == outPlug() ) + { + const std::string name = namePlug()->getValue(); + const Box2i value = context->get( name, Box2i() ); + + const size_t index = output == parent->getChild( 0 ) ? 0 : 1; + float result; + if( parent == outPlug()->minPlug() ) + { + result = value.min[index]; + } + else + { + result = value.max[index]; + } + static_cast( output )->setValue( result ); + } + else + { + ComputeNode::compute( output, context ); + } + } + + private : + + static size_t g_firstPlugIndex; + +}; + + +size_t Box2iContextVariable::g_firstPlugIndex = 0; +GAFFER_NODE_DEFINE_TYPE( Box2iContextVariable ) + +IE_CORE_DECLAREPTR( Box2iContextVariable ) + +void renderLine2D( const Style *style, V2f a, V2f b, float width, const Color4f &col ) +{ + style->renderLine( LineSegment3f( V3f( a.x, a.y, 0.0 ), V3f( b.x, b.y, 0 ) ), width, &col ); +} + +// TODO - these are some terrible ways of drawing circles, but I just wanted something quick that works. Add +// something better somewhere central +void renderCircle2D( const Style *style, V2f center, V2f radius, float width, const Color4f &col ) +{ + int segments = 16; + V2f prevAngle( 1, 0 ); + for( int i = 0; i < segments; i++ ) + { + V2f angle( cos( 2.0f * M_PI * ( i + 1.0f ) / segments ), sin( 2.0f * M_PI * ( i + 1.0f ) / segments ) ); + renderLine2D( style, center + prevAngle * radius, center + angle * radius, width, col ); + prevAngle = angle; + } +} + +void renderFilledCircle2D( const Style *style, V2f center, V2f radius, const Color4f &col ) +{ + // TODO : Terrible hack, rendering a dummy rectangle which will put the style's shader in a state where + // it will allow us to draw a polygon + style->renderRectangle( Box2f( center, center ) ); + int segments = 16; + glColor( col ); + glBegin( GL_POLYGON ); + + for( int i = 0; i < segments; i++ ) + { + V2f angle( cos( 2.0f * M_PI * ( i + 1.0f ) / segments ), sin( 2.0f * M_PI * ( i + 1.0f ) / segments ) ); + glVertex2f( center.x + angle.x * radius.x, center.y + angle.y * radius.y ); + } + + glEnd(); +} + +class Box2iGadget : public GafferUI::Gadget +{ + + public : + + Box2iGadget( Box2iPlugPtr plug, std::string id ) + : Gadget(), m_plug( plug ), m_id( id ), m_editable( true ), m_handleSize( 10 ), m_hover( 0 ), m_deletePressed( false ) + { + enterSignal().connect( boost::bind( &Box2iGadget::enter, this, ::_2 ) ); + mouseMoveSignal().connect( boost::bind( &Box2iGadget::mouseMove, this, ::_2 ) ); + buttonPressSignal().connect( boost::bind( &Box2iGadget::buttonPress, this, ::_2 ) ); + dragBeginSignal().connect( boost::bind( &Box2iGadget::dragBegin, this, ::_1, ::_2 ) ); + dragEnterSignal().connect( boost::bind( &Box2iGadget::dragEnter, this, ::_1, ::_2 ) ); + dragMoveSignal().connect( boost::bind( &Box2iGadget::dragMove, this, ::_2 ) ); + dragEndSignal().connect( boost::bind( &Box2iGadget::dragEnd, this, ::_2 ) ); + buttonReleaseSignal().connect( boost::bind( &Box2iGadget::buttonRelease, this, ::_2 ) ); + leaveSignal().connect( boost::bind( &Box2iGadget::leave, this ) ); + + plug->node()->plugDirtiedSignal().connect( boost::bind( &Box2iGadget::plugDirtied, this, ::_1 ) ); + } + + GAFFER_GRAPHCOMPONENT_DECLARE_TYPE( Box2iGadget, Box2iGadgetTypeId, GafferUI::Gadget ); + + Imath::Box3f bound() const override + { + Box2i rect = m_plug->getValue(); + return Box3f( + V3f( rect.min.x, rect.min.y, 0 ), + V3f( rect.max.x, rect.max.y, 0 ) + ); + } + + const Box2iPlug *getPlug() const + { + return m_plug.get(); + } + + typedef boost::signal DeleteClickedSignal; + DeleteClickedSignal &deleteClickedSignal() + { + return m_deleteClickedSignal; + } + + protected : + + void doRenderLayer( Layer layer, const Style *style ) const override + { + if( layer != Layer::Main ) + { + return; + } + + Box2i rect = m_plug->getValue(); + if( rect.isEmpty() ) + { + return; + } + + float pixelAspect = 1.0f; + V2f screenScale = screenToImageScale( &pixelAspect ); + + const V2f threshold( screenScale * m_handleSize ); + + glPushMatrix(); + glScalef( pixelAspect, 1, 1 ); + + V2f crossHairSize( + std::min ( threshold.x, rect.size().x * 0.5f ), + std::min ( threshold.y, rect.size().y * 0.5f ) + ); + + V2f rectCenter( 0.5f * ( V2f( rect.min ) + V2f( rect.max ) ) ); + V2f deleteButtonCenter( rect.max.x + threshold.x, rect.max.y + threshold.y ); + V2f deleteButtonSize( threshold.x * 0.5, threshold.y * 0.5 ); + glPushAttrib( GL_CURRENT_BIT | GL_LINE_BIT | GL_ENABLE_BIT ); + + if( IECoreGL::Selector::currentSelector() ) + { + if( m_editable ) + { + V2f upperLeft( rect.min.x, rect.max.y ); + V2f lowerRight( rect.max.x, rect.min.y ); + // Center handle + style->renderSolidRectangle( Box2f( rectCenter - threshold, rectCenter + threshold ) ); + // Vertical bars + style->renderSolidRectangle( Box2f( rect.min - threshold, upperLeft + threshold ) ); + style->renderSolidRectangle( Box2f( lowerRight - threshold, rect.max + threshold ) ); + // Horizontal bars + style->renderSolidRectangle( Box2f( rect.min - threshold, lowerRight + threshold ) ); + style->renderSolidRectangle( Box2f( upperLeft - threshold, rect.max + threshold ) ); + // Delete button + style->renderSolidRectangle( Box2f( V2f( deleteButtonCenter.x, deleteButtonCenter.y ) - 0.5f * threshold, V2f( deleteButtonCenter.x, deleteButtonCenter.y ) + 0.5f * threshold ) ); + } + } + else + { + glEnable( GL_LINE_SMOOTH ); + glLineWidth( 2.0f ); + glColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); + style->renderRectangle( Box2f( V2f(rect.min) - 1.0f * screenScale, V2f(rect.max) + 1.0f * screenScale ) ); + glLineWidth( 1.0f ); + glColor4f( 0.8f, 0.8f, 0.8f, 1.0f ); + Color4f foreground( 0.8f, 0.8f, 0.8f, 1.0f ); + style->renderRectangle( Box2f( rect.min, rect.max ) ); + renderLine2D( style, rectCenter - crossHairSize * V2f( 1, 0 ), rectCenter + crossHairSize * V2f( 1, 0 ), 1 * screenScale.x, foreground ); + renderLine2D( style, rectCenter - crossHairSize * V2f( 0, 1 ), rectCenter + crossHairSize * V2f( 0, 1 ), 1 * screenScale.x, foreground ); + + if( m_hover ) + { + renderFilledCircle2D( style, deleteButtonCenter, deleteButtonSize * 1.4f, Color4f( 0.4, 0.4, 0.4, 1.0 ) ); + Color4f buttonCol( 0.0f, 0.0f, 0.0f, 1.0f ); + if( m_hover == 2 ) + { + buttonCol = Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); + } + renderLine2D( style, deleteButtonCenter - deleteButtonSize, deleteButtonCenter + deleteButtonSize, 4.0 * screenScale.x, buttonCol ); + renderLine2D( style, deleteButtonCenter + deleteButtonSize * V2f( 1, -1 ), deleteButtonCenter + deleteButtonSize * V2f( -1, 1 ), 4.0 * screenScale.x, buttonCol ); + } + + float textScale = 10; + float textLength = style->textBound( Style::LabelText, m_id ).size().x; + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glPushMatrix(); + glTranslatef( rect.min.x - ( textScale * textLength + 5 ) * screenScale.x, rect.max.y + 5 * screenScale.y, 0.0f ); + glScalef( textScale * screenScale.x, textScale * screenScale.y, 1 ); + style->renderText( Style::LabelText, m_id ); + glPopMatrix(); + } + + glPopAttrib(); + + glPopMatrix(); + } + + private : + + void plugDirtied( Plug *plug ) + { + if( plug == m_plug ) + { + dirty( DirtyType::Bound ); + } + } + + bool enter( const ButtonEvent &event ) + { + Metadata::registerValue( m_plug.get(), g_hoveredKey, new IECore::BoolData( true ), false ); + return true; + } + + bool mouseMove( const ButtonEvent &event ) + { + // Request render in case the hover state has changed + dirty( DirtyType::Render ); + + const V2f p = eventPosition( event ); + + if( onDeleteButton( p ) ) + { + Pointer::setCurrent( "" ); + m_hover = 2; + return false; + } + + m_hover = 1; + + const V2i dir = dragDirection( p ); + if( dir.x && dir.y ) + { + Pointer::setCurrent( ( dir.x * dir.y < 0 ) ? "moveDiagonallyDown" : "moveDiagonallyUp" ); + } + else if( dir.x ) + { + Pointer::setCurrent( "moveHorizontally" ); + } + else if( dir.y ) + { + Pointer::setCurrent( "moveVertically" ); + } + else + { + Pointer::setCurrent( "move" ); + } + + return false; + } + + bool buttonPress( const GafferUI::ButtonEvent &event ) + { + if( event.buttons != ButtonEvent::Left ) + { + return false; + } + + // Anything within the bound is draggable except the delete button + const V2f p = eventPosition( event ); + if( onDeleteButton( p ) ) + { + m_deletePressed = true; + return true; + } + + return true; + } + + IECore::RunTimeTypedPtr dragBegin( GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event ) + { + if( m_deletePressed ) + { + return nullptr; + } + m_dragStart = eventPosition( event ); + m_dragDirection = dragDirection( m_dragStart ); + m_dragStartRectangle = m_plug->getValue(); + return IECore::NullObject::defaultNullObject(); + } + + bool dragEnter( const GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event ) + { + if( event.sourceGadget != this ) + { + return false; + } + + updateDragRectangle( event ); + return true; + } + + bool dragMove( const GafferUI::DragDropEvent &event ) + { + updateDragRectangle( event ); + return true; + } + + bool dragEnd( const GafferUI::DragDropEvent &event ) + { + updateDragRectangle( event ); + return true; + } + + void updateDragRectangle( const GafferUI::DragDropEvent &event ) + { + const V2f p = eventPosition( event ); + Box2i b = m_dragStartRectangle; + + if( m_dragDirection == V2i( 0, 0 ) ) + { + const V2f offset = p - m_dragStart; + const V2i intOffset = V2i( round( offset.x ), round( offset.y ) ); + b.min += intOffset; + b.max += intOffset; + } + else + { + if( m_dragDirection.x == -1 ) + { + b.min.x = round( p.x ); + } + else if( m_dragDirection.x == 1 ) + { + b.max.x = round( p.x ); + } + + if( m_dragDirection.y == -1 ) + { + b.min.y = round( p.y ); + } + else if( m_dragDirection.y == 1 ) + { + b.max.y = round( p.y ); + } + } + + // fix max < min issues + Box2i c; + c.extendBy( b.min ); + c.extendBy( b.max ); + + m_plug->setValue( c ); + } + + bool buttonRelease( const GafferUI::ButtonEvent &event ) + { + const V2f p = eventPosition( event ); + if( m_deletePressed && onDeleteButton( p ) ) + { + m_deleteClickedSignal( m_plug.get() ); + } + m_deletePressed = false; + + return true; + } + + void leave() + { + Pointer::setCurrent( "" ); + m_hover = 0; + + Metadata::registerValue( m_plug.get(), g_hoveredKey, new IECore::BoolData( false ), false ); + } + + // Returns the scale from screen raster pixels to Gaffer image pixels. This includes both + // the scaling applied by ViewportGadget, and the pixelAspect scaling which isn't applied + // automatically ( it is optionally returned separately so we can apply it manually in + // doRenderLayer ) + V2f screenToImageScale( float *pixelAspectOut = nullptr ) const + { + const ViewportGadget *viewportGadget = ancestor(); + const V2f viewportPlanarScale( + viewportGadget->getCamera()->getAperture()[0] / viewportGadget->getViewport()[0], + viewportGadget->getCamera()->getAperture()[1] / viewportGadget->getViewport()[1] + ); + + const ImageGadget *imageGadget = static_cast( viewportGadget->getPrimaryChild() ); + + // We want to grab the cached version of imageGadget->format(), but it's not exposed publicly, so we + // get it from pixelAt. + // In the future, it would be better if format() was public and we didn't have to worry about it + // throwing. + float pixelAspect = 1.0f; + try + { + pixelAspect = 1.0f / imageGadget->pixelAt( LineSegment3f( V3f( 1, 0, 0 ), V3f( 1, 0, 1 ) ) ).x; + } + catch( ... ) + { + // Not worried about rendering correctly for images which can't be evaluated properly + } + + if( pixelAspectOut ) + { + *pixelAspectOut = pixelAspect; + } + + return viewportPlanarScale * V2f( 1.0f / pixelAspect, 1.0f ); + } + + bool onDeleteButton( const V2f &p ) const + { + // Any positions that are part of the gadget, but not part of an extended bound, are + // on the delete button + Box2i rect = m_plug->getValue(); + const V2f screenScale = screenToImageScale(); + const V2f threshold( screenScale * m_handleSize ); + return + p.x > rect.max.x + 0.5 * threshold.x - screenScale.x && + p.y > rect.max.y + 0.5 * threshold.y - screenScale.y; + } + + V2i dragDirection( const V2f &p ) const + { + + Box2i rect = m_plug->getValue(); + V2f rectCenter( 0.5f * ( V2f( rect.min ) + V2f( rect.max ) ) ); + V2f centerDisp = p - rectCenter; + + const V2f screenScale = screenToImageScale(); + const V2f threshold( screenToImageScale() * m_handleSize ); + + if( rect.intersects( p ) && fabs( centerDisp.x ) < threshold.x && fabs( centerDisp.y ) < threshold.y ) + { + // Center handle + return V2i( 0, 0 ); + } + + + // We're not in the center, so we must be over an edge. Return which edge + // Not that there is an extra pixel of tolerance here, since the selection rect snaps + // to the nearest half-pixel, and we need to include the whole selection rect + Box2f rectInner( V2f(rect.min) + threshold + screenScale, V2f(rect.max) - threshold - screenScale ); + return V2i( + p.x > rectInner.max.x ? 1 : ( p.x < rectInner.min.x ? -1 : 0 ), + p.y > rectInner.max.y ? 1 : ( p.y < rectInner.min.y ? -1 : 0 ) + ); + } + + V2f eventPosition( const ButtonEvent &event ) const + { + const ViewportGadget *viewportGadget = ancestor(); + const ImageGadget *imageGadget = static_cast( viewportGadget->getPrimaryChild() ); + V2f pixel = imageGadget->pixelAt( event.line ); + Context::Scope contextScope( imageGadget->getContext() ); + return pixel; + } + + Box2iPlugPtr m_plug; + const std::string m_id; + bool m_editable; + float m_handleSize; + int m_hover; // Hover state: 0 no hover, 1 for hovered, 2 for deleteButton hovered + bool m_deletePressed; + + DeleteClickedSignal m_deleteClickedSignal; + + + Imath::Box2i m_dragStartRectangle; + Imath::V2f m_dragStart; + Imath::V2i m_dragDirection; +}; +GAFFER_GRAPHCOMPONENT_DEFINE_TYPE( Box2iGadget ) + +class V2iGadget : public GafferUI::Gadget +{ + + public : + + V2iGadget( V2iPlugPtr plug, std::string id ) + : Gadget(), m_plug( plug ), m_id( id ), m_editable( true ), m_handleSize( 10 ), m_hover( 0 ), m_deletePressed( false ) + { + enterSignal().connect( boost::bind( &V2iGadget::enter, this, ::_2 ) ); + mouseMoveSignal().connect( boost::bind( &V2iGadget::mouseMove, this, ::_2 ) ); + buttonPressSignal().connect( boost::bind( &V2iGadget::buttonPress, this, ::_2 ) ); + dragBeginSignal().connect( boost::bind( &V2iGadget::dragBegin, this, ::_1, ::_2 ) ); + dragEnterSignal().connect( boost::bind( &V2iGadget::dragEnter, this, ::_1, ::_2 ) ); + dragMoveSignal().connect( boost::bind( &V2iGadget::dragMove, this, ::_2 ) ); + dragEndSignal().connect( boost::bind( &V2iGadget::dragEnd, this, ::_2 ) ); + buttonReleaseSignal().connect( boost::bind( &V2iGadget::buttonRelease, this, ::_2 ) ); + leaveSignal().connect( boost::bind( &V2iGadget::leave, this ) ); + + plug->node()->plugDirtiedSignal().connect( boost::bind( &V2iGadget::plugDirtied, this, ::_1 ) ); + } + + GAFFER_GRAPHCOMPONENT_DECLARE_TYPE( V2iGadget, V2iGadgetTypeId, GafferUI::Gadget ); + + Imath::Box3f bound() const override + { + V2i p = m_plug->getValue(); + return Box3f( + V3f( p.x, p.y, 0 ), + V3f( p.x, p.y, 0 ) + ); + } + + const V2iPlug *getPlug() const + { + return m_plug.get(); + } + + typedef boost::signal DeleteClickedSignal; + DeleteClickedSignal &deleteClickedSignal() + { + return m_deleteClickedSignal; + } + + protected : + + void doRenderLayer( Layer layer, const Style *style ) const override + { + if( layer != Layer::Main ) + { + return; + } + + float pixelAspect = 1.0f; + V2f screenScale = screenToImageScale( &pixelAspect ); + + const V2f threshold( screenScale * m_handleSize ); + + glPushMatrix(); + glScalef( pixelAspect, 1, 1 ); + + V2f point = V2f(m_plug->getValue()) + V2f( 0.5 ); + V2f deleteButtonCenter( point.x + threshold.x, point.y + threshold.y ); + V2f deleteButtonSize( threshold.x * 0.5, threshold.y * 0.5 ); + glPushAttrib( GL_CURRENT_BIT | GL_LINE_BIT | GL_ENABLE_BIT ); + + if( IECoreGL::Selector::currentSelector() ) + { + if( m_editable ) + { + // Center handle + style->renderSolidRectangle( Box2f( point - threshold, point + threshold ) ); + // Delete button + style->renderSolidRectangle( Box2f( V2f( deleteButtonCenter.x, deleteButtonCenter.y ) - 0.5f * threshold, V2f( deleteButtonCenter.x, deleteButtonCenter.y ) + 0.5f * threshold ) ); + } + } + else + { + glEnable( GL_LINE_SMOOTH ); + Color4f black( 0.0f, 0.0f, 0.0f, 1.0f ); + renderLine2D( style, point - V2f( threshold.x, 0 ), point - V2f( 2.5 * screenScale.x, 0 ), screenScale.y * 2.0f, black ); + renderLine2D( style, point + V2f( threshold.x, 0 ), point + V2f( 2.5 * screenScale.x, 0 ), screenScale.y * 2.0f, black ); + renderLine2D( style, point - V2f( 0, threshold.y ), point - V2f( 0, 2.5 * screenScale.y ), screenScale.x * 2.0f, black ); + renderLine2D( style, point + V2f( 0, threshold.y ), point + V2f( 0, 2.5 * screenScale.y ), screenScale.x * 2.0f, black ); + renderCircle2D( style, point, 2.5f * screenScale, screenScale.x * 2.0f, Color4f( 0.8, 0.8, 0.8, 1.0 ) ); + + if( m_hover ) + { + renderFilledCircle2D( style, deleteButtonCenter, deleteButtonSize * 1.4f, Color4f( 0.4, 0.4, 0.4, 1.0 ) ); + Color4f buttonCol( 0.0f, 0.0f, 0.0f, 1.0f ); + if( m_hover == 2 ) + { + buttonCol = Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); + } + renderLine2D( style, deleteButtonCenter - deleteButtonSize, deleteButtonCenter + deleteButtonSize, 4.0 * screenScale.x, buttonCol ); + renderLine2D( style, deleteButtonCenter + deleteButtonSize * V2f( 1, -1 ), deleteButtonCenter + deleteButtonSize * V2f( -1, 1 ), 4.0 * screenScale.x, buttonCol ); + } + + float textScale = 10; + float textLength = style->textBound( Style::LabelText, m_id ).size().x; + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glPushMatrix(); + glTranslatef( point.x - ( textScale * textLength + 5 ) * screenScale.x, point.y + 5 * screenScale.y, 0.0f ); + glScalef( textScale * screenScale.x, textScale * screenScale.y, 1 ); + style->renderText( Style::LabelText, m_id ); + glPopMatrix(); + } + + glPopAttrib(); + + glPopMatrix(); + } + + private : + + void plugDirtied( Plug *plug ) + { + if( plug == m_plug ) + { + dirty( DirtyType::Bound ); + } + } + + bool enter( const ButtonEvent &event ) + { + Metadata::registerValue( m_plug.get(), g_hoveredKey, new IECore::BoolData( true ), false ); + return true; + } + + bool mouseMove( const ButtonEvent &event ) + { + // Request render in case the hover state has changed + dirty( DirtyType::Render ); + + const V2f p = eventPosition( event ); + + if( onDeleteButton( p ) ) + { + Pointer::setCurrent( "" ); + m_hover = 2; + return false; + } + + m_hover = 1; + + Pointer::setCurrent( "move" ); + + return false; + } + + bool buttonPress( const GafferUI::ButtonEvent &event ) + { + if( event.buttons != ButtonEvent::Left ) + { + return false; + } + + // Anything within the bound is draggable except the delete button + const V2f p = eventPosition( event ); + if( onDeleteButton( p ) ) + { + m_deletePressed = true; + return true; + } + + return true; + } + + IECore::RunTimeTypedPtr dragBegin( GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event ) + { + if( m_deletePressed ) + { + return nullptr; + } + + m_dragStart = eventPosition( event ); + m_dragStartPlugValue = m_plug->getValue(); + return IECore::NullObject::defaultNullObject(); + } + + bool dragEnter( const GafferUI::Gadget *gadget, const GafferUI::DragDropEvent &event ) + { + if( event.sourceGadget != this ) + { + return false; + } + + updateDragPoint( event ); + return true; + } + + bool dragMove( const GafferUI::DragDropEvent &event ) + { + updateDragPoint( event ); + return true; + } + + bool dragEnd( const GafferUI::DragDropEvent &event ) + { + updateDragPoint( event ); + return true; + } + + void updateDragPoint( const GafferUI::DragDropEvent &event ) + { + const V2f p = eventPosition( event ); + V2i point = m_dragStartPlugValue; + + const V2f offset = p - m_dragStart; + point += V2i( round( offset.x ), round( offset.y ) ); + + m_plug->setValue( point ); + } + + bool buttonRelease( const GafferUI::ButtonEvent &event ) + { + const V2f p = eventPosition( event ); + if( m_deletePressed && onDeleteButton( p ) ) + { + m_deleteClickedSignal( m_plug.get() ); + } + m_deletePressed = false; + + return true; + } + + void leave() + { + Pointer::setCurrent( "" ); + m_hover = 0; + + Metadata::registerValue( m_plug.get(), g_hoveredKey, new IECore::BoolData( false ), false ); + } + + // Returns the scale from screen raster pixels to Gaffer image pixels. This includes both + // the scaling applied by ViewportGadget, and the pixelAspect scaling which isn't applied + // automatically ( it is optionally returned separately so we can apply it manually in + // doRenderLayer ) + V2f screenToImageScale( float *pixelAspectOut = nullptr ) const + { + const ViewportGadget *viewportGadget = ancestor(); + const V2f viewportPlanarScale( + viewportGadget->getCamera()->getAperture()[0] / viewportGadget->getViewport()[0], + viewportGadget->getCamera()->getAperture()[1] / viewportGadget->getViewport()[1] + ); + + const ImageGadget *imageGadget = static_cast( viewportGadget->getPrimaryChild() ); + + // We want to grab the cached version of imageGadget->format(), but it's not exposed publicly, so we + // get it from pixelAt. + // In the future, it would be better if format() was public and we didn't have to worry about it + // throwing. + float pixelAspect = 1.0f; + try + { + pixelAspect = 1.0f / imageGadget->pixelAt( LineSegment3f( V3f( 1, 0, 0 ), V3f( 1, 0, 1 ) ) ).x; + } + catch( ... ) + { + // Not worried about rendering correctly for images which can't be evaluated properly + } + + if( pixelAspectOut ) + { + *pixelAspectOut = pixelAspect; + } + + return viewportPlanarScale * V2f( 1.0f / pixelAspect, 1.0f ); + } + + bool onDeleteButton( const V2f &p ) const + { + // Any positions that are part of the gadget, but not part of an extended bound, are + // on the delete button + V2f point = V2f(m_plug->getValue()) + V2f( 0.5 ); + const V2f screenScale = screenToImageScale(); + const V2f threshold( screenScale * m_handleSize ); + return + p.x > point.x + 0.5 * threshold.x - screenScale.x && + p.y > point.y + 0.5 * threshold.y - screenScale.y; + } + + V2f eventPosition( const ButtonEvent &event ) const + { + const ViewportGadget *viewportGadget = ancestor(); + const ImageGadget *imageGadget = static_cast( viewportGadget->getPrimaryChild() ); + V2f pixel = imageGadget->pixelAt( event.line ); + Context::Scope contextScope( imageGadget->getContext() ); + return pixel; + } + + V2iPlugPtr m_plug; + const std::string m_id; + bool m_editable; + float m_handleSize; + int m_hover; // Hover state: 0 no hover, 1 for hovered, 2 for deleteButton hovered + bool m_deletePressed; + + DeleteClickedSignal m_deleteClickedSignal; + + Imath::V2i m_dragStartPlugValue; + Imath::V2f m_dragStart; +}; +GAFFER_GRAPHCOMPONENT_DEFINE_TYPE( V2iGadget ) + } // namespace +ImageView::ColorInspectorPlug::ColorInspectorPlug( const std::string &name, Direction direction, unsigned flags ) + : ValuePlug( name, direction, flags ) +{ + addChild( new IntPlug( "mode", Direction::In, (int)Mode::Pixel, (int)Mode::Cursor, (int)Mode::Area ) ); + addChild( new V2iPlug( "pixel" ) ); + addChild( new Box2iPlug( "area" ) ); +} + +Gaffer::IntPlug *ImageView::ColorInspectorPlug::modePlug() +{ + return getChild( 0 ); +} + +const Gaffer::IntPlug *ImageView::ColorInspectorPlug::modePlug() const +{ + return getChild( 0 ); +} + +Gaffer::V2iPlug *ImageView::ColorInspectorPlug::pixelPlug() +{ + return getChild( 1 ); +} + +const Gaffer::V2iPlug *ImageView::ColorInspectorPlug::pixelPlug() const +{ + return getChild( 1 ); +} + +Gaffer::Box2iPlug *ImageView::ColorInspectorPlug::areaPlug() +{ + return getChild( 2 ); +} + +const Gaffer::Box2iPlug *ImageView::ColorInspectorPlug::areaPlug() const +{ + return getChild( 2 ); +} + +bool ImageView::ColorInspectorPlug::acceptsChild( const Gaffer::GraphComponent *potentialChild ) const +{ + if( !Plug::acceptsChild( potentialChild ) ) + { + return false; + } + return children().size() <= 3; +} + +Gaffer::PlugPtr ImageView::ColorInspectorPlug::createCounterpart( const std::string &name, Direction direction ) const +{ + return new ColorInspectorPlug( name, direction, getFlags() ); +} + class ImageView::ColorInspector : public boost::signals::trackable { @@ -285,26 +1187,38 @@ class ImageView::ColorInspector : public boost::signals::trackable ColorInspector( ImageView *view ) : m_view( view ), m_pixel( new V2fContextVariable ), + m_area( new Box2iContextVariable ), m_deleteContextVariables( new DeleteContextVariables ), - m_sampler( new ImageSampler ) + m_sampler( new ImageSampler ), + m_areaSampler( new ImageStats ) { + // ---- Create a plug on ImageView which will be used for evaluating colorInspectors + PlugPtr plug = new Plug( "colorInspector" ); view->addChild( plug ); - plug->addChild( new Color4fPlug( "color" ) ); + + PlugPtr evaluatorPlug = new Plug( "evaluator" ); + plug->addChild( evaluatorPlug ); + evaluatorPlug->addChild( new Color4fPlug( "pixelColor" ) ); + evaluatorPlug->addChild( new Color4fPlug( "areaColor" ) ); // We use `m_pixel` to fetch a context variable to transfer // the mouse position into `m_sampler`. We could use `mouseMoveSignal()` // to instead call `m_sampler->pixelPlug()->setValue()`, but that // would cause cancellation of the ImageView background compute every - // time the mouse was moved. The "colorInspector:pixel" variable is + // time the mouse was moved. The "colorInspector:source" variable is // created in ImageViewUI's `_ColorInspectorPlugValueWidget`. - m_pixel->namePlug()->setValue( "colorInspector:pixel" ); + m_pixel->namePlug()->setValue( "colorInspector:source" ); + + // The same thing, but when we need an area to evaluate areaColor + // instead of a pixel to evaluate pixelColor + m_area->namePlug()->setValue( "colorInspector:source" ); // And we use a DeleteContextVariables node to make sure that our // private context variable doesn't become visible to the upstream // graph. m_deleteContextVariables->setup( view->inPlug() ); - m_deleteContextVariables->variablesPlug()->setValue( "colorInspector:pixel" ); + m_deleteContextVariables->variablesPlug()->setValue( "colorInspector:source" ); // We want to sample the image before the display transforms // are applied. We can't simply get this image from inPlug() @@ -316,34 +1230,126 @@ class ImageView::ColorInspector : public boost::signals::trackable m_sampler->imagePlug()->setInput( m_deleteContextVariables->outPlug() ); m_sampler->pixelPlug()->setInput( m_pixel->outPlug() ); - plug->getChild( "color" )->setInput( m_sampler->colorPlug() ); + evaluatorPlug->getChild( "pixelColor" )->setInput( m_sampler->colorPlug() ); + + + m_areaSampler->inPlug()->setInput( m_deleteContextVariables->outPlug() ); + m_areaSampler->areaPlug()->setInput( m_area->outPlug() ); + evaluatorPlug->getChild( "areaColor" )->setInput( m_areaSampler->averagePlug() ); ImageGadget *imageGadget = static_cast( m_view->viewportGadget()->getPrimaryChild() ); imageGadget->channelsChangedSignal().connect( boost::bind( &ColorInspector::channelsChanged, this ) ); + + + // ---- Create a plug on ImageView for storing colorInspectors + plug->addChild( new ArrayPlug( "inspectors", Plug::In, new ColorInspectorPlug(), 1, 1024, Plug::Default & ~Plug::AcceptsInputs ) ); + colorInspectorsPlug()->childAddedSignal().connect( boost::bind( &ColorInspector::colorInspectorAdded, this, ::_2 ) ); + colorInspectorsPlug()->childRemovedSignal().connect( boost::bind( &ColorInspector::colorInspectorRemoved, this, ::_2 ) ); + + colorInspectorsPlug()->getChild( 0 )->modePlug()->setValue( (int)ColorInspectorPlug::Mode::Cursor ); + + view->plugSetSignal().connect( boost::bind( &ColorInspector::plugSet, this, ::_1 ) ); } private : - Plug *plug() + Gaffer::ArrayPlug *colorInspectorsPlug() + { + return m_view->getChild( "colorInspector" )->getChild( "inspectors" ); + } + + const Gaffer::ArrayPlug *colorInspectorsPlug() const { - return m_view->getChild( "colorInspector" ); + return m_view->getChild( "colorInspector" )->getChild( "inspectors" ); + } + + void plugSet( Gaffer::Plug *plug ) + { + // Note that this code is currently unused, since I've disabled the ability to toggle + // mode from the UI. Perhaps this should be deleted? + if( plug->parent()->parent() == colorInspectorsPlug() ) + { + ColorInspectorPlug *colorInspector = static_cast( plug->parent() ); + if( plug == colorInspector->modePlug() ) + { + if( colorInspector->modePlug()->getValue() == (int)ColorInspectorPlug::Mode::Pixel ) + { + colorInspector->pixelPlug()->setValue( colorInspector->areaPlug()->getValue().center() ); + } + else if( colorInspector->modePlug()->getValue() == (int)ColorInspectorPlug::Mode::Area ) + { + V2i pixel = colorInspector->pixelPlug()->getValue(); + colorInspector->areaPlug()->setValue( Box2i( pixel - V2i( 50 ), pixel + V2i( 50 ) ) ); + } + colorInspectorRemoved( colorInspector ); + colorInspectorAdded( colorInspector ); + } + } + } + + void colorInspectorAdded( GraphComponent *colorInspector ) + { + ColorInspectorPlug *colorInspectorTyped = static_cast( colorInspector ); + if( colorInspectorTyped->modePlug()->getValue() == (int)ColorInspectorPlug::Mode::Pixel ) + { + V2iGadget::Ptr r = new V2iGadget( colorInspectorTyped->pixelPlug(), colorInspector->getName().value().substr( 1 ) ); + r->deleteClickedSignal().connect( boost::bind( &ColorInspector::deleteClicked, this, ::_1 ) ); + m_view->viewportGadget()->addChild( r ); + } + else + { + Box2iGadget::Ptr r = new Box2iGadget( colorInspectorTyped->areaPlug(), colorInspector->getName().value().substr( 1 ) ); + r->deleteClickedSignal().connect( boost::bind( &ColorInspector::deleteClicked, this, ::_1 ) ); + m_view->viewportGadget()->addChild( r ); + } + } + + void colorInspectorRemoved( GraphComponent *colorInspector ) + { + ColorInspectorPlug *colorInspectorTyped = static_cast( colorInspector ); + for( auto &i : m_view->viewportGadget()->children() ) + { + if( Box2iGadget *boxGadget = runTimeCast( i.get() ) ) + { + if( boxGadget->getPlug() == colorInspectorTyped->areaPlug() ) + { + m_view->viewportGadget()->removeChild( i ); + return; + } + } + else if( V2iGadget *v2iGadget = runTimeCast( i.get() ) ) + { + if( v2iGadget->getPlug() == colorInspectorTyped->pixelPlug() ) + { + m_view->viewportGadget()->removeChild( i ); + return; + } + } + } + } + + void deleteClicked( Gaffer::Plug *plug ) + { + colorInspectorsPlug()->removeChild( plug->parent() ); } void channelsChanged() { ImageGadget *imageGadget = static_cast( m_view->viewportGadget()->getPrimaryChild() ); - m_sampler->channelsPlug()->setValue( - new StringVectorData( std::vector( - imageGadget->getChannels().begin(), - imageGadget->getChannels().end() - ) ) - ); + StringVectorDataPtr channels = new StringVectorData( std::vector( + imageGadget->getChannels().begin(), + imageGadget->getChannels().end() + ) ); + m_sampler->channelsPlug()->setValue( channels ); + m_areaSampler->channelsPlug()->setValue( channels ); } ImageView *m_view; V2fContextVariablePtr m_pixel; + Box2iContextVariablePtr m_area; DeleteContextVariablesPtr m_deleteContextVariables; ImageSamplerPtr m_sampler; + ImageStatsPtr m_areaSampler; }; @@ -447,6 +1453,22 @@ void ImageView::insertConverter( Gaffer::NodePtr converter ) ImageView::~ImageView() { + // Addons like m_colorInspector add plugs to us which are connected to nodes held by them, which are not + // our children. If these were child nodes, they would be held by the GraphComponent base class which + // gets destructed very late, but because they are not actually children, they will be destructed fairly + // quickly by our destructor ... before the signals in the Node base class get destructed. + // + // These graph modifications happening during our destructor would trigger signals to be sent, which + // is very dangerous - those signals could be connected to something which tries to access us, and anyone + // who takes an intrusive pointer to us while we're destructing will trigger a segfault. + // + // We can safeguard against this by disconnecting any slots that would be trigger by graph structure changes + // before we destruct member variables. + // + // This shouldn't be necessary once we come up with a more general solution to: + // https://github.com/GafferHQ/gaffer/issues/4221 + plugInputChangedSignal().disconnect_all_slots(); + plugDirtiedSignal().disconnect_all_slots(); } Gaffer::BoolPlug *ImageView::clippingPlug() diff --git a/src/GafferImageUIModule/ImageViewBinding.cpp b/src/GafferImageUIModule/ImageViewBinding.cpp index 6a776ccd23a..883e4dde925 100644 --- a/src/GafferImageUIModule/ImageViewBinding.cpp +++ b/src/GafferImageUIModule/ImageViewBinding.cpp @@ -44,6 +44,7 @@ #include "GafferImage/ImageProcessor.h" #include "GafferBindings/NodeBinding.h" +#include "GafferBindings/PlugBinding.h" using namespace std; using namespace boost::python; @@ -121,7 +122,7 @@ ImageProcessorPtr createDisplayTransform( const std::string &name ) void GafferImageUIModule::bindImageView() { - GafferBindings::NodeClass() + scope s = GafferBindings::NodeClass() .def( init() ) .def( "imageGadget", (ImageGadget *(ImageView::*)())&ImageView::imageGadget, return_value_policy() ) .def( "_insertConverter", &ImageViewWrapper::insertConverter ) @@ -133,4 +134,21 @@ void GafferImageUIModule::bindImageView() .staticmethod( "createDisplayTransform" ) ; + scope ci = GafferBindings::PlugClass() + .def( init( + ( + boost::python::arg_( "name" )=GraphComponent::defaultName(), + boost::python::arg_( "direction" )=Plug::In, + boost::python::arg_( "flags" )=Plug::Default + ) + ) + ) + ; + + enum_( "Mode" ) + .value( "Cursor", ImageView::ColorInspectorPlug::Mode::Cursor ) + .value( "Pixel", ImageView::ColorInspectorPlug::Mode::Pixel ) + .value( "Area", ImageView::ColorInspectorPlug::Mode::Area ) + ; + } diff --git a/src/GafferModule/AnimationBinding.cpp b/src/GafferModule/AnimationBinding.cpp index c2a16f52ab5..341a2a39ec8 100644 --- a/src/GafferModule/AnimationBinding.cpp +++ b/src/GafferModule/AnimationBinding.cpp @@ -114,7 +114,7 @@ class CurvePlugSerialiser : public ValuePlugSerialiser public : - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = ValuePlugSerialiser::postConstructor( graphComponent, identifier, serialisation ); const Animation::CurvePlug *curve = static_cast( graphComponent ); diff --git a/src/GafferModule/ArrayPlugBinding.cpp b/src/GafferModule/ArrayPlugBinding.cpp index f6b4e215dc5..2d3b462b6ef 100644 --- a/src/GafferModule/ArrayPlugBinding.cpp +++ b/src/GafferModule/ArrayPlugBinding.cpp @@ -113,7 +113,7 @@ class ArrayPlugSerialiser : public PlugSerialiser return PlugSerialiser::childNeedsConstruction( child, serialisation ); } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return ::repr( static_cast( graphComponent ) ); } diff --git a/src/GafferModule/CompoundNumericPlugBinding.cpp b/src/GafferModule/CompoundNumericPlugBinding.cpp index 9d4c3a19694..244d09ea136 100644 --- a/src/GafferModule/CompoundNumericPlugBinding.cpp +++ b/src/GafferModule/CompoundNumericPlugBinding.cpp @@ -58,7 +58,7 @@ namespace { template -std::string serialisationRepr( const T *plug, const Serialisation *serialisation = nullptr ) +std::string serialisationRepr( const T *plug, Serialisation *serialisation = nullptr ) { std::string extraArgs = ""; @@ -70,6 +70,11 @@ std::string serialisationRepr( const T *plug, const Serialisation *serialisation boost::python::object interpretationRepr = interpretationAsPythonObject.attr( "__repr__" )(); extraArgs = "interpretation = " + std::string( boost::python::extract( interpretationRepr ) ); boost::replace_first( extraArgs, "_IECore", "GeometricData" ); + + if( serialisation ) + { + serialisation->addModule( "IECore" ); + } } return ValuePlugSerialiser::repr( plug, extraArgs, serialisation ); @@ -87,7 +92,7 @@ class CompoundNumericPlugSerialiser : public ValuePlugSerialiser public : - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return serialisationRepr( static_cast( graphComponent ), &serialisation ); } diff --git a/src/GafferModule/ContextBinding.cpp b/src/GafferModule/ContextBinding.cpp index a7da13793cb..871f8fef7c4 100644 --- a/src/GafferModule/ContextBinding.cpp +++ b/src/GafferModule/ContextBinding.cpp @@ -78,28 +78,29 @@ void set( Context &c, const IECore::InternedString &name, const T &value ) c.set( name, value ); } -// In the C++ API, get() returns "const Data *". Because python has no idea of constness, -// by default we return a copy from the bindings because we don't want the unwitting Python -// scripter to accidentally modify the internals of a Context. We do however expose the -// option to get the original object returned using an "_copy = False" keyword argument, -// in the same way as we do for the TypedObjectPlug::getValue() binding. This is mainly of -// use in the unit tests, but may also have the odd application where performance is critical. -// As a general rule, you should be wary of using this parameter. -object get( Context &c, const IECore::InternedString &name, object defaultValue, bool copy ) +void setFromData( Context &c, const IECore::InternedString &name, const IECore::Data * value ) { - ConstDataPtr d = c.get( name, nullptr ); - return dataToPython( d.get(), copy, defaultValue ); + IECorePython::ScopedGILRelease gilRelease; + c.set( name, value ); +} + +// In the C++ API, the untemplated get() returns a freshly copied ConstDataPtr, so it is safe +// to just pass to Python without copying again. +object get( Context &c, const IECore::InternedString &name, object defaultValue ) +{ + DataPtr d = c.getAsData( name, nullptr ); + return dataToPython( d.get(), false, defaultValue ); } object getItem( Context &c, const IECore::InternedString &name ) { - ConstDataPtr d = c.get( name ); - return dataToPython( d.get(), /* copy = */ true ); + DataPtr d = c.getAsData( name ); + return dataToPython( d.get(), /* copy = */ false ); } bool contains( Context &c, const IECore::InternedString &name ) { - return c.get( name, nullptr ); + return bool( c.getAsData( name, nullptr ) ); } void delItem( Context &context, const IECore::InternedString &name ) @@ -155,15 +156,9 @@ void GafferModule::bindContext() IECorePython::RefCountedClass contextClass( "Context" ); scope s = contextClass; - enum_( "Ownership" ) - .value( "Copied", Context::Copied ) - .value( "Shared", Context::Shared ) - .value( "Borrowed", Context::Borrowed ) - ; - contextClass .def( init<>() ) - .def( init( ( arg( "other" ), arg( "ownership" ) = Context::Copied ) ) ) + .def( init( ( arg( "other" ) ) ) ) .def( init( ( arg( "other" ), arg( "canceller" ) ) ) [ with_custodian_and_ward<1,3>() @@ -184,7 +179,8 @@ void GafferModule::bindContext() .def( "set", &set ) .def( "set", &set ) .def( "set", &set ) - .def( "set", &set ) + .def( "set", &set ) + .def( "set", &setFromData ) .def( "__setitem__", &set ) .def( "__setitem__", &set ) .def( "__setitem__", &set ) @@ -193,18 +189,19 @@ void GafferModule::bindContext() .def( "__setitem__", &set ) .def( "__setitem__", &set ) .def( "__setitem__", &set ) - .def( "__setitem__", &set ) - .def( "get", &get, ( arg( "defaultValue" ) = object(), arg( "_copy" ) = true ) ) + .def( "__setitem__", &set ) + .def( "__setitem__", &setFromData ) + .def( "get", &get, ( arg( "defaultValue" ) = object() ) ) .def( "__getitem__", &getItem ) .def( "__contains__", &contains ) .def( "remove", &delItem ) .def( "__delitem__", &delItem ) .def( "removeMatching", &removeMatching ) - .def( "changed", &Context::changed ) .def( "names", &names ) .def( "keys", &names ) .def( "changedSignal", &Context::changedSignal, return_internal_reference<1>() ) .def( "hash", &Context::hash ) + .def( "variableHash", &Context::variableHash ) .def( self == self ) .def( self != self ) .def( "substitute", &Context::substitute, ( arg( "input" ), arg( "substitutions" ) = IECore::StringAlgo::AllSubstitutions ) ) diff --git a/src/GafferModule/ContextProcessorBinding.cpp b/src/GafferModule/ContextProcessorBinding.cpp index 2b8ce919561..1a6fbfde0cd 100644 --- a/src/GafferModule/ContextProcessorBinding.cpp +++ b/src/GafferModule/ContextProcessorBinding.cpp @@ -88,7 +88,7 @@ class SetupBasedNodeSerialiser : public NodeSerialiser return NodeSerialiser::childNeedsConstruction( child, serialisation ); } - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); diff --git a/src/GafferModule/DotBinding.cpp b/src/GafferModule/DotBinding.cpp index 97eb22731e1..08d51801cf4 100644 --- a/src/GafferModule/DotBinding.cpp +++ b/src/GafferModule/DotBinding.cpp @@ -72,7 +72,7 @@ class DotSerialiser : public NodeSerialiser return NodeSerialiser::childNeedsConstruction( child, serialisation ); } - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); diff --git a/src/GafferModule/ExpressionBinding.cpp b/src/GafferModule/ExpressionBinding.cpp index 02048802be1..fc598e70433 100644 --- a/src/GafferModule/ExpressionBinding.cpp +++ b/src/GafferModule/ExpressionBinding.cpp @@ -106,6 +106,44 @@ struct ExpressionChangedSlotCaller } }; +ValuePlug::CachePolicy defaultExecuteCachePolicy() +{ + // Expressions implemented through Python will be forced to run serially due to the GIL, + // which makes it very bad to allow parallel evaluations of the same plug, since they will + // all compete over the same GIL. + + // In the long term, we can probably lock this to Standard, but in the short term, overriding + // to Legacy or TaskIsolation using an env var could provide a workaround if facilities have + // Gaffer nodes that do their own tbb calls without properly isolating them, which could + // cause hangs when using the Standard policy + if( const char *cp = getenv( "GAFFER_PYTHONEXPRESSION_CACHEPOLICY" ) ) + { + if( !strcmp( cp, "Standard" ) ) + { + return ValuePlug::CachePolicy::Standard; + } + else if( !strcmp( cp, "TaskCollaboration" ) ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } + else if( !strcmp( cp, "TaskIsolation" ) ) + { + return ValuePlug::CachePolicy::TaskIsolation; + } + else if( !strcmp( cp, "Legacy" ) ) + { + return ValuePlug::CachePolicy::Legacy; + } + else + { + IECore::msg( IECore::Msg::Warning, "Expression", "Invalid value for GAFFER_GAFFER_PYTHONEXPRESSION_CACHEPOLICY. Must be Standard, TaskCollaboration, TaskIsolation or Legacy." ); + } + } + + return ValuePlug::CachePolicy::Standard; +} + + class EngineWrapper : public IECorePython::RefCountedWrapper { public : @@ -172,6 +210,11 @@ class EngineWrapper : public IECorePython::RefCountedWrapper throw IECore::Exception( "Engine::execute() python method not defined" ); } + ValuePlug::CachePolicy executeCachePolicy() const override + { + return g_cachePolicy; + } + void apply( ValuePlug *proxyOutput, const ValuePlug *topLevelProxyOutput, const IECore::Object *value ) const override { if( isSubclassed() ) @@ -292,8 +335,12 @@ class EngineWrapper : public IECorePython::RefCountedWrapper return boost::python::tuple( l ); } + static ValuePlug::CachePolicy g_cachePolicy; }; + +ValuePlug::CachePolicy EngineWrapper::g_cachePolicy( defaultExecuteCachePolicy() ); + static tuple languages() { std::vector languages; @@ -322,7 +369,7 @@ class ExpressionSerialiser : public NodeSerialiser } } - std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { // We delay the serialisation of the values for the engine and expression plugs // until now so that `Expression::plugSet()` can successfully restore the engine diff --git a/src/GafferModule/GraphComponentBinding.cpp b/src/GafferModule/GraphComponentBinding.cpp index 8c30411cc07..69a75c9cd26 100644 --- a/src/GafferModule/GraphComponentBinding.cpp +++ b/src/GafferModule/GraphComponentBinding.cpp @@ -45,9 +45,11 @@ #include "Gaffer/GraphComponent.h" +#include "IECorePython/ExceptionAlgo.h" #include "IECorePython/ScopedGILRelease.h" #include "boost/format.hpp" +#include "boost/python/suite/indexing/container_utils.hpp" using namespace boost::python; using namespace GafferBindings; @@ -138,6 +140,14 @@ void clearChildren( GraphComponent &g ) g.clearChildren(); } +void reorderChildren( GraphComponent &g, object pythonNewOrder ) +{ + GraphComponent::ChildContainer newOrder; + boost::python::container_utils::extend_container( newOrder, pythonNewOrder ); + IECorePython::ScopedGILRelease gilRelease; + g.reorderChildren( newOrder ); +} + GraphComponentPtr getChild( GraphComponent &g, const IECore::InternedString &n ) { return g.getChild( n ); @@ -276,6 +286,28 @@ struct BinarySlotCaller } }; +struct ChildrenReorderedSlotCaller +{ + + boost::signals::detail::unusable operator()( boost::python::object slot, GraphComponentPtr g, const std::vector &oldIndices ) + { + try + { + boost::python::list oldIndicesList; + for( auto i : oldIndices ) + { + oldIndicesList.append( i ); + } + slot( g, oldIndicesList ); + } + catch( const error_already_set &e ) + { + IECorePython::ExceptionAlgo::translatePythonException(); + } + return boost::signals::detail::unusable(); + } +}; + } // namespace void GafferModule::bindGraphComponent() @@ -293,6 +325,7 @@ void GafferModule::bindGraphComponent() .def( "addChild", &addChild ) .def( "removeChild", &removeChild ) .def( "clearChildren", &clearChildren ) + .def( "reorderChildren", &reorderChildren ) .def( "setChild", &setChild ) .def( "getChild", &getChild ) .def( "descendant", &descendant ) @@ -324,9 +357,11 @@ void GafferModule::bindGraphComponent() .def( "childAddedSignal", &GraphComponent::childAddedSignal, return_internal_reference<1>() ) .def( "childRemovedSignal", &GraphComponent::childRemovedSignal, return_internal_reference<1>() ) .def( "parentChangedSignal", &GraphComponent::parentChangedSignal, return_internal_reference<1>() ) + .def( "childrenReorderedSignal", &GraphComponent::childrenReorderedSignal, return_internal_reference<1>() ) ; SignalClass, UnarySlotCaller>( "UnarySignal" ); SignalClass, BinarySlotCaller>( "BinarySignal" ); + SignalClass, ChildrenReorderedSlotCaller>( "BinarySignal" ); } diff --git a/src/GafferModule/MetadataAlgoBinding.cpp b/src/GafferModule/MetadataAlgoBinding.cpp index 769ffd019af..e477108915a 100644 --- a/src/GafferModule/MetadataAlgoBinding.cpp +++ b/src/GafferModule/MetadataAlgoBinding.cpp @@ -54,6 +54,9 @@ using namespace Gaffer::MetadataAlgo; namespace { +// Read only +// ========= + void setReadOnlyWrapper( GraphComponent &graphComponent, bool readOnly, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; @@ -76,6 +79,9 @@ GraphComponentPtr readOnlyReasonWrapper( GraphComponent &g ) return const_cast( readOnlyReason( &g ) ); } +// Bookmarks +// ========= + void setBookmarkedWrapper( Node &node, bool bookmarked, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; @@ -107,6 +113,60 @@ NodePtr getNumericBookmarkWrapper( ScriptNode &scriptNode, int bookmark ) return getNumericBookmark( &scriptNode, bookmark ); } +// Annotations +// =========== + +void addAnnotationWrapper( Node &node, const std::string &name, const Annotation &annotation, bool persistent ) +{ + IECorePython::ScopedGILRelease gilRelease; + addAnnotation( &node, name, annotation, persistent ); +} + +object getAnnotationWrapper( const Node &node, const std::string &name, bool inheritTemplate ) +{ + Annotation a = getAnnotation( &node, name, inheritTemplate ); + return a ? object( a ) : object(); +} + +void removeAnnotationWrapper( Node &node, const std::string &name ) +{ + IECorePython::ScopedGILRelease gilRelease; + removeAnnotation( &node, name ); +} + +list annotationsWrapper( const Node &node ) +{ + std::vector names; + annotations( &node, names ); + list result; + for( const auto &n : names ) + { + result.append( n ); + } + return result; +} + +object getAnnotationTemplateWrapper( const std::string &name ) +{ + Annotation a = getAnnotationTemplate( name ); + return a ? object( a ) : object(); +} + +list annotationTemplatesWrapper( bool userOnly ) +{ + std::vector names; + annotationTemplates( names, userOnly ); + list result; + for( const auto &n : names ) + { + result.append( n ); + } + return result; +} + +// Copying +// ======= + void deprecatedCopyWrapper( const GraphComponent &from, GraphComponent &to, const IECore::StringAlgo::MatchPattern &exclude, bool persistentOnly, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; @@ -150,6 +210,9 @@ void GafferModule::bindMetadataAlgo() scope().attr( "MetadataAlgo" ) = module; scope moduleScope( module ); + // Read only + // ========= + def( "setReadOnly", &setReadOnlyWrapper, ( arg( "graphComponent" ), arg( "readOnly"), arg( "persistent" ) = true ) ); def( "getReadOnly", &getReadOnly ); def( "setChildNodesAreReadOnly", &setChildNodesAreReadOnlyWrapper, ( arg( "node" ), arg( "readOnly"), arg( "persistent" ) = true ) ); @@ -177,6 +240,9 @@ void GafferModule::bindMetadataAlgo() ( arg( "changedKey" ) ) ); + // Bookmarks + // ========= + def( "setBookmarked", &setBookmarkedWrapper, ( arg( "graphComponent" ), arg( "bookmarked"), arg( "persistent" ) = true ) ); def( "getBookmarked", &getBookmarked ); def( "bookmarkedAffectedByChange", &bookmarkedAffectedByChange ); @@ -187,6 +253,34 @@ void GafferModule::bindMetadataAlgo() def( "numericBookmark", &numericBookmark, ( arg( "node" ) ) ); def( "numericBookmarkAffectedByChange", &numericBookmarkAffectedByChange, ( arg( "changedKey" ) ) ); + // Annotations + // =========== + + class_( "Annotation" ) + .def( init( arg( "text" ) ) ) + .def( init( ( arg( "text" ), arg( "color" ) ) ) ) + .def( "text", &Annotation::text, return_value_policy() ) + .def( "color", &Annotation::color, return_value_policy() ) + .def( self == self ) + .def( self != self ) + .def( !self ) + ; + + def( "addAnnotation", &addAnnotationWrapper, ( arg( "node" ), arg( "name" ), arg( "annotation" ), arg( "persistent" ) = true ) ); + def( "getAnnotation", &getAnnotationWrapper, ( arg( "node" ), arg( "name" ), arg( "inheritTemplate" ) = false ) ); + def( "removeAnnotation", &removeAnnotationWrapper, ( arg( "node" ), arg( "name" ) ) ); + def( "annotations", &annotationsWrapper, ( arg( "node" ) ) ); + + def( "addAnnotationTemplate", &addAnnotationTemplate, ( arg( "name" ), arg( "annotation" ), arg( "user" ) = true ) ); + def( "getAnnotationTemplate", &getAnnotationTemplateWrapper, ( arg( "name" ) ) ); + def( "removeAnnotationTemplate", &removeAnnotationTemplate, arg( "name" ) ); + def( "annotationTemplates", &annotationTemplatesWrapper, arg( "userOnly" ) = false ); + + def( "annotationsAffectedByChange", &annotationsAffectedByChange, ( arg( "changedKey" ) ) ); + + // Change queries + // ============== + def( "affectedByChange", (bool (*)( const Plug *, IECore::TypeId, const IECore::StringAlgo::MatchPattern &, const Plug * ))&affectedByChange, @@ -221,6 +315,9 @@ void GafferModule::bindMetadataAlgo() ( arg( "graphComponent" ), arg( "changedNodeTypeId"), arg( "changedNode" ) ) ); + // Copying + // ======= + def( "copy", &deprecatedCopyWrapper, ( arg( "from" ), arg( "to" ), arg( "exclude" ) = "", arg( "persistentOnly" ) = true, arg( "persistent" ) = true ) ); def( "copy", ©Wrapper, ( arg( "from" ), arg( "to" ), arg( "persistent" ) ) ); def( "copyIf", ©IfWrapper, ( arg( "from" ), arg( "to" ), arg( "predicate" ), arg( "persistent" ) = true ) ); diff --git a/src/GafferModule/MetadataBinding.cpp b/src/GafferModule/MetadataBinding.cpp index fcfed373eee..2506558f35c 100644 --- a/src/GafferModule/MetadataBinding.cpp +++ b/src/GafferModule/MetadataBinding.cpp @@ -398,12 +398,12 @@ void GafferModule::bindMetadata() .def( "valueChangedSignal", &Metadata::valueChangedSignal, return_value_policy() ) .staticmethod( "valueChangedSignal" ) - .def( "nodeValueChangedSignal", (Metadata::NodeValueChangedSignal &(*)() )&Metadata::nodeValueChangedSignal, return_value_policy() ) - .def( "nodeValueChangedSignal", (Metadata::NodeValueChangedSignal2 &(*)( Gaffer::Node * ) )&Metadata::nodeValueChangedSignal, return_value_policy() ) + .def( "nodeValueChangedSignal", (Metadata::LegacyNodeValueChangedSignal &(*)() )&Metadata::nodeValueChangedSignal, return_value_policy() ) + .def( "nodeValueChangedSignal", (Metadata::NodeValueChangedSignal &(*)( Gaffer::Node * ) )&Metadata::nodeValueChangedSignal, return_value_policy() ) .staticmethod( "nodeValueChangedSignal" ) - .def( "plugValueChangedSignal", (Metadata::PlugValueChangedSignal &(*)() )&Metadata::plugValueChangedSignal, return_value_policy() ) - .def( "plugValueChangedSignal", (Metadata::PlugValueChangedSignal2 &(*)( Gaffer::Node * ) )&Metadata::plugValueChangedSignal, return_value_policy() ) + .def( "plugValueChangedSignal", (Metadata::LegacyPlugValueChangedSignal &(*)() )&Metadata::plugValueChangedSignal, return_value_policy() ) + .def( "plugValueChangedSignal", (Metadata::PlugValueChangedSignal &(*)( Gaffer::Node * ) )&Metadata::plugValueChangedSignal, return_value_policy() ) .staticmethod( "plugValueChangedSignal" ) .def( "plugsWithMetadata", &plugsWithMetadata, @@ -433,9 +433,9 @@ void GafferModule::bindMetadata() ; SignalClass, ValueChangedSlotCaller>( "ValueChangedSignal" ); - SignalClass, ValueChangedSlotCaller>( "NodeValueChangedSignal2" ); - SignalClass, ValueChangedSlotCaller>( "PlugValueChangedSignal2" ); SignalClass, ValueChangedSlotCaller>( "NodeValueChangedSignal" ); SignalClass, ValueChangedSlotCaller>( "PlugValueChangedSignal" ); + SignalClass, ValueChangedSlotCaller>( "LegacyNodeValueChangedSignal" ); + SignalClass, ValueChangedSlotCaller>( "LegacyPlugValueChangedSignal" ); } diff --git a/src/GafferModule/MonitorBinding.cpp b/src/GafferModule/MonitorBinding.cpp index a5bbd1af909..b6d5761e9fb 100644 --- a/src/GafferModule/MonitorBinding.cpp +++ b/src/GafferModule/MonitorBinding.cpp @@ -122,22 +122,34 @@ list contextMonitorVariableNames( const ContextMonitor::Statistics &s ) return result; } -void annotateWrapper1( Node &root, const PerformanceMonitor &monitor ) +void annotateWrapper1( Node &root, const PerformanceMonitor &monitor, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; - MonitorAlgo::annotate( root, monitor ); + MonitorAlgo::annotate( root, monitor, persistent ); } -void annotateWrapper2( Node &root, const PerformanceMonitor &monitor, MonitorAlgo::PerformanceMetric metric ) +void annotateWrapper2( Node &root, const PerformanceMonitor &monitor, MonitorAlgo::PerformanceMetric metric, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; - MonitorAlgo::annotate( root, monitor, metric ); + MonitorAlgo::annotate( root, monitor, metric, persistent ); } -void annotateWrapper3( Node &root, const ContextMonitor &monitor ) +void annotateWrapper3( Node &root, const ContextMonitor &monitor, bool persistent ) { IECorePython::ScopedGILRelease gilRelease; - MonitorAlgo::annotate( root, monitor ); + MonitorAlgo::annotate( root, monitor, persistent ); +} + +void removePerformanceAnnotationsWrapper( Node &root ) +{ + IECorePython::ScopedGILRelease gilRelease; + MonitorAlgo::removePerformanceAnnotations( root ); +} + +void removeContextAnnotationsWrapper( Node &root ) +{ + IECorePython::ScopedGILRelease gilRelease; + MonitorAlgo::removeContextAnnotations( root ); } } // namespace @@ -184,20 +196,23 @@ void GafferModule::bindMonitor() def( "annotate", &annotateWrapper1, - ( arg( "node" ), arg( "monitor" ) ) + ( arg( "node" ), arg( "monitor" ), arg( "persistent" ) = true ) ); def( "annotate", &annotateWrapper2, - ( arg( "node" ), arg( "monitor" ), arg( "metric" ) ) + ( arg( "node" ), arg( "monitor" ), arg( "metric" ), arg( "persistent" ) = true ) ); def( "annotate", &annotateWrapper3, - ( arg( "node" ), arg( "monitor" ) ) + ( arg( "node" ), arg( "monitor" ), arg( "persistent" ) = true ) ); + + def( "removePerformanceAnnotations", &removePerformanceAnnotationsWrapper, arg( "root" ) ); + def( "removeContextAnnotations", &removeContextAnnotationsWrapper, arg( "root" ) ); } { diff --git a/src/GafferModule/NameValuePlugBinding.cpp b/src/GafferModule/NameValuePlugBinding.cpp index 2f786b83cac..1d70ce670ab 100644 --- a/src/GafferModule/NameValuePlugBinding.cpp +++ b/src/GafferModule/NameValuePlugBinding.cpp @@ -63,13 +63,12 @@ class NameValuePlugSerialiser : public ValuePlugSerialiser return false; } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return repr( static_cast( graphComponent ), &serialisation ); } - - static std::string repr( const Gaffer::NameValuePlug *plug, const Serialisation *serialisation ) + static std::string repr( const Gaffer::NameValuePlug *plug, Serialisation *serialisation ) { if( !plug->namePlug() || !plug->valuePlug() ) { diff --git a/src/GafferModule/NodeBinding.cpp b/src/GafferModule/NodeBinding.cpp index 8354b0dce5c..63fd1e70e53 100644 --- a/src/GafferModule/NodeBinding.cpp +++ b/src/GafferModule/NodeBinding.cpp @@ -118,7 +118,6 @@ void GafferModule::bindNode() .def( "scriptNode", (ScriptNode *(Node::*)())&Node::scriptNode, return_value_policy() ) .def( "plugSetSignal", &Node::plugSetSignal, return_internal_reference<1>() ) .def( "plugInputChangedSignal", &Node::plugInputChangedSignal, return_internal_reference<1>() ) - .def( "plugFlagsChangedSignal", &Node::plugFlagsChangedSignal, return_internal_reference<1>() ) .def( "plugDirtiedSignal", &Node::plugDirtiedSignal, return_internal_reference<1>() ) .def( "errorSignal", (Node::ErrorSignal &(Node::*)())&Node::errorSignal, return_internal_reference<1>() ) ; diff --git a/src/GafferModule/ProcessMessageHandlerBinding.cpp b/src/GafferModule/ProcessMessageHandlerBinding.cpp index 4a0d37d9baf..cf8b2d0e79e 100644 --- a/src/GafferModule/ProcessMessageHandlerBinding.cpp +++ b/src/GafferModule/ProcessMessageHandlerBinding.cpp @@ -50,5 +50,5 @@ using namespace IECore; void GafferModule::bindProcessMessageHandler() { RefCountedClass( "ProcessMessageHandler" ) - .def(init() ); + .def(init() ); } diff --git a/src/GafferModule/SerialisationBinding.cpp b/src/GafferModule/SerialisationBinding.cpp index 6c43533859e..ae9fe2f823e 100644 --- a/src/GafferModule/SerialisationBinding.cpp +++ b/src/GafferModule/SerialisationBinding.cpp @@ -90,7 +90,7 @@ IECore::ObjectPtr objectFromBase64Wrapper( const std::string &base64String ) void GafferModule::bindSerialisation() { - scope s = boost::python::class_( "Serialisation", no_init ) + scope s = boost::python::class_( "Serialisation", no_init ) .def( init ( @@ -104,10 +104,11 @@ void GafferModule::bindSerialisation() .def( "parent", &parent ) .def( "identifier", &Serialisation::identifier ) .def( "childIdentifier", &childIdentifier ) + .def( "addModule", &Serialisation::addModule ) .def( "result", &Serialisation::result ) - .def( "modulePath", (std::string (*)( object & ))&Serialisation::modulePath ) + .def( "modulePath", (std::string (*)( const object & ))&Serialisation::modulePath ) .staticmethod( "modulePath" ) - .def( "classPath", (std::string (*)( object & ))&Serialisation::classPath ) + .def( "classPath", (std::string (*)( const object & ))&Serialisation::classPath ) .staticmethod( "classPath" ) .def( "objectToBase64", &objectToBase64Wrapper ) .staticmethod( "objectToBase64" ) diff --git a/src/GafferModule/SetBinding.cpp b/src/GafferModule/SetBinding.cpp index b02c0424e17..4528727aa06 100644 --- a/src/GafferModule/SetBinding.cpp +++ b/src/GafferModule/SetBinding.cpp @@ -222,4 +222,3 @@ void GafferModule::bindSet() ; } - diff --git a/src/GafferModule/SignalBinding.cpp b/src/GafferModule/SignalBinding.cpp index ad34a0aeecb..e32e0bcd8d6 100644 --- a/src/GafferModule/SignalBinding.cpp +++ b/src/GafferModule/SignalBinding.cpp @@ -77,7 +77,7 @@ struct SlotCallRange if( current == last ) { PyErr_SetString( PyExc_StopIteration, "No more results" ); - throw_error_already_set(); + throw_error_already_set(); } else { diff --git a/src/GafferModule/SplinePlugBinding.cpp b/src/GafferModule/SplinePlugBinding.cpp index b70f35e9dd2..98f572ea821 100644 --- a/src/GafferModule/SplinePlugBinding.cpp +++ b/src/GafferModule/SplinePlugBinding.cpp @@ -140,7 +140,7 @@ class SplinePlugSerialiser : public ValuePlugSerialiser public : - std::string postConstructor( const Gaffer::GraphComponent *plug, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *plug, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = ValuePlugSerialiser::postConstructor( plug, identifier, serialisation ); if( !omitValue( plug, serialisation ) ) diff --git a/src/GafferModule/SpreadsheetBinding.cpp b/src/GafferModule/SpreadsheetBinding.cpp index 76a961875c3..64c12e43383 100644 --- a/src/GafferModule/SpreadsheetBinding.cpp +++ b/src/GafferModule/SpreadsheetBinding.cpp @@ -109,7 +109,7 @@ class RowsPlugSerialiser : public ValuePlugSerialiser public : - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = ValuePlugSerialiser::postConstructor( graphComponent, identifier, serialisation ); const auto *plug = static_cast( graphComponent ); @@ -202,7 +202,7 @@ class RowsPlugSerialiser : public ValuePlugSerialiser // // The strategy is a recursion where each child returns `true` if it needs the caller // the emit a serialisation on its behalf and `false` otherwise. - bool defaultValueSerialisationsWalk( const ValuePlug *plug, const ValuePlug *defaultPlug, const Serialisation &serialisation, std::string &result ) const + bool defaultValueSerialisationsWalk( const ValuePlug *plug, const ValuePlug *defaultPlug, Serialisation &serialisation, std::string &result ) const { const size_t numChildren = plug->children().size(); assert( defaultPlug->children().size() == numChildren ); @@ -257,7 +257,7 @@ class RowsPlugSerialiser : public ValuePlugSerialiser /// \todo Build identifier recursively (but lazily) and making sure to use the faster /// version of `childIdentifier()`. const std::string childPlugIdentifier = serialisation.identifier( childPlug ); - result += childPlugIdentifier + ".setValue( " + valueRepr( pythonDefaultValue ) + " )\n"; + result += childPlugIdentifier + ".setValue( " + valueRepr( pythonDefaultValue, &serialisation ) + " )\n"; } return false; diff --git a/src/GafferModule/StringPlugBinding.cpp b/src/GafferModule/StringPlugBinding.cpp index e48262eb496..6fedd5f49aa 100644 --- a/src/GafferModule/StringPlugBinding.cpp +++ b/src/GafferModule/StringPlugBinding.cpp @@ -101,12 +101,16 @@ std::string substitutionsRepr( unsigned substitutions ) return result; } -std::string serialisationRepr( const Gaffer::StringPlug *plug, const Serialisation *serialisation ) +std::string serialisationRepr( const Gaffer::StringPlug *plug, Serialisation *serialisation ) { std::string extraArguments; if( plug->substitutions() != IECore::StringAlgo::AllSubstitutions ) { extraArguments = "substitutions = " + substitutionsRepr( plug->substitutions() ); + if( serialisation ) + { + serialisation->addModule( "IECore" ); + } } return ValuePlugSerialiser::repr( plug, extraArguments, serialisation ); } @@ -121,7 +125,7 @@ class StringPlugSerialiser : public ValuePlugSerialiser public : - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return serialisationRepr( static_cast( graphComponent ), &serialisation ); } diff --git a/src/GafferModule/SubGraphBinding.cpp b/src/GafferModule/SubGraphBinding.cpp index 8c5c59c53df..2ebe4bf3da2 100644 --- a/src/GafferModule/SubGraphBinding.cpp +++ b/src/GafferModule/SubGraphBinding.cpp @@ -104,7 +104,7 @@ class BoxIOSerialiser : public NodeSerialiser return NodeSerialiser::childNeedsConstruction( child, serialisation ); } - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); @@ -139,7 +139,7 @@ class BoxIOSerialiser : public NodeSerialiser return result; } - std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postScript( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = NodeSerialiser::postScript( graphComponent, identifier, serialisation ); @@ -278,7 +278,7 @@ struct ReferenceLoadedSlotCaller class ReferenceSerialiser : public NodeSerialiser { - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { const Reference *r = static_cast( graphComponent ); diff --git a/src/GafferModule/SwitchBinding.cpp b/src/GafferModule/SwitchBinding.cpp index d38f0674a04..afd4fe02209 100644 --- a/src/GafferModule/SwitchBinding.cpp +++ b/src/GafferModule/SwitchBinding.cpp @@ -82,7 +82,7 @@ class SwitchSerialiser : public NodeSerialiser return NodeSerialiser::childNeedsConstruction( child, serialisation ); } - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string result = NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); diff --git a/src/GafferModule/Transform2DPlugBinding.cpp b/src/GafferModule/Transform2DPlugBinding.cpp index 9baf690e33f..2dbd2ab446d 100644 --- a/src/GafferModule/Transform2DPlugBinding.cpp +++ b/src/GafferModule/Transform2DPlugBinding.cpp @@ -62,7 +62,7 @@ class Transform2DPlugSerialiser : public ValuePlugSerialiser return false; } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return repr( static_cast( graphComponent ), &serialisation ); } diff --git a/src/GafferModule/TransformPlugBinding.cpp b/src/GafferModule/TransformPlugBinding.cpp index de912e5b2d4..bc4280ef7e3 100644 --- a/src/GafferModule/TransformPlugBinding.cpp +++ b/src/GafferModule/TransformPlugBinding.cpp @@ -62,12 +62,12 @@ class TransformPlugSerialiser : public ValuePlugSerialiser return false; } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { return repr( static_cast( graphComponent ), &serialisation ); } - static std::string repr( const TransformPlug *plug, const Serialisation *serialisation ) + static std::string repr( const TransformPlug *plug, Serialisation *serialisation ) { std::string result = "Gaffer.TransformPlug( \"" + plug->getName().string() + "\", "; diff --git a/src/GafferOSL/OSLCode.cpp b/src/GafferOSL/OSLCode.cpp index e9ba6e8dcc8..27f96cb4b2a 100644 --- a/src/GafferOSL/OSLCode.cpp +++ b/src/GafferOSL/OSLCode.cpp @@ -129,14 +129,14 @@ string generate( const OSLCode *shader, string &shaderName ) string result; - for( PlugIterator it( shader->parametersPlug() ); !it.done(); ++it ) + for( Plug::Iterator it( shader->parametersPlug() ); !it.done(); ++it ) { result += parameter( it->get() ); } result += "\n"; - for( PlugIterator it( shader->outPlug() ); !it.done(); ++it ) + for( Plug::Iterator it( shader->outPlug() ); !it.done(); ++it ) { result += parameter( it->get() ); } diff --git a/src/GafferOSL/OSLExpressionEngine.cpp b/src/GafferOSL/OSLExpressionEngine.cpp index e1984395d0a..725c0840342 100644 --- a/src/GafferOSL/OSLExpressionEngine.cpp +++ b/src/GafferOSL/OSLExpressionEngine.cpp @@ -35,6 +35,12 @@ ////////////////////////////////////////////////////////////////////////// #include "GafferOSL/Private/CapturingErrorHandler.h" +// `OpenImageIO/strutil.h` includes `OpenImageIO/detail/farmhash.h`, and +// that leaks all sorts of macros, including one for `fmix` which breaks +// the definition of Cortex's perfectly well namespaced `IECore::fmix()` +// in `IECore/MurmurHash.inl`. So we must undefine the offending macro. +// See https://github.com/OpenImageIO/oiio/issues/3000. +#undef fmix #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/Context.h" @@ -126,16 +132,18 @@ class RendererServices : public OSL::RendererServices return false; } - const Data *data = renderState->context->get( name.c_str(), nullptr ); + // TODO - might be nice if there was some way to speed this up by directly querying the type matching + // the TypeDesc, instead of getting as a generic Data? + const DataPtr data = renderState->context->getAsData( name.c_str(), nullptr ); if( !data ) { return false; } - IECoreImage::OpenImageIOAlgo::DataView dataView( data, /* createUStrings = */ true ); + IECoreImage::OpenImageIOAlgo::DataView dataView( data.get(), /* createUStrings = */ true ); if( !dataView.data ) { - if( auto b = runTimeCast( data ) ) + if( auto b = runTimeCast( data.get() ) ) { // BoolData isn't supported by `DataView` because `OIIO::TypeDesc` doesn't // have a boolean type. We could work around this in `DataView` by casting to @@ -351,9 +359,10 @@ class OSLExpressionEngine : public Gaffer::Expression::Engine IECore::ConstObjectVectorPtr execute( const Gaffer::Context *context, const std::vector &proxyInputs ) const override { ShadingSystem *s = shadingSystem(); - OSL::ShadingContext *shadingContext = s->get_context( /* threadInfo */ nullptr ); + OSL::PerThreadInfo *threadInfo = s->create_thread_info(); + OSL::ShadingContext *shadingContext = s->get_context( threadInfo ); - OSL::ShaderGlobals shaderGlobals; + OSL::ShaderGlobals shaderGlobals; memset( (void *)&shaderGlobals, 0, sizeof( ShaderGlobals ) ); if( m_needsTime ) @@ -411,9 +420,15 @@ class OSLExpressionEngine : public Gaffer::Expression::Engine } s->release_context( shadingContext ); + s->destroy_thread_info( threadInfo ); return result; } + ValuePlug::CachePolicy executeCachePolicy() const override + { + return ValuePlug::CachePolicy::Legacy; + } + void apply( Gaffer::ValuePlug *proxyOutput, const Gaffer::ValuePlug *topLevelProxyOutput, const IECore::Object *value ) const override { switch( value->typeId() ) @@ -759,7 +774,7 @@ class OSLExpressionEngine : public Gaffer::Expression::Engine // prepend it to the source. shaderName = "oslExpression" + MurmurHash().append( result ).toString(); - result = "#include \"GafferOSL/Expression.h\"\n\nshader " + shaderName + " " + result; + result = "#include \"GafferOSL/Expression.h\"\n\nshader " + shaderName + " " + result; return result; } diff --git a/src/GafferOSL/OSLImage.cpp b/src/GafferOSL/OSLImage.cpp index e041bb34bf1..d6af9bae8aa 100644 --- a/src/GafferOSL/OSLImage.cpp +++ b/src/GafferOSL/OSLImage.cpp @@ -41,6 +41,8 @@ #include "GafferOSL/OSLShader.h" #include "GafferOSL/ShadingEngine.h" +#include "GafferImage/ImageAlgo.h" + #include "Gaffer/Context.h" #include "Gaffer/NameValuePlug.h" #include "Gaffer/ScriptNode.h" @@ -72,6 +74,7 @@ OSLImage::OSLImage( const std::string &name ) addChild( new GafferScene::ShaderPlug( "__shader", Plug::In, Plug::Default & ~Plug::Serialisable ) ); addChild( new Gaffer::ObjectPlug( "__shading", Gaffer::Plug::Out, new CompoundData() ) ); + addChild( new Gaffer::StringVectorDataPlug( "__affectedChannels", Gaffer::Plug::Out, new StringVectorData() ) ); addChild( new Plug( "channels", Plug::In, Plug::Default & ~Plug::AcceptsInputs ) ); addChild( new OSLCode( "__oslCode" ) ); @@ -124,44 +127,54 @@ const Gaffer::ObjectPlug *OSLImage::shadingPlug() const return getChild( g_firstPlugIndex + 2 ); } +Gaffer::StringVectorDataPlug *OSLImage::affectedChannelsPlug() +{ + return getChild( g_firstPlugIndex + 3 ); +} + +const Gaffer::StringVectorDataPlug *OSLImage::affectedChannelsPlug() const +{ + return getChild( g_firstPlugIndex + 3 ); +} + Gaffer::Plug *OSLImage::channelsPlug() { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } const Gaffer::Plug *OSLImage::channelsPlug() const { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } GafferOSL::OSLCode *OSLImage::oslCode() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const GafferOSL::OSLCode *OSLImage::oslCode() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } GafferImage::Constant *OSLImage::defaultConstant() { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } const GafferImage::Constant *OSLImage::defaultConstant() const { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 6 ); } GafferImage::ImagePlug *OSLImage::defaultInPlug() { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } const GafferImage::ImagePlug *OSLImage::defaultInPlug() const { - return getChild( g_firstPlugIndex + 6 ); + return getChild( g_firstPlugIndex + 7 ); } const GafferImage::ImagePlug *OSLImage::defaultedInPlug() const @@ -193,9 +206,21 @@ void OSLImage::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outpu { outputs.push_back( shadingPlug() ); } - else if( input == shadingPlug() ) + + if( input == shaderPlug() || input == shadingPlug() ) + { + // shaderPlug() is affected by all the children of channelsPlug due to connections + // made in updateChannels, so this implicitly catches any changes to the channelsPlug + outputs.push_back( affectedChannelsPlug() ); + } + + if( input == affectedChannelsPlug() || input == inPlug()->channelNamesPlug() ) { outputs.push_back( outPlug()->channelNamesPlug() ); + } + + if( input == shadingPlug() ) + { outputs.push_back( outPlug()->channelDataPlug() ); } @@ -236,6 +261,41 @@ void OSLImage::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *con { hashShading( context, h ); } + else if( output == affectedChannelsPlug() ) + { + bool hasClosures = false; + for( NameValuePlug::Iterator inputPlug( channelsPlug() ); !inputPlug.done(); ++inputPlug ) + { + BoolPlug* enabledPlug = (*inputPlug)->enabledPlug(); + if( enabledPlug ) + { + if( !enabledPlug->getValue() ) + { + continue; + } + } + + (*inputPlug)->namePlug()->hash( h ); + int valueType = (*inputPlug)->valuePlug()->typeId(); + if( valueType == ClosurePlug::staticTypeId() ) + { + hasClosures = true; + } + h.append( valueType ); + } + + if( hasClosures ) + { + const Box2i dataWindow = defaultedInPlug()->dataWindowPlug()->getValue(); + if( !dataWindow.isEmpty() ) + { + ImagePlug::ChannelDataScope channelDataScope( context ); + Imath::V2i dataTileOrigin = ImagePlug::tileOrigin( dataWindow.min ); + channelDataScope.setTileOrigin( &dataTileOrigin ); + shadingPlug()->hash( h ); + } + } + } } void OSLImage::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const @@ -245,12 +305,78 @@ void OSLImage::compute( Gaffer::ValuePlug *output, const Gaffer::Context *contex static_cast( output )->setValue( computeShading( context ) ); return; } + else if( output == affectedChannelsPlug() ) + { + set result; + + bool hasClosures = false; + for( NameValuePlug::Iterator inputPlug( channelsPlug() ); !inputPlug.done(); ++inputPlug ) + { + BoolPlug* enabledPlug = (*inputPlug)->enabledPlug(); + if( enabledPlug ) + { + if( !enabledPlug->getValue() ) + { + continue; + } + } + + std::string name = (*inputPlug)->namePlug()->getValue(); + int valueType = (*inputPlug)->valuePlug()->typeId(); + switch( valueType ) + { + case ClosurePlugTypeId : + hasClosures = true; + break; + case FloatPlugTypeId : + result.insert( name ); + break; + case Color3fPlugTypeId : + // Logic must imitate outLayer in shaders/GafferOSL/ImageProcessing.h + result.insert( ImageAlgo::channelName( name, "R" ) ); + result.insert( ImageAlgo::channelName( name, "G" ) ); + result.insert( ImageAlgo::channelName( name, "B" ) ); + break; + } + } + + if( hasClosures ) + { + // If there are closures, then new names can be created during shading, and we have to actually + // run the shader + const Box2i dataWindow = defaultedInPlug()->dataWindowPlug()->getValue(); + if( !dataWindow.isEmpty() ) + { + ImagePlug::ChannelDataScope channelDataScope( context ); + Imath::V2i dataTileOrigin = ImagePlug::tileOrigin( dataWindow.min ); + channelDataScope.setTileOrigin( &dataTileOrigin ); + + ConstCompoundDataPtr shading = runTimeCast( shadingPlug()->getValue() ); + for( CompoundDataMap::const_iterator it = shading->readable().begin(), eIt = shading->readable().end(); it != eIt; ++it ) + { + result.insert( it->first ); + } + } + } + + // Note that this result has been sorted through insertion into the set + StringVectorDataPtr resultVector = new StringVectorData( vector( result.begin(), result.end() ) ); + static_cast( output )->setValue( resultVector ); + } ImageProcessor::compute( output, context ); } Gaffer::ValuePlug::CachePolicy OSLImage::computeCachePolicy( const Gaffer::ValuePlug *output ) const { + if( output == shadingPlug() ) + { + // shadingEngine->shade( ... ) uses tbb internally, so we need to at least isolate it to + // prevent hangs due to task stealing causing false recursion. + // Using TaskCollaboration allows for an actual speedup when multiple threads request the + // same channelData + return ValuePlug::CachePolicy::TaskCollaboration; + } if( output == outPlug()->channelDataPlug() ) { // We disable caching for the channel data plug, because our compute @@ -267,13 +393,7 @@ void OSLImage::hashChannelNames( const GafferImage::ImagePlug *output, const Gaf ImageProcessor::hashChannelNames( output, context, h ); defaultedInPlug()->channelNamesPlug()->hash( h ); - const Box2i dataWindow = defaultedInPlug()->dataWindowPlug()->getValue(); - if( !dataWindow.isEmpty() ) - { - ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setTileOrigin( ImagePlug::tileOrigin( dataWindow.min ) ); - shadingPlug()->hash( h ); - } + affectedChannelsPlug()->hash( h); } IECore::ConstStringVectorDataPtr OSLImage::computeChannelNames( const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const @@ -282,17 +402,10 @@ IECore::ConstStringVectorDataPtr OSLImage::computeChannelNames( const Gaffer::Co set result( channelNamesData->readable().begin(), channelNamesData->readable().end() ); - const Box2i dataWindow = defaultedInPlug()->dataWindowPlug()->getValue(); - if( !dataWindow.isEmpty() ) + ConstStringVectorDataPtr affectedChannels = affectedChannelsPlug()->getValue(); + for( const std::string &i : affectedChannels->readable() ) { - ImagePlug::ChannelDataScope channelDataScope( context ); - channelDataScope.setTileOrigin( ImagePlug::tileOrigin( dataWindow.min ) ); - - ConstCompoundDataPtr shading = runTimeCast( shadingPlug()->getValue() ); - for( CompoundDataMap::const_iterator it = shading->readable().begin(), eIt = shading->readable().end(); it != eIt; ++it ) - { - result.insert( it->first ); - } + result.insert( i ); } return new StringVectorData( vector( result.begin(), result.end() ) ); @@ -301,36 +414,56 @@ IECore::ConstStringVectorDataPtr OSLImage::computeChannelNames( const Gaffer::Co void OSLImage::hashChannelData( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ImageProcessor::hashChannelData( output, context, h ); + const std::string &channelName = context->get( ImagePlug::channelNameContextName ); - h.append( channelName ); - shadingPlug()->hash( h ); + const V2i &tileOrigin = context->get( ImagePlug::tileOriginContextName ); - ConstStringVectorDataPtr channelNamesData; { - ImagePlug::GlobalScope c( context ); - channelNamesData = defaultedInPlug()->channelNamesPlug()->getValue(); - } + Context::EditableScope c( context ); + c.remove( ImagePlug::channelNameContextName ); + c.remove( ImagePlug::tileOriginContextName ); - if( - std::find( channelNamesData->readable().begin(), channelNamesData->readable().end(), channelName ) != - channelNamesData->readable().end() - ) - { - defaultedInPlug()->channelDataPlug()->hash( h ); + ConstStringVectorDataPtr affectedChannels = affectedChannelsPlug()->getValue(); + + if( std::binary_search( affectedChannels->readable().begin(), affectedChannels->readable().end(), channelName ) ) + { + // Channel is affected, include shading hash + c.set( ImagePlug::tileOriginContextName, &tileOrigin ); + shadingPlug()->hash( h ); + h.append( channelName ); + return; + } } + + // Not in affectedChannels, just pass through the input hash + h = defaultedInPlug()->channelDataPlug()->hash(); } IECore::ConstFloatVectorDataPtr OSLImage::computeChannelData( const std::string &channelName, const Imath::V2i &tileOrigin, const Gaffer::Context *context, const GafferImage::ImagePlug *parent ) const { - ConstCompoundDataPtr shadedPoints = runTimeCast( shadingPlug()->getValue() ); - ConstFloatVectorDataPtr result = shadedPoints->member( channelName ); - - if( !result ) { - result = defaultedInPlug()->channelDataPlug()->getValue(); + Context::EditableScope c( context ); + c.remove( ImagePlug::channelNameContextName ); + c.remove( ImagePlug::tileOriginContextName ); + + ConstStringVectorDataPtr affectedChannels = affectedChannelsPlug()->getValue(); + + if( std::binary_search( affectedChannels->readable().begin(), affectedChannels->readable().end(), channelName ) ) + { + // Channel is affected, evaluate shading + c.set( ImagePlug::tileOriginContextName, &tileOrigin ); + + ConstCompoundDataPtr shadedPoints = runTimeCast( shadingPlug()->getValue() ); + ConstFloatVectorDataPtr result = shadedPoints->member( channelName ); + if( result ) + { + return result; + } + } } - return result; + // Not written by OSL, just pass through the input data + return defaultedInPlug()->channelDataPlug()->getValue(); } void OSLImage::hashFormat( const GafferImage::ImagePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const @@ -379,19 +512,18 @@ void OSLImage::hashShading( const Gaffer::Context *context, IECore::MurmurHash & deep = defaultedInPlug()->deepPlug()->getValue(); } + if( deep ) { - ImagePlug::ChannelDataScope c( context ); - if( deep ) - { - c.remove( ImagePlug::channelNameContextName ); - defaultedInPlug()->sampleOffsetsPlug()->hash( h ); - } + defaultedInPlug()->sampleOffsetsPlug()->hash( h ); + } + { + ImagePlug::ChannelDataScope c( context ); for( const auto &channelName : channelNamesData->readable() ) { if( shadingEngine->needsAttribute( channelName ) ) { - c.setChannelName( channelName ); + c.setChannelName( &channelName ); defaultedInPlug()->channelDataPlug()->hash( h ); } } @@ -428,20 +560,19 @@ IECore::ConstCompoundDataPtr OSLImage::computeShading( const Gaffer::Context *co CompoundDataPtr shadingPoints = new CompoundData(); ConstIntVectorDataPtr sampleOffsetsData; + if( deep ) + { + sampleOffsetsData = defaultedInPlug()->sampleOffsetsPlug()->getValue(); + } { ImagePlug::ChannelDataScope c( context ); - if( deep ) - { - c.remove( ImagePlug::channelNameContextName ); - sampleOffsetsData = defaultedInPlug()->sampleOffsetsPlug()->getValue(); - } for( const auto &channelName : channelNamesData->readable() ) { if( shadingEngine->needsAttribute( channelName ) ) { - c.setChannelName( channelName ); + c.setChannelName( &channelName ); shadingPoints->writable()[channelName] = boost::const_pointer_cast( defaultedInPlug()->channelDataPlug()->getValue() ); @@ -544,7 +675,7 @@ void OSLImage::updateChannels() std::string code = "Ci = 0;\n"; - for( NameValuePlugIterator inputPlug( channelsPlug() ); !inputPlug.done(); ++inputPlug ) + for( NameValuePlug::Iterator inputPlug( channelsPlug() ); !inputPlug.done(); ++inputPlug ) { std::string prefix = ""; BoolPlug* enabledPlug = (*inputPlug)->enabledPlug(); diff --git a/src/GafferOSL/OSLLight.cpp b/src/GafferOSL/OSLLight.cpp index 59adacfedfc..9c61037a548 100644 --- a/src/GafferOSL/OSLLight.cpp +++ b/src/GafferOSL/OSLLight.cpp @@ -268,4 +268,3 @@ IECoreScene::ConstShaderNetworkPtr OSLLight::computeLight( const Gaffer::Context IECore::ConstCompoundObjectPtr shaderAttributes = shaderInPlug()->attributes(); return shaderAttributes->member( "osl:light" ); } - diff --git a/src/GafferOSL/OSLObject.cpp b/src/GafferOSL/OSLObject.cpp index 98e0388e595..8a040eb835c 100644 --- a/src/GafferOSL/OSLObject.cpp +++ b/src/GafferOSL/OSLObject.cpp @@ -463,7 +463,7 @@ void OSLObject::updatePrimitiveVariables() std::string code = "closure color out = 0;\n"; - for( NameValuePlugIterator inputPlug( primitiveVariablesPlug() ); !inputPlug.done(); ++inputPlug ) + for( NameValuePlug::Iterator inputPlug( primitiveVariablesPlug() ); !inputPlug.done(); ++inputPlug ) { std::string prefix = ""; BoolPlug* enabledPlug = (*inputPlug)->enabledPlug(); diff --git a/src/GafferOSL/OSLShader.cpp b/src/GafferOSL/OSLShader.cpp index 366244152b4..474773a1e91 100644 --- a/src/GafferOSL/OSLShader.cpp +++ b/src/GafferOSL/OSLShader.cpp @@ -39,8 +39,6 @@ #include "GafferOSL/ClosurePlug.h" #include "GafferOSL/ShadingEngine.h" -#include "GafferScene/RendererAlgo.h" - #include "Gaffer/CompoundNumericPlug.h" #include "Gaffer/Metadata.h" #include "Gaffer/NumericPlug.h" @@ -60,7 +58,6 @@ #include "boost/algorithm/string/predicate.hpp" #include "boost/container/flat_set.hpp" - #include "tbb/mutex.h" using namespace std; @@ -136,8 +133,38 @@ ShadingEngineCache g_shadingEngineCache( getter, 10000 ); typedef boost::container::flat_set ShaderTypeSet; ShaderTypeSet &compatibleShaders() { - static ShaderTypeSet g_compatibleShaders; - return g_compatibleShaders; + static ShaderTypeSet g_compatibleShaders; + return g_compatibleShaders; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////// +// LRUCache of OSLQueries +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +using OSLQueryPtr = shared_ptr; +using QueryCache = IECorePreview::LRUCache; + +QueryCache &queryCache() +{ + static QueryCache g_cache( + [] ( const std::string &shaderName, size_t &cost ) { + const char *searchPath = getenv( "OSL_SHADER_PATHS" ); + OSLQueryPtr query = make_shared(); + if( !query->open( shaderName, searchPath ? searchPath : "" ) ) + { + throw Exception( query->geterror() ); + } + cost = 1; + return query; + }, + 10000 + ); + return g_cache; } } // namespace @@ -783,7 +810,7 @@ Plug *loadStructParameter( const OSLQuery &query, const OSLQuery::Parameter *par if( existingPlug ) { // Transfer old plugs onto the replacement. - for( PlugIterator it( existingPlug ); !it.done(); ++it ) + for( Plug::Iterator it( existingPlug ); !it.done(); ++it ) { result->addChild( *it ); } @@ -1006,13 +1033,7 @@ void OSLShader::loadShader( const std::string &shaderName, bool keepExistingValu return; } - const char *searchPath = getenv( "OSL_SHADER_PATHS" ); - - OSLQuery query; - if( !query.open( shaderName, searchPath ? searchPath : "" ) ) - { - throw Exception( query.geterror() ); - } + OSLQueryPtr query = queryCache().get( shaderName ); const bool outPlugHadChildren = existingOut ? existingOut->children().size() : false; if( !keepExistingValues ) @@ -1029,7 +1050,7 @@ void OSLShader::loadShader( const std::string &shaderName, bool keepExistingValu m_metadata = nullptr; namePlug->source()->setValue( shaderName ); - typePlug->source()->setValue( std::string( "osl:" ) + query.shadertype().c_str() ); + typePlug->source()->setValue( std::string( "osl:" ) + query->shadertype().c_str() ); const IECore::CompoundData *metadata = OSLShader::metadata(); const IECore::CompoundData *parameterMetadata = nullptr; @@ -1038,7 +1059,7 @@ void OSLShader::loadShader( const std::string &shaderName, bool keepExistingValu parameterMetadata = metadata->member( "parameter" ); } - loadShaderParameters( query, parametersPlug, parameterMetadata ); + loadShaderParameters( *query, parametersPlug, parameterMetadata ); if( existingOut ) { @@ -1055,7 +1076,7 @@ void OSLShader::loadShader( const std::string &shaderName, bool keepExistingValu // We had an out plug but it was the wrong type (we used // to use a CompoundPlug before that was deprecated). Move // over any existing child plugs onto our replacement. - for( PlugIterator it( existingOut ); !it.done(); ++it ) + for( Plug::Iterator it( existingOut ); !it.done(); ++it ) { outPlug->addChild( *it ); } @@ -1063,9 +1084,9 @@ void OSLShader::loadShader( const std::string &shaderName, bool keepExistingValu setChild( "out", outPlug ); } - if( query.shadertype() == "shader" ) + if( query->shadertype() == "shader" ) { - loadShaderParameters( query, outPlug(), parameterMetadata ); + loadShaderParameters( *query, outPlug(), parameterMetadata ); } else { @@ -1270,8 +1291,9 @@ const IECore::Data *OSLShader::parameterMetadata( const Gaffer::Plug *plug, cons void OSLShader::reloadShader() { - // Remove any metadata cache entry for the given shader name, allowing - // it to be reloaded fresh if it has changed + // Remove any cache entries for the given shader name, allowing + // them to be reloaded fresh if the shader has changed. + queryCache().erase( namePlug()->getValue() ); g_metadataCache.erase( namePlug()->getValue() ); Shader::reloadShader(); } @@ -1281,4 +1303,3 @@ bool OSLShader::registerCompatibleShader( const IECore::InternedString shaderTyp ShaderTypeSet &cs = compatibleShaders(); return cs.insert( shaderType ).second; } - diff --git a/src/GafferOSL/ShadingEngine.cpp b/src/GafferOSL/ShadingEngine.cpp index b73b1454c9f..0be0d54b79a 100644 --- a/src/GafferOSL/ShadingEngine.cpp +++ b/src/GafferOSL/ShadingEngine.cpp @@ -82,7 +82,7 @@ using namespace GafferOSL; // keyword matrix parameter macro. reference: OSL/genclosure.h #define CLOSURE_MATRIX_KEYPARAM(st, fld, key) \ - { TypeDesc::TypeMatrix44, (int)reckless_offsetof(st, fld), key, fieldsize(st, fld) } + { TypeDesc::TypeMatrix44, (int)reckless_offsetof(st, fld), key, fieldsize(st, fld) } ////////////////////////////////////////////////////////////////////////// // Conversion utilities @@ -282,8 +282,10 @@ class RenderState const Gaffer::Context *context ) { - for( CompoundDataMap::const_iterator it = shadingPoints->readable().begin(), - eIt = shadingPoints->readable().end(); it != eIt; ++it ) + for( + CompoundDataMap::const_iterator it = shadingPoints->readable().begin(), + eIt = shadingPoints->readable().end(); it != eIt; ++it + ) { UserData userData; userData.dataView = IECoreImage::OpenImageIOAlgo::DataView( it->second.get(), /* createUStrings = */ true ); @@ -307,13 +309,14 @@ class RenderState for( const auto &name : contextVariablesNeeded ) { + DataPtr contextEntryData = context->getAsData( name.string(), nullptr ); m_contextVariables.insert( make_pair( ustring( name.c_str() ), - IECoreImage::OpenImageIOAlgo::DataView( - context->get( name.string(), nullptr ), - /* createUStrings = */ true - ) + ContextData{ + IECoreImage::OpenImageIOAlgo::DataView( contextEntryData.get(), /* createUStrings = */ true ), + contextEntryData + } ) ); } @@ -327,7 +330,7 @@ class RenderState return false; } - return ShadingSystem::convert_value( value, type, it->second.data, it->second.type ); + return ShadingSystem::convert_value( value, type, it->second.dataView.data, it->second.dataView.type ); } bool userData( size_t pointIndex, ustring name, TypeDesc type, void *value ) const @@ -393,8 +396,14 @@ class RenderState size_t numValues; }; + struct ContextData + { + IECoreImage::OpenImageIOAlgo::DataView dataView; + ConstDataPtr dataStorage; + }; + container::flat_map m_userData; - container::flat_map m_contextVariables; + container::flat_map m_contextVariables; }; @@ -623,23 +632,6 @@ OSL::ShadingSystem *shadingSystem() return g_shadingSystem; } -// This just exists to ensure that release is called -struct ShadingContextWrapper -{ - - ShadingContextWrapper() - : shadingContext( ::shadingSystem()->get_context( /* threadInfo */ nullptr ) ) - { - } - - ~ShadingContextWrapper() - { - ::shadingSystem()->release_context( shadingContext ); - } - - ShadingContext *shadingContext; -}; - } // namespace @@ -842,6 +834,28 @@ class ShadingResults }; +// Thread specific information needed during shading - this includes both the per-thread result cache, +// and the OSL machinery that needs to be stored per "renderer-thread" +struct ThreadInfo +{ + ThreadInfo() + : oslThreadInfo( ::shadingSystem()->create_thread_info() ), + shadingContext( ::shadingSystem()->get_context( oslThreadInfo ) ) + { + } + + ~ThreadInfo() + { + ::shadingSystem()->release_context( shadingContext ); + ::shadingSystem()->destroy_thread_info( oslThreadInfo ); + } + + ShadingResults::DebugResultsMap debugResults; + OSL::PerThreadInfo *oslThreadInfo; + OSL::ShadingContext *shadingContext; +}; + + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -878,7 +892,7 @@ void declareSpline( const InternedString &name, const Spline &spline, ShadingSys basis = "linear"; } - OSLShader::prepareSplineCVsForOSL( positions, values, basis ); + OSLShader::prepareSplineCVsForOSL( positions, values, basis ); TypeDesc positionsType = TypeDescFromType::typeDesc(); TypeDesc valuesType = TypeDescFromType::typeDesc(); @@ -1116,15 +1130,7 @@ void ShadingEngine::hash( IECore::MurmurHash &h ) const } for( const auto &name : m_contextVariablesNeeded ) { - const IECore::Data *d = context->get( name, nullptr ); - if( d ) - { - d->hash( h ); - } - else - { - h.append( 0 ); - } + h.append( context->variableHash( name ) ); } } } @@ -1198,17 +1204,16 @@ IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points // Iterate over the input points, doing the shading as we go - typedef tbb::enumerable_thread_specific ThreadLocalDebugResults; - ThreadLocalDebugResults debugResultsCache; + tbb::enumerable_thread_specific threadInfoCache; const IECore::Canceller *canceller = context->canceller(); ShadingSystem *shadingSystem = ::shadingSystem(); ShaderGroup &shaderGroup = **static_cast( m_shaderGroupRef ); - auto f = [&shadingSystem, &renderState, &results, &shaderGlobals, &p, &u, &v, &uv, &n, &shaderGroup, &debugResultsCache, canceller]( const tbb::blocked_range &r ) + auto f = [&shadingSystem, &renderState, &results, &shaderGlobals, &p, &u, &v, &uv, &n, &shaderGroup, &threadInfoCache, canceller]( const tbb::blocked_range &r ) { - ThreadLocalDebugResults::reference resultCache = debugResultsCache.local(); + ThreadInfo &threadInfo = threadInfoCache.local(); ThreadRenderState threadRenderState( renderState ); @@ -1216,7 +1221,6 @@ IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points threadShaderGlobals.renderstate = &threadRenderState; - ShadingContextWrapper contextWrapper; for( size_t i = r.begin(); i < r.end(); ++i ) { IECore::Canceller::check( canceller ); @@ -1248,9 +1252,9 @@ IECore::CompoundDataPtr ShadingEngine::shade( const IECore::CompoundData *points threadShaderGlobals.Ci = nullptr; threadRenderState.pointIndex = i; - shadingSystem->execute( contextWrapper.shadingContext, shaderGroup, threadShaderGlobals ); + shadingSystem->execute( threadInfo.shadingContext, shaderGroup, threadShaderGlobals ); - results.addResult( i, threadShaderGlobals.Ci, resultCache ); + results.addResult( i, threadShaderGlobals.Ci, threadInfo.debugResults ); } }; diff --git a/src/GafferOSLUI/OSLImageUI.cpp b/src/GafferOSLUI/OSLImageUI.cpp index 968275b6c0b..930303dc16f 100644 --- a/src/GafferOSLUI/OSLImageUI.cpp +++ b/src/GafferOSLUI/OSLImageUI.cpp @@ -110,7 +110,7 @@ class OSLImagePlugAdder : public PlugAdder std::set usedNames() const { std::set used; - for( NameValuePlugIterator it( m_plugsParent.get() ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( m_plugsParent.get() ); !it.done(); ++it ) { // TODO - this method for checking if a plug variesWithContext should probably live in PlugAlgo // ( it's based on Switch::variesWithContext ) @@ -322,5 +322,3 @@ struct Registration Registration g_registration; } // namespace - - diff --git a/src/GafferOSLUI/OSLObjectUI.cpp b/src/GafferOSLUI/OSLObjectUI.cpp index 50f12ece1ff..5527ba8acf6 100644 --- a/src/GafferOSLUI/OSLObjectUI.cpp +++ b/src/GafferOSLUI/OSLObjectUI.cpp @@ -102,7 +102,7 @@ class OSLObjectPlugAdder : public PlugAdder std::set usedNames() const { std::set used; - for( NameValuePlugIterator it( m_plugsParent.get() ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( m_plugsParent.get() ); !it.done(); ++it ) { // TODO - this method for checking if a plug variesWithContext should probably live in PlugAlgo // ( it's based on Switch::variesWithContext ) @@ -280,5 +280,3 @@ struct Registration Registration g_registration; } // namespace - - diff --git a/src/GafferOSLUIModule/GafferOSLUIModule.cpp b/src/GafferOSLUIModule/GafferOSLUIModule.cpp index b0852bff181..395c68cd7ed 100644 --- a/src/GafferOSLUIModule/GafferOSLUIModule.cpp +++ b/src/GafferOSLUIModule/GafferOSLUIModule.cpp @@ -39,4 +39,3 @@ BOOST_PYTHON_MODULE( _GafferOSLUI ) { } - diff --git a/src/GafferScene/AttributeProcessor.cpp b/src/GafferScene/AttributeProcessor.cpp index 99baf345b41..d80d9e986ed 100644 --- a/src/GafferScene/AttributeProcessor.cpp +++ b/src/GafferScene/AttributeProcessor.cpp @@ -121,4 +121,3 @@ IECore::ConstCompoundObjectPtr AttributeProcessor::computeAttributes( const Scen return inPlug()->attributesPlug()->getValue(); } } - diff --git a/src/GafferScene/Attributes.cpp b/src/GafferScene/Attributes.cpp index ce2ff282c42..e130936c0e2 100644 --- a/src/GafferScene/Attributes.cpp +++ b/src/GafferScene/Attributes.cpp @@ -151,7 +151,7 @@ IECore::ConstCompoundObjectPtr Attributes::computeGlobals( const Gaffer::Context result->members() = inputGlobals->members(); std::string name; - for( NameValuePlugIterator it( p ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( p ); !it.done(); ++it ) { IECore::DataPtr d = p->memberDataAndName( it->get(), name ); if( d ) diff --git a/src/GafferScene/BoundQuery.cpp b/src/GafferScene/BoundQuery.cpp new file mode 100644 index 00000000000..09f965d8db1 --- /dev/null +++ b/src/GafferScene/BoundQuery.cpp @@ -0,0 +1,389 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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/BoundQuery.h" + +#include "OpenEXR/ImathBoxAlgo.h" + +#include +#include + +namespace +{ + +float ensurePositiveZero( float const value ) +{ + return ( value == 0.0f ) ? std::fabs( value ) : value; +} + +void +setV3fPlugComponentValue( Gaffer::V3fPlug const& parent, Gaffer::NumericPlug< float >& child, Imath::V3f const& value ) +{ + float cv; + + if( & child == parent.getChild( 0 ) ) + { + cv = value.x; + } + else if( & child == parent.getChild( 1 ) ) + { + cv = value.y; + } + else if( & child == parent.getChild( 2 ) ) + { + cv = value.z; + } + else + { + assert( 0 ); // NOTE : Unknown child plug + cv = 0.f; + } + + child.setValue( ensurePositiveZero( cv ) ); +} + +Imath::Box3f const g_singularBox( Imath::V3f( 0.f, 0.f, 0.f ), Imath::V3f( 0.f, 0.f, 0.f ) ); + +} // namespace + +namespace GafferScene +{ + +size_t BoundQuery::g_firstPlugIndex = 0; + +GAFFER_NODE_DEFINE_TYPE( BoundQuery ); + +BoundQuery::BoundQuery( std::string const& name ) +: Gaffer::ComputeNode( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new ScenePlug( "scene" ) ); + addChild( new Gaffer::StringPlug( "location" ) ); + addChild( new Gaffer::IntPlug( "space", Gaffer::Plug::In, + static_cast< int >( Space::World ), + 0, static_cast< int >( Space::Relative ) ) ); + addChild( new Gaffer::StringPlug( "relativeLocation" ) ); + addChild( new Gaffer::Box3fPlug( "bound", Gaffer::Plug::Out ) ); + addChild( new Gaffer::AtomicBox3fPlug( "__internalBound", Gaffer::Plug::Out ) ); + addChild( new Gaffer::V3fPlug( "center", Gaffer::Plug::Out ) ); + addChild( new Gaffer::V3fPlug( "size", Gaffer::Plug::Out ) ); +} + +BoundQuery::~BoundQuery() +{} + +ScenePlug* BoundQuery::scenePlug() +{ + return const_cast< ScenePlug* >( + static_cast< BoundQuery const* >( this )->scenePlug() ); +} + +ScenePlug const* BoundQuery::scenePlug() const +{ + return getChild< ScenePlug >( g_firstPlugIndex ); +} + +Gaffer::StringPlug* BoundQuery::locationPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< BoundQuery const* >( this )->locationPlug() ); +} + +Gaffer::StringPlug const* BoundQuery::locationPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 1 ); +} + +Gaffer::IntPlug* BoundQuery::spacePlug() +{ + return const_cast< Gaffer::IntPlug* >( + static_cast< BoundQuery const* >( this )->spacePlug() ); +} + +Gaffer::IntPlug const* BoundQuery::spacePlug() const +{ + return getChild< Gaffer::IntPlug >( g_firstPlugIndex + 2 ); +} + +Gaffer::StringPlug* BoundQuery::relativeLocationPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< BoundQuery const* >( this )->relativeLocationPlug() ); +} + +Gaffer::StringPlug const* BoundQuery::relativeLocationPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 3 ); +} + +Gaffer::Box3fPlug* BoundQuery::boundPlug() +{ + return const_cast< Gaffer::Box3fPlug* >( + static_cast< BoundQuery const* >( this )->boundPlug() ); +} + +Gaffer::Box3fPlug const* BoundQuery::boundPlug() const +{ + return getChild< Gaffer::Box3fPlug >( g_firstPlugIndex + 4 ); +} + +Gaffer::AtomicBox3fPlug* BoundQuery::internalBoundPlug() +{ + return const_cast< Gaffer::AtomicBox3fPlug* >( + static_cast< BoundQuery const* >( this )->internalBoundPlug() ); +} + +Gaffer::AtomicBox3fPlug const* BoundQuery::internalBoundPlug() const +{ + return getChild< Gaffer::AtomicBox3fPlug >( g_firstPlugIndex + 5 ); +} + +Gaffer::V3fPlug* BoundQuery::centerPlug() +{ + return const_cast< Gaffer::V3fPlug* >( + static_cast< BoundQuery const* >( this )->centerPlug() ); +} + +Gaffer::V3fPlug const* BoundQuery::centerPlug() const +{ + return getChild< Gaffer::V3fPlug >( g_firstPlugIndex + 6 ); +} + +Gaffer::V3fPlug* BoundQuery::sizePlug() +{ + return const_cast< Gaffer::V3fPlug* >( + static_cast< BoundQuery const* >( this )->sizePlug() ); +} + +Gaffer::V3fPlug const* BoundQuery::sizePlug() const +{ + return getChild< Gaffer::V3fPlug >( g_firstPlugIndex + 7 ); +} + +void BoundQuery::affects( Gaffer::Plug const* const input, AffectedPlugsContainer& outputs ) const +{ + ComputeNode::affects( input, outputs ); + + if( input == internalBoundPlug() ) + { + outputs.push_back( boundPlug()->minPlug()->getChild( 0 ) ); + outputs.push_back( boundPlug()->minPlug()->getChild( 1 ) ); + outputs.push_back( boundPlug()->minPlug()->getChild( 2 ) ); + outputs.push_back( boundPlug()->maxPlug()->getChild( 0 ) ); + outputs.push_back( boundPlug()->maxPlug()->getChild( 1 ) ); + outputs.push_back( boundPlug()->maxPlug()->getChild( 2 ) ); + outputs.push_back( centerPlug()->getChild( 0 ) ); + outputs.push_back( centerPlug()->getChild( 1 ) ); + outputs.push_back( centerPlug()->getChild( 2 ) ); + outputs.push_back( sizePlug()->getChild( 0 ) ); + outputs.push_back( sizePlug()->getChild( 1 ) ); + outputs.push_back( sizePlug()->getChild( 2 ) ); + } + else if( + ( input == spacePlug() ) || + ( input == locationPlug() ) || + ( input == relativeLocationPlug() ) || + ( input == scenePlug()->boundPlug() ) || + ( input == scenePlug()->existsPlug() ) || + ( input == scenePlug()->transformPlug() ) ) + { + outputs.push_back( internalBoundPlug() ); + } +} + +void BoundQuery::hash( Gaffer::ValuePlug const* const output, Gaffer::Context const* const context, IECore::MurmurHash& h ) const +{ + ComputeNode::hash( output, context, h ); + + if( output == internalBoundPlug() ) + { + std::string const loc = locationPlug()->getValue(); + if( ! loc.empty() ) + { + ScenePlug const* const splug = scenePlug(); + assert( splug != 0 ); + + ScenePlug::ScenePath const path = ScenePlug::stringToPath( loc ); + + if( splug->exists( path ) ) + { + switch( static_cast< Space >( spacePlug()->getValue() ) ) + { + case Space::Local: + h = splug->boundHash( path ); + break; + case Space::World: + h.append( splug->fullTransformHash( path ) ); + h.append( splug->boundHash( path ) ); + break; + case Space::Relative: + { + std::string const rloc = relativeLocationPlug()->getValue(); + + if( ! rloc.empty() ) + { + if( loc == rloc ) + { + h = splug->boundHash( path ); + } + else + { + ScenePlug::ScenePath const rpath = ScenePlug::stringToPath( rloc ); + + if( splug->exists( rpath ) ) + { + h.append( splug->fullTransformHash( path ) ); + h.append( splug->fullTransformHash( rpath ) ); + h.append( splug->boundHash( path ) ); + } + } + } + break; + } + default: + break; + } + } + } + } + else + { + Gaffer::GraphComponent const* const parent = output->parent(); + + if( + ( parent == boundPlug()->minPlug() ) || + ( parent == boundPlug()->maxPlug() ) || + ( parent == centerPlug() ) || + ( parent == sizePlug() ) ) + { + internalBoundPlug()->hash( h ); + } + } +} + +void BoundQuery::compute( Gaffer::ValuePlug* const output, Gaffer::Context const* const context ) const +{ + if( output == internalBoundPlug() ) + { + Imath::Box3f b; + + std::string const loc = locationPlug()->getValue(); + if( ! loc.empty() ) + { + ScenePlug const* const splug = scenePlug(); + assert( splug != 0 ); + + ScenePlug::ScenePath const path = ScenePlug::stringToPath( loc ); + + if( splug->exists( path ) ) + { + switch( static_cast< Space >( spacePlug()->getValue() ) ) + { + case Space::Local: + b = splug->bound( path ); + break; + case Space::World: + b = Imath::transform( splug->bound( path ), splug->fullTransform( path ) ); + break; + case Space::Relative: + { + std::string const rloc = relativeLocationPlug()->getValue(); + + if( ! rloc.empty() ) + { + if( loc == rloc ) + { + b = splug->bound( path ); + } + else + { + ScenePlug::ScenePath const rpath = ScenePlug::stringToPath( rloc ); + + if( splug->exists( rpath ) ) + { + b = Imath::transform( splug->bound( path ), + splug->fullTransform( path ) * splug->fullTransform( rpath ).inverse() ); + } + } + } + break; + } + default: + break; + } + } + } + + IECore::assertedStaticCast< Gaffer::AtomicBox3fPlug >( output )->setValue( b.isEmpty() ? g_singularBox : b ); + } + else + { + Gaffer::GraphComponent* const parent = output->parent(); + + if( parent == boundPlug()->minPlug() ) + { + Imath::Box3f const b = internalBoundPlug()->getValue(); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), b.min ); + } + else if( parent == boundPlug()->maxPlug() ) + { + Imath::Box3f const b = internalBoundPlug()->getValue(); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), b.max ); + } + else if( parent == centerPlug() ) + { + Imath::Box3f const b = internalBoundPlug()->getValue(); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), b.center() ); + } + else if( parent == sizePlug() ) + { + Imath::Box3f const b = internalBoundPlug()->getValue(); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), b.size() ); + } + } + + ComputeNode::compute( output, context ); +} + +} // GafferScene diff --git a/src/GafferScene/BranchCreator.cpp b/src/GafferScene/BranchCreator.cpp index 643a8e42006..cc4f10239ff 100644 --- a/src/GafferScene/BranchCreator.cpp +++ b/src/GafferScene/BranchCreator.cpp @@ -46,6 +46,12 @@ #include "IECore/NullObject.h" +#include "tbb/spin_mutex.h" + +#include "boost/make_unique.hpp" + +#include + using namespace std; using namespace Imath; using namespace IECore; @@ -78,37 +84,306 @@ void mergeSetNames( const InternedStringVectorData *toAdd, vectorexistsPlug()->getValue() ) + { + return result; + } + result.pop_back(); + } + return result; +} + +// InternedString compares by pointer address by default, which will give differing +// results betweeen processes. Comparing by string value gives an alphabetical ordering +// we can rely on. +bool internedStringValueLess( const InternedString &a, const InternedString &b ) +{ + return a.string() < b.string(); +} + } // namespace ////////////////////////////////////////////////////////////////////////// -// BranchCreator +// BranchCreator::BranchesData ////////////////////////////////////////////////////////////////////////// +class BranchCreator::BranchesData : public IECore::Data +{ + + public : + + struct Location + { + using Ptr = std::unique_ptr; + using ChildMap = std::unordered_map; + using SourcePaths = vector; + + Location( size_t depth, bool exists ) : exists( exists ), depth( depth ) {} + + // True if this location exists in the input + // scene. + const bool exists; + // Depth of this location in the scene. + const size_t depth; + // Child locations. + ChildMap children; + // The source paths for this destination. + // Null if this is not a destination. + std::unique_ptr sourcePaths; + // Names of children which do not exist in + // the input scene. Null if all children + // exist in the input. + InternedStringVectorDataPtr newChildNames; + + }; + + BranchesData( const BranchCreator *branchCreator, const Context *context ) + : m_root( new Location( 0, true ) ) + { + auto f = [this, branchCreator]( const GafferScene::ScenePlug *scene, const GafferScene::ScenePlug::ScenePath &path ) + { + addBranch( branchCreator, path ); + return true; + }; + SceneAlgo::filteredParallelTraverse( branchCreator->inPlug(), branchCreator->filterPlug(), f ); + + if( !branchCreator->filterPlug()->getInput() ) + { + const auto parent = branchCreator->parentPlugPath(); + if( parent ) + { + ScenePlug::PathScope pathScope( context, &*parent ); + addBranch( branchCreator, *parent ); + } + } + + // When multiple sources map to the same destination, we have no guarantees about + // the order they will be visited above. Sort them alphabetically so that our output + // is stable from run to run. + visitLocationsWalk( + [] ( const ScenePlug::ScenePath &path, Location *location ) { + if( location->sourcePaths ) + { + std::sort( + location->sourcePaths->begin(), location->sourcePaths->end(), + [] ( const ScenePlug::ScenePath &a, const ScenePlug::ScenePath &b ) { + return lexicographical_compare( + a.begin(), a.end(), b.begin(), b.end(), + internedStringValueLess + ); + } + ); + } + if( location->newChildNames ) + { + std::sort( + location->newChildNames->writable().begin(), location->newChildNames->writable().end(), + internedStringValueLess + ); + } + }, + ScenePath(), + m_root.get() + ); + + } + + static bool affectedBy( const BranchCreator *branchCreator, const Plug *input ) + { + return + input == branchCreator->filterPlug() || + input == branchCreator->inPlug()->childNamesPlug() || + input == branchCreator->parentPlug() || + input == branchCreator->inPlug()->existsPlug() || + input == branchCreator->destinationPlug() + ; + } + + static void hash( const BranchCreator *branchCreator, const Context *context, IECore::MurmurHash &h ) + { + // See `SceneAlgo::matchingPathsHash()` for documentation of this hashing strategy. + std::atomic h1, h2; + auto f = [branchCreator, &h1, &h2]( const GafferScene::ScenePlug *scene, const GafferScene::ScenePlug::ScenePath &path ) + { + IECore::MurmurHash h; + hashBranch( branchCreator, path, h ); + h1 += h.h1(); + h2 += h.h2(); + return true; + }; + SceneAlgo::filteredParallelTraverse( branchCreator->inPlug(), branchCreator->filterPlug(), f ); + h.append( MurmurHash( h1, h2 ) ); + + if( !branchCreator->filterPlug()->getInput() ) + { + const auto parent = branchCreator->parentPlugPath(); + if( parent ) + { + ScenePlug::PathScope pathScope( context, &*parent ); + hashBranch( branchCreator, *parent, h ); + } + } + } + + bool empty() const + { + return m_root->children.empty() && !m_root->sourcePaths; + } + + const Location *locationOrAncestor( const ScenePlug::ScenePath &path ) const + { + const Location *result = m_root.get(); + for( const auto &name : path ) + { + const auto it = result->children.find( name ); + if( it != result->children.end() ) + { + result = it->second.get(); + } + else + { + break; + } + } + return result; + } + + const Location::SourcePaths &sourcePaths( const ScenePlug::ScenePath &destination ) const + { + const Location *location = locationOrAncestor( destination ); + assert( location->depth == destination.size() ); + if( !location->sourcePaths ) + { + throw IECore::Exception( boost::str( boost::format( "No source paths found for destination \"%1%\"" ) % ScenePlug::pathToString( destination ) ) ); + } + return *location->sourcePaths; + } + + template + void visitDestinations( F &&f ) const + { + visitLocationsWalk( + [ &f ] ( const ScenePlug::ScenePath &path, const Location *location ) { + if( location->sourcePaths ) + { + f( path, *location->sourcePaths ); + } + }, + ScenePath(), + m_root.get() + ); + } + + private : + + template + void visitLocationsWalk( F &&f, const ScenePlug::ScenePath &path, Location *location ) const + { + f( path, location ); + + ScenePlug::ScenePath childPath = path; childPath.push_back( InternedString() ); + for( const auto &child : location->children ) + { + childPath.back() = child.first; + visitLocationsWalk( f, childPath, child.second.get() ); + } + } + + static void hashBranch( const BranchCreator *branchCreator, const ScenePlug::ScenePath &path, IECore::MurmurHash &h ) + { + h.append( path.data(), path.size() ); + h.append( (uint64_t)path.size() ); + + ScenePlug::ScenePath destination = ScenePlug::stringToPath( branchCreator->destinationPlug()->getValue() ); + h.append( destination.data(), destination.size() ); + h.append( (uint64_t)destination.size() ); + + const ScenePlug::ScenePath existing = closestExistingPath( branchCreator->inPlug(), destination ); + h.append( existing.data(), existing.size() ); + h.append( (uint64_t)existing.size() ); + } + + void addBranch( const BranchCreator *branchCreator, const ScenePlug::ScenePath &path ) + { + ScenePlug::ScenePath destination = ScenePlug::stringToPath( branchCreator->destinationPlug()->getValue() ); + const ScenePlug::ScenePath existing = closestExistingPath( branchCreator->inPlug(), destination ); + + tbb::spin_mutex::scoped_lock lock( m_mutex ); + + Location *location = m_root.get(); + for( const auto &name : destination ) + { + if( !location->exists && location->sourcePaths ) + { + // We don't yet support merging branch children with new locations + // introduced by destinations that didn't previously exist. + throw IECore::Exception( boost::str( + boost::format( "Destination \"%1%\" contains a nested destination" ) + % ScenePlug::pathToString( ScenePath( destination.begin(), destination.begin() + location->depth ) ) + ) ); + } + + const auto inserted = location->children.insert( Location::ChildMap::value_type( name, Location::Ptr() ) ); + if( inserted.second ) + { + const bool exists = location->depth < existing.size(); + inserted.first->second = boost::make_unique( location->depth + 1, exists ); + if( !exists ) + { + if( !location->newChildNames ) + { + location->newChildNames = new InternedStringVectorData; + } + location->newChildNames->writable().push_back( name ); + } + } + location = inserted.first->second.get(); + } + + if( !location->sourcePaths ) + { + if( !location->exists && location->children.size() ) + { + throw IECore::Exception( boost::str( + boost::format( "Destination \"%1%\" contains a nested destination" ) + % ScenePlug::pathToString( destination ) + ) ); + } + location->sourcePaths.reset( new Location::SourcePaths ); + } + location->sourcePaths->push_back( path ); + } + + tbb::spin_mutex m_mutex; + Location::Ptr m_root; + +}; + +////////////////////////////////////////////////////////////////////////// +// BranchCreator +////////////////////////////////////////////////////////////////////////// GAFFER_NODE_DEFINE_TYPE( BranchCreator ); size_t BranchCreator::g_firstPlugIndex = 0; -static InternedString g_childNamesKey( "__BranchCreatorChildNames" ); -static InternedString g_forwardMappingKey( "__BranchCreatorForwardMappings" ); - BranchCreator::BranchCreator( const std::string &name ) : FilteredSceneProcessor( name, IECore::PathMatcher::NoMatch ) { storeIndexOfNextChild( g_firstPlugIndex ); addChild( new StringPlug( "parent" ) ); + addChild( new StringPlug( "destination", Gaffer::Plug::In, "${scene:path}" ) ); - addChild( new PathMatcherDataPlug( "__filteredPaths", Gaffer::Plug::In, new IECore::PathMatcherData(), Plug::Default & ~Plug::Serialisable ) ); - addChild( new PathMatcherDataPlug( "__parentPaths", Gaffer::Plug::Out, new IECore::PathMatcherData() ) ); - + addChild( new ObjectPlug( "__branches", Gaffer::Plug::Out, IECore::NullObject::defaultNullObject() ) ); addChild( new ObjectPlug( "__mapping", Gaffer::Plug::Out, IECore::NullObject::defaultNullObject() ) ); - FilterResultsPtr filterResults = new FilterResults( "__filterResults" ); - addChild( filterResults ); - filterResults->scenePlug()->setInput( inPlug() ); - filterResults->filterPlug()->setInput( filterPlug() ); - filteredPathsPlug()->setInput( filterResults->outPlug() ); - outPlug()->globalsPlug()->setInput( inPlug()->globalsPlug() ); outPlug()->childBoundsPlug()->setFlags( Plug::AcceptsDependencyCycles, true ); } @@ -127,24 +402,24 @@ const Gaffer::StringPlug *BranchCreator::parentPlug() const return getChild( g_firstPlugIndex ); } -Gaffer::PathMatcherDataPlug *BranchCreator::filteredPathsPlug() +Gaffer::StringPlug *BranchCreator::destinationPlug() { - return getChild( g_firstPlugIndex + 1 ); + return getChild( g_firstPlugIndex + 1 ); } -const Gaffer::PathMatcherDataPlug *BranchCreator::filteredPathsPlug() const +const Gaffer::StringPlug *BranchCreator::destinationPlug() const { - return getChild( g_firstPlugIndex + 1 ); + return getChild( g_firstPlugIndex + 1 ); } -Gaffer::PathMatcherDataPlug *BranchCreator::parentPathsPlug() +Gaffer::ObjectPlug *BranchCreator::branchesPlug() { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 2 ); } -const Gaffer::PathMatcherDataPlug *BranchCreator::parentPathsPlug() const +const Gaffer::ObjectPlug *BranchCreator::branchesPlug() const { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 2 ); } Gaffer::ObjectPlug *BranchCreator::mappingPlug() @@ -161,18 +436,22 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs { FilteredSceneProcessor::affects( input, outputs ); - if( input == parentPlug() || input == filteredPathsPlug() || input == inPlug()->existsPlug() ) + if( BranchesData::affectedBy( this, input ) ) { - outputs.push_back( parentPathsPlug() ); + outputs.push_back( branchesPlug() ); } - if( input == inPlug()->childNamesPlug() || affectsBranchChildNames( input ) ) + if( + input == inPlug()->childNamesPlug() || + input == branchesPlug() || + affectsBranchChildNames( input ) + ) { outputs.push_back( mappingPlug() ); } if( - input == parentPathsPlug() || + input == branchesPlug() || input == mappingPlug() || input == inPlug()->boundPlug() || input == outPlug()->childBoundsPlug() || @@ -183,7 +462,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - input == parentPathsPlug() || + input == branchesPlug() || input == mappingPlug() || input == inPlug()->transformPlug() || affectsBranchTransform( input ) @@ -193,7 +472,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - input == parentPathsPlug() || + input == branchesPlug() || input == mappingPlug() || input == inPlug()->attributesPlug() || affectsBranchAttributes( input ) @@ -203,7 +482,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - input == parentPathsPlug() || + input == branchesPlug() || input == mappingPlug() || input == inPlug()->objectPlug() || affectsBranchObject( input ) @@ -213,7 +492,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - input == parentPathsPlug() || + input == branchesPlug() || input == mappingPlug() || input == inPlug()->childNamesPlug() || affectsBranchChildNames( input ) @@ -223,7 +502,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - input == parentPathsPlug() || + input == branchesPlug() || input == inPlug()->setNamesPlug() || affectsBranchSetNames( input ) ) @@ -232,7 +511,7 @@ void BranchCreator::affects( const Plug *input, AffectedPlugsContainer &outputs } if( - affectsParentPathsForSet( input ) || + affectsBranchesForSet( input ) || input == mappingPlug() || input == inPlug()->setPlug() || affectsBranchSet( input ) @@ -264,20 +543,9 @@ void BranchCreator::hash( const Gaffer::ValuePlug *output, const Gaffer::Context { FilteredSceneProcessor::hash( output, context, h ); - if( output == parentPathsPlug() ) + if( output == branchesPlug() ) { - ScenePlug::GlobalScope globalScope( context ); - filteredPathsPlug()->hash( h ); - - if( !filterPlug()->getInput() ) - { - const auto parent = parentPlugPath(); - if( parent ) - { - h.append( parent->data(), parent->size() ); - h.append( (uint64_t)parent->size() ); - } - } + BranchesData::hash( this, context, h ); } else if( output == mappingPlug() ) { @@ -287,21 +555,9 @@ void BranchCreator::hash( const Gaffer::ValuePlug *output, const Gaffer::Context void BranchCreator::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const { - if( output == parentPathsPlug() ) + if( output == branchesPlug() ) { - ScenePlug::GlobalScope globalScope( context ); - PathMatcherDataPtr parentPaths = new PathMatcherData; - parentPaths->writable() = filteredPathsPlug()->getValue()->readable(); - - if( !filterPlug()->getInput() ) - { - const auto parent = parentPlugPath(); - if( parent ) - { - parentPaths->writable().addPath( *parent ); - } - } - static_cast( output )->setValue( parentPaths ); + static_cast( output )->setValue( new BranchesData( this, context ) ); } else if( output == mappingPlug() ) { @@ -315,103 +571,152 @@ void BranchCreator::compute( Gaffer::ValuePlug *output, const Gaffer::Context *c void BranchCreator::hashBound( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - hashBranchBound( parentPath, branchPath, context, h ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch || parentMatch == IECore::PathMatcher::DescendantMatch ) - { - FilteredSceneProcessor::hashBound( path, context, parent, h ); - inPlug()->boundPlug()->hash( h ); - outPlug()->childBoundsPlug()->hash( h ); - } - else + switch( locationType ) { - h = inPlug()->boundPlug()->hash(); + case Branch : + hashBranchBound( sourcePath, branchPath, context, h ); + break; + case Destination : + case Ancestor : + FilteredSceneProcessor::hashBound( path, context, parent, h ); + inPlug()->boundPlug()->hash( h ); + outPlug()->childBoundsPlug()->hash( h ); + break; + case NewDestination : + case NewAncestor : + h = outPlug()->childBoundsPlug()->hash(); + break; + case PassThrough : + default : + h = inPlug()->boundPlug()->hash(); + break; } } Imath::Box3f BranchCreator::computeBound( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - return computeBranchBound( parentPath, branchPath, context ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch || parentMatch == IECore::PathMatcher::DescendantMatch ) - { - Box3f result = inPlug()->boundPlug()->getValue(); - result.extendBy( outPlug()->childBoundsPlug()->getValue() ); - return result; - } - else + switch( locationType ) { - return inPlug()->boundPlug()->getValue(); + case Branch : + return computeBranchBound( sourcePath, branchPath, context ); + case Destination : + case Ancestor : { + Box3f result = inPlug()->boundPlug()->getValue(); + result.extendBy( outPlug()->childBoundsPlug()->getValue() ); + return result; + } + case NewDestination : + case NewAncestor : + return outPlug()->childBoundsPlug()->getValue(); + case PassThrough : + default : + return inPlug()->boundPlug()->getValue(); } } void BranchCreator::hashTransform( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - hashBranchTransform( parentPath, branchPath, context, h ); - } - else + switch( locationType ) { - h = inPlug()->transformPlug()->hash(); + case Branch : { + hashBranchTransform( sourcePath, branchPath, context, h ); + if( branchPath.size() == 1 ) + { + ScenePath destinationPath = path; destinationPath.pop_back(); + if( sourcePath != destinationPath ) + { + h.append( inPlug()->fullTransformHash( sourcePath ) ); + h.append( outPlug()->fullTransform( destinationPath ) ); + } + } + break; + } + case Destination : + case Ancestor : + case PassThrough : + h = inPlug()->transformPlug()->hash(); + break; + default : + h = inPlug()->transformPlug()->defaultHash(); } } Imath::M44f BranchCreator::computeTransform( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) + switch( locationType ) { - return computeBranchTransform( parentPath, branchPath, context ); - } - else - { - return inPlug()->transformPlug()->getValue(); + case Branch : { + M44f result = computeBranchTransform( sourcePath, branchPath, context ); + if( branchPath.size() == 1 ) + { + ScenePath destinationPath = path; destinationPath.pop_back(); + if( sourcePath != destinationPath ) + { + // Account for the difference between source and destination transforms so that + // branches are positioned as if they were parented below the source. + M44f relativeTransform = inPlug()->fullTransform( sourcePath ) * outPlug()->fullTransform( destinationPath ).inverse(); + result *= relativeTransform; + } + } + return result; + } + case Destination : + case Ancestor : + case PassThrough : + return inPlug()->transformPlug()->getValue(); + default : + return inPlug()->transformPlug()->defaultValue(); } } void BranchCreator::hashAttributes( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - hashBranchAttributes( parentPath, branchPath, context, h ); - } - else + switch( locationType ) { - h = inPlug()->attributesPlug()->hash(); + case Branch : + hashBranchAttributes( sourcePath, branchPath, context, h ); + break; + case Destination : + case Ancestor : + case PassThrough : + h = inPlug()->attributesPlug()->hash(); + break; + default : + h = inPlug()->attributesPlug()->defaultHash(); + break; } } IECore::ConstCompoundObjectPtr BranchCreator::computeAttributes( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) + switch( locationType ) { - return computeBranchAttributes( parentPath, branchPath, context ); - } - else - { - return inPlug()->attributesPlug()->getValue(); + case Branch : + return computeBranchAttributes( sourcePath, branchPath, context ); + case Destination : + case Ancestor : + case PassThrough : + return inPlug()->attributesPlug()->getValue(); + default : + return inPlug()->attributesPlug()->defaultValue(); } } @@ -422,90 +727,142 @@ bool BranchCreator::processesRootObject() const void BranchCreator::hashObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - hashBranchObject( parentPath, branchPath, context, h ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch && processesRootObject() ) + switch( locationType ) { - // note branchPath is empty here - hashBranchObject( parentPath, branchPath, context, h ); - } - else - { - h = inPlug()->objectPlug()->hash(); + case Branch : + hashBranchObject( sourcePath, branchPath, context, h ); + break; + case Destination : + if( processesRootObject() ) + { + // See notes in `hashObject()`. + if( !destinationPlug()->isSetToDefault() ) + { + throw IECore::Exception( "Can only process root object when `destination` is default." ); + } + hashBranchObject( path, branchPath, context, h ); + break; + } + // Fall through + case Ancestor : + case PassThrough : + h = inPlug()->objectPlug()->hash(); + break; + default : + h = inPlug()->objectPlug()->defaultHash(); } } IECore::ConstObjectPtr BranchCreator::computeObject( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - return computeBranchObject( parentPath, branchPath, context ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch && processesRootObject() ) + switch( locationType ) { - // note branchPath is empty here - return computeBranchObject( parentPath, branchPath, context ); - } - else - { - return inPlug()->objectPlug()->getValue(); + case Branch : + return computeBranchObject( sourcePath, branchPath, context ); + case Destination : + if( processesRootObject() ) + { + /// \todo The `processesRootObject()` mechanism was intended to allow derived + /// classes to modify the object at `sourcePath`, but it is not compatible + /// with the new `destination` plug. We could continue to support it by doing + /// another filter evaluation, but the mechanism of processing the "root" + /// object feels bogus now that source and destination are decoupled. Come up + /// with something more logical. + if( !destinationPlug()->isSetToDefault() ) + { + throw IECore::Exception( "Can only process root object when `destination` is default." ); + } + return computeBranchObject( path, branchPath, context ); + } + // Fall through + case Ancestor : + case PassThrough : + return inPlug()->objectPlug()->getValue(); + default : + return inPlug()->objectPlug()->defaultValue(); } } void BranchCreator::hashChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + ConstInternedStringVectorDataPtr newChildNames; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath, &newChildNames ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - hashBranchChildNames( parentPath, branchPath, context, h ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch ) + switch( locationType ) { - Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); - h = mapping->outputChildNames()->Object::hash(); - } - else - { - h = inPlug()->childNamesPlug()->hash(); + case Branch : + hashBranchChildNames( sourcePath, branchPath, context, h ); + break; + case Destination : + case NewDestination : { + Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); + h = mapping->outputChildNames()->Object::hash(); + break; + } + case NewAncestor : + h = newChildNames->Object::hash(); + break; + case Ancestor : + if( newChildNames ) + { + FilteredSceneProcessor::hashChildNames( path, context, parent, h ); + inPlug()->childNamesPlug()->hash( h ); + newChildNames->hash( h ); + return; + } + // Fall through + case PassThrough : + default : + h = inPlug()->childNamesPlug()->hash(); } } IECore::ConstInternedStringVectorDataPtr BranchCreator::computeChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent ) const { - ScenePath parentPath, branchPath; - const IECore::PathMatcher::Result parentMatch = parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + ConstInternedStringVectorDataPtr newChildNames; + const LocationType locationType = sourceAndBranchPaths( path, sourcePath, branchPath, &newChildNames ); - if( parentMatch == IECore::PathMatcher::AncestorMatch ) - { - return computeBranchChildNames( parentPath, branchPath, context ); - } - else if( parentMatch == IECore::PathMatcher::ExactMatch ) - { - Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); - return mapping->outputChildNames(); - } - else + switch( locationType ) { - return inPlug()->childNamesPlug()->getValue(); + case Branch : + return computeBranchChildNames( sourcePath, branchPath, context ); + case Destination : + case NewDestination : { + Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); + return mapping->outputChildNames(); + } + case NewAncestor : + return newChildNames; + case Ancestor : + if( newChildNames ) + { + InternedStringVectorDataPtr combinedNames = inPlug()->childNamesPlug()->getValue()->copy(); + combinedNames->writable().insert( + combinedNames->writable().end(), + newChildNames->readable().begin(), + newChildNames->readable().end() + ); + return combinedNames; + } + // Fall through + case PassThrough : + default : + return inPlug()->childNamesPlug()->getValue(); } } void BranchCreator::hashSetNames( const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - ConstPathMatcherDataPtr parentPathsData = parentPathsPlug()->getValue(); - const PathMatcher &parentPaths = parentPathsData->readable(); - - if( parentPaths.isEmpty() ) + ConstBranchesDataPtr branchesData = branches( context ); + if( branchesData->empty() ) { h = inPlug()->setNamesPlug()->hash(); return; @@ -522,23 +879,25 @@ void BranchCreator::hashSetNames( const Gaffer::Context *context, const ScenePlu } else { - for( PathMatcher::Iterator it = parentPaths.begin(), eIt = parentPaths.end(); it != eIt; ++it ) - { - MurmurHash branchSetNamesHash; - hashBranchSetNames( *it, context, branchSetNamesHash ); - h.append( branchSetNamesHash ); - } + branchesData->visitDestinations( + [&context, &h, this] ( const ScenePath &destination, const BranchesData::Location::SourcePaths &sourcePaths ) { + for( const auto &sourcePath : sourcePaths ) + { + MurmurHash branchSetNamesHash; + hashBranchSetNames( sourcePath, context, branchSetNamesHash ); + h.append( branchSetNamesHash ); + } + } + ); } } IECore::ConstInternedStringVectorDataPtr BranchCreator::computeSetNames( const Gaffer::Context *context, const ScenePlug *parent ) const { - ConstPathMatcherDataPtr parentPathsData = parentPathsPlug()->getValue(); - const PathMatcher &parentPaths = parentPathsData->readable(); + ConstBranchesDataPtr branchesData = branches( context ); ConstInternedStringVectorDataPtr inputSetNamesData = inPlug()->setNamesPlug()->getValue(); - - if( parentPaths.isEmpty() ) + if( branchesData->empty() ) { return inputSetNamesData; } @@ -553,11 +912,15 @@ IECore::ConstInternedStringVectorDataPtr BranchCreator::computeSetNames( const G } else { - for( PathMatcher::Iterator it = parentPaths.begin(), eIt = parentPaths.end(); it != eIt; ++it ) - { - ConstInternedStringVectorDataPtr branchSetNamesData = computeBranchSetNames( *it, context ); - mergeSetNames( branchSetNamesData.get(), result ); - } + branchesData->visitDestinations( + [&context, &result, this] ( const ScenePath &destination, const BranchesData::Location::SourcePaths &sourcePaths ) { + for( const auto &sourcePath : sourcePaths ) + { + ConstInternedStringVectorDataPtr branchSetNamesData = computeBranchSetNames( sourcePath, context ); + mergeSetNames( branchSetNamesData.get(), result ); + } + } + ); } return resultData; @@ -565,8 +928,8 @@ IECore::ConstInternedStringVectorDataPtr BranchCreator::computeSetNames( const G void BranchCreator::hashSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - const PathMatcher parentPaths = parentPathsForSet( setName, context ); - if( parentPaths.isEmpty() ) + ConstBranchesDataPtr branches = branchesForSet( setName, context ); + if( !branches ) { h = inPlug()->setPlug()->hash(); return; @@ -576,27 +939,27 @@ void BranchCreator::hashSet( const IECore::InternedString &setName, const Gaffer inPlug()->setPlug()->hash( h ); /// \todo Parallelise. - for( PathMatcher::Iterator it = parentPaths.begin(), eIt = parentPaths.end(); it != eIt; ++it ) - { - const ScenePlug::ScenePath &parentPath = *it; - { - ScenePlug::PathScope pathScope( context, parentPath ); - pathScope.setPath( parentPath ); + branches->visitDestinations( + [&setName, &context, &h, this] ( const ScenePath &destination, const BranchesData::Location::SourcePaths &sourcePaths ) { + for( const auto &sourcePath : sourcePaths ) + { + MurmurHash branchSetHash; + hashBranchSet( sourcePath, setName, context, branchSetHash ); + h.append( branchSetHash ); + } + ScenePlug::PathScope pathScope( context, &destination ); mappingPlug()->hash( h ); + h.append( destination.data(), destination.size() ); } - MurmurHash branchSetHash; - hashBranchSet( parentPath, setName, context, branchSetHash ); - h.append( branchSetHash ); - h.append( parentPath.data(), parentPath.size() ); - } + ); } IECore::ConstPathMatcherDataPtr BranchCreator::computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const { ConstPathMatcherDataPtr inputSetData = inPlug()->setPlug()->getValue(); - const PathMatcher parentPaths = parentPathsForSet( setName, context ); - if( parentPaths.isEmpty() ) + ConstBranchesDataPtr branches = branchesForSet( setName, context ); + if( !branches ) { return inputSetData; } @@ -605,27 +968,18 @@ IECore::ConstPathMatcherDataPtr BranchCreator::computeSet( const IECore::Interne PathMatcher &outputSet = outputSetData->writable(); /// \todo Parallelise. - vector outputPrefix; - for( PathMatcher::Iterator it = parentPaths.begin(), eIt = parentPaths.end(); it != eIt; ++it ) - { - const ScenePlug::ScenePath &parentPath = *it; - ConstPathMatcherDataPtr branchSetData = computeBranchSet( parentPath, setName, context ); - if( !branchSetData ) - { - continue; - } - - Private::ConstChildNamesMapPtr mapping; - { - ScenePlug::PathScope pathScope( context, parentPath ); - mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); + branches->visitDestinations( + [&setName, &context, &outputSet, this] ( const ScenePath &destination, const BranchesData::Location::SourcePaths &sourcePaths ) { + vector branchSets = { nullptr }; + for( const auto &sourcePath : sourcePaths ) + { + branchSets.push_back( computeBranchSet( sourcePath, setName, context ) ); + } + ScenePlug::PathScope pathScope( context, &destination ); + Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); + outputSet.addPaths( mapping->set( branchSets ), destination ); } - - outputSet.addPaths( - mapping->set( { nullptr, branchSetData } ), - parentPath - ); - } + ); return outputSetData; } @@ -640,10 +994,23 @@ Gaffer::ValuePlug::CachePolicy BranchCreator::hashCachePolicy( const Gaffer::Val // shared between all threads and is almost guaranteed not to be evicted. return ValuePlug::CachePolicy::TaskIsolation; } + else if( output == branchesPlug() ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } return FilteredSceneProcessor::hashCachePolicy( output ); } -IECore::PathMatcher BranchCreator::parentPathsForSet( const IECore::InternedString &setName, const Gaffer::Context *context ) const +Gaffer::ValuePlug::CachePolicy BranchCreator::computeCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( output == branchesPlug() ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } + return FilteredSceneProcessor::computeCachePolicy( output ); +} + +BranchCreator::ConstBranchesDataPtr BranchCreator::branchesForSet( const IECore::InternedString &setName, const Gaffer::Context *context ) const { if( constantBranchSetNames() ) { @@ -657,23 +1024,24 @@ IECore::PathMatcher BranchCreator::parentPathsForSet( const IECore::InternedStri } if( !branchSetNamesData ) { - return IECore::PathMatcher(); + return nullptr; } const auto &branchSetNames = branchSetNamesData->readable(); if( find( branchSetNames.begin(), branchSetNames.end(), setName ) == branchSetNames.end() ) { - return IECore::PathMatcher(); + return nullptr; } } - return parentPathsPlug()->getValue()->readable(); + ConstBranchesDataPtr b = branches( context ); + return b->empty() ? nullptr : b; } -bool BranchCreator::affectsParentPathsForSet( const Gaffer::Plug *input ) const +bool BranchCreator::affectsBranchesForSet( const Gaffer::Plug *input ) const { return ( constantBranchSetNames() && affectsBranchSetNames( input ) ) || - input == parentPathsPlug() + input == branchesPlug() ; } @@ -712,36 +1080,36 @@ bool BranchCreator::affectsBranchSet( const Gaffer::Plug *input ) const return false; } -void BranchCreator::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { FilteredSceneProcessor::hashBound( context->get( ScenePlug::scenePathContextName ), context, inPlug(), h ); } -void BranchCreator::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { FilteredSceneProcessor::hashTransform( context->get( ScenePlug::scenePathContextName ), context, inPlug(), h ); } -void BranchCreator::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { FilteredSceneProcessor::hashAttributes( context->get( ScenePlug::scenePathContextName ), context, inPlug(), h ); } -void BranchCreator::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { FilteredSceneProcessor::hashObject( context->get( ScenePlug::scenePathContextName ), context, inPlug(), h ); } -void BranchCreator::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { FilteredSceneProcessor::hashChildNames( context->get( ScenePlug::scenePathContextName ), context, inPlug(), h ); } -void BranchCreator::hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { } -IECore::ConstInternedStringVectorDataPtr BranchCreator::computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr BranchCreator::computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { // It's OK to return nullptr, because the value returned from this method // isn't used as the result of a compute(), and won't be stored on a plug. @@ -754,11 +1122,11 @@ bool BranchCreator::constantBranchSetNames() const return true; } -void BranchCreator::hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void BranchCreator::hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const { } -IECore::ConstPathMatcherDataPtr BranchCreator::computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const +IECore::ConstPathMatcherDataPtr BranchCreator::computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const { // See comments in computeBranchSetNames. return nullptr; @@ -766,63 +1134,117 @@ IECore::ConstPathMatcherDataPtr BranchCreator::computeBranchSet( const ScenePath void BranchCreator::hashMapping( const Gaffer::Context *context, IECore::MurmurHash &h ) const { - const ScenePlug::ScenePath parent = context->get( ScenePlug::scenePathContextName ); - MurmurHash branchChildNamesHash; - hashBranchChildNames( parent, ScenePath(), context, branchChildNamesHash ); - h.append( branchChildNamesHash ); - inPlug()->childNamesPlug()->hash( h ); + if( inPlug()->existsPlug()->getValue() ) + { + inPlug()->childNamesPlug()->hash( h ); + } + + ConstBranchesDataPtr branchesData = branches( Context::current() ); + const ScenePlug::ScenePath &destinationPath = context->get( ScenePlug::scenePathContextName ); + for( const auto &sourcePath : branchesData->sourcePaths( destinationPath ) ) + { + MurmurHash branchChildNamesHash; + hashBranchChildNames( sourcePath, ScenePath(), context, branchChildNamesHash ); + h.append( branchChildNamesHash ); + } } IECore::ConstDataPtr BranchCreator::computeMapping( const Gaffer::Context *context ) const { - const ScenePlug::ScenePath parent = context->get( ScenePlug::scenePathContextName ); - return new Private::ChildNamesMap( { - inPlug()->childNamesPlug()->getValue(), - computeBranchChildNames( parent, ScenePath(), context ) - } ); + vector childNames; + if( inPlug()->existsPlug()->getValue() ) + { + childNames.push_back( inPlug()->childNamesPlug()->getValue() ); + } + else + { + childNames.push_back( new InternedStringVectorData ); + } + + ConstBranchesDataPtr branchesData = branches( Context::current() ); + const ScenePlug::ScenePath &destinationPath = context->get( ScenePlug::scenePathContextName ); + for( const auto &sourcePath : branchesData->sourcePaths( destinationPath ) ) + { + childNames.push_back( computeBranchChildNames( sourcePath, ScenePath(), context ) ); + } + + return new Private::ChildNamesMap( childNames ); +} + +BranchCreator::ConstBranchesDataPtr BranchCreator::branches( const Gaffer::Context *context ) const +{ + ScenePlug::GlobalScope globalScope( context ); + globalScope.remove( SceneAlgo::historyIDContextName() ); + return static_pointer_cast( branchesPlug()->getValue() ); } -IECore::PathMatcher::Result BranchCreator::parentAndBranchPaths( const ScenePath &path, ScenePath &parentPath, ScenePath &branchPath ) const +BranchCreator::LocationType BranchCreator::sourceAndBranchPaths( const ScenePath &path, ScenePath &sourcePath, ScenePath &branchPath, IECore::ConstInternedStringVectorDataPtr *newChildNames ) const { - ConstPathMatcherDataPtr parentPathsData = parentPathsPlug()->getValue(); - const PathMatcher &parentPaths = parentPathsData->readable(); + ConstBranchesDataPtr branchesData = branches( Context::current() ); + const BranchesData::Location *location = branchesData->locationOrAncestor( path ); - const unsigned match = parentPaths.match( path ); - if( match & PathMatcher::ExactMatch ) + if( newChildNames && location->depth == path.size() ) { - parentPath = path; - return PathMatcher::ExactMatch; + *newChildNames = location->newChildNames; } - else if( match & PathMatcher::AncestorMatch ) - { - parentPath = path; - do { - parentPath.pop_back(); - } while( !(parentPaths.match( parentPath ) & PathMatcher::ExactMatch) ); - Private::ConstChildNamesMapPtr mapping; + if( location->sourcePaths ) + { + if( location->depth < path.size() ) { - ScenePlug::PathScope pathScope( Context::current(), parentPath ); - mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); - } + Private::ConstChildNamesMapPtr mapping; + { + const ScenePath destinationPath( path.begin(), path.begin() + location->depth ); + ScenePlug::PathScope pathScope( Context::current(), &destinationPath ); + mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); + } - const Private::ChildNamesMap::Input input = mapping->input( path[parentPath.size()] ); - if( input.index == 1 ) - { - branchPath.push_back( input.name ); - branchPath.insert( branchPath.end(), path.begin() + parentPath.size() + 1, path.end() ); - return PathMatcher::AncestorMatch; + const Private::ChildNamesMap::Input input = mapping->input( path[location->depth] ); + if( input.index >= 1 ) + { + branchPath.assign( path.begin() + location->depth, path.end() ); + branchPath[0] = input.name; + sourcePath = (*location->sourcePaths)[input.index-1]; + return Branch; + } + else + { + return PassThrough; + } } else { - // Descendant comes from the primary input, rather than being part of the generated branch. - return PathMatcher::NoMatch; + return location->exists ? Destination : NewDestination; } } - else if( match & PathMatcher::DescendantMatch ) + + if( location->children.empty() ) + { + return PassThrough; + } + else + { + return location->exists ? Ancestor : NewAncestor; + } +} + +IECore::PathMatcher::Result BranchCreator::parentAndBranchPaths( const ScenePath &path, ScenePath &parentPath, ScenePath &branchPath ) const +{ + const LocationType locationType = sourceAndBranchPaths( path, parentPath, branchPath ); + switch( locationType ) { - return PathMatcher::DescendantMatch; + case Branch : + return IECore::PathMatcher::AncestorMatch; + case Destination : + case NewDestination : + return IECore::PathMatcher::ExactMatch; + case Ancestor : + case NewAncestor : + return IECore::PathMatcher::DescendantMatch; + case PassThrough : + return IECore::PathMatcher::NoMatch; } - return PathMatcher::NoMatch; + assert( false ); // Should not get here + return IECore::PathMatcher::NoMatch; } diff --git a/src/GafferScene/CameraTweaks.cpp b/src/GafferScene/CameraTweaks.cpp index 4c413b7bd1c..411379ca8b9 100644 --- a/src/GafferScene/CameraTweaks.cpp +++ b/src/GafferScene/CameraTweaks.cpp @@ -108,7 +108,7 @@ IECore::ConstObjectPtr CameraTweaks::computeProcessedObject( const ScenePath &pa IECoreScene::CameraPtr result = inputCamera->copy(); - for( TweakPlugIterator tIt( tweaksPlug ); !tIt.done(); ++tIt ) + for( TweakPlug::Iterator tIt( tweaksPlug ); !tIt.done(); ++tIt ) { if( !(*tIt)->enabledPlug()->getValue() ) { diff --git a/src/GafferScene/Capsule.cpp b/src/GafferScene/Capsule.cpp index 5c914f64e4d..e36352a93de 100644 --- a/src/GafferScene/Capsule.cpp +++ b/src/GafferScene/Capsule.cpp @@ -36,7 +36,7 @@ #include "GafferScene/Capsule.h" -#include "GafferScene/RendererAlgo.h" +#include "GafferScene/Private/RendererAlgo.h" #include "GafferScene/ScenePlug.h" #include "Gaffer/Node.h" @@ -195,8 +195,8 @@ void Capsule::render( IECoreScenePreview::Renderer *renderer ) const throwIfNoScene(); ScenePlug::GlobalScope scope( m_context.get() ); IECore::ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue(); - RendererAlgo::RenderSets renderSets( m_scene ); - RendererAlgo::outputObjects( m_scene, globals.get(), renderSets, /* lightLinks = */ nullptr, renderer, m_root ); + GafferScene::Private::RendererAlgo::RenderSets renderSets( m_scene ); + GafferScene::Private::RendererAlgo::outputObjects( m_scene, globals.get(), renderSets, /* lightLinks = */ nullptr, renderer, m_root ); } const ScenePlug *Capsule::scene() const @@ -230,4 +230,3 @@ void Capsule::throwIfNoScene() const throw IECore::Exception( "Source scene plug no longer valid." ); } } - diff --git a/src/GafferScene/ChildNamesMap.cpp b/src/GafferScene/ChildNamesMap.cpp index 31c389e143a..0b5ca96aa6b 100644 --- a/src/GafferScene/ChildNamesMap.cpp +++ b/src/GafferScene/ChildNamesMap.cpp @@ -174,7 +174,7 @@ IECore::PathMatcher ChildNamesMap::set( const std::vectorgetValue() ); for( const std::string & suffix : suffixesData->readable() ) { - scope.set( suffixContextVariableName, suffix ); + scope.set( suffixContextVariableName, &suffix ); IECore::MurmurHash curHash = inPlug()->objectPlug()->hash(); if( curHash != inputHash ) { @@ -180,7 +180,7 @@ IECore::ConstObjectPtr CollectPrimitiveVariables::computeProcessedObject( const InternedString suffixContextVariableName( suffixContextVariablePlug()->getValue() ); for( unsigned int i = 0; i < suffixes.size(); i++ ) { - scope.set( suffixContextVariableName, suffixes[i] ); + scope.set( suffixContextVariableName, &suffixes[i] ); IECore::MurmurHash collectObjectHash = inPlug()->objectPlug()->hash(); if( collectObjectHash == inputHash ) { @@ -233,5 +233,3 @@ IECore::ConstObjectPtr CollectPrimitiveVariables::computeProcessedObject( const return result; } - - diff --git a/src/GafferScene/CollectScenes.cpp b/src/GafferScene/CollectScenes.cpp index 062eeea9dfe..36664454703 100644 --- a/src/GafferScene/CollectScenes.cpp +++ b/src/GafferScene/CollectScenes.cpp @@ -185,7 +185,7 @@ class CollectScenes::SourceScope : public Context::EditableScope { } - void setRoot( const std::string &root ) + void setRoot( const std::string *root ) { set( m_rootVariable, root ); } @@ -210,18 +210,17 @@ class CollectScenes::SourcePathScope : public SourceScope m_rootTreeLocation = m_rootTree->locationOrAncestor( downstreamPath ); if( m_rootTreeLocation->isRoot() ) { - setRoot( m_rootTreeLocation->rootVariableValue ); - ScenePlug::ScenePath upstreamPath; + setRoot( &m_rootTreeLocation->rootVariableValue ); // We evaluate the sourceRootPlug _after_ setting the root name, // so that users can use the root name in expressions and // substitutions. - ScenePlug::stringToPath( collectScenes->sourceRootPlug()->getValue(), upstreamPath ); - upstreamPath.insert( upstreamPath.end(), downstreamPath.begin() + m_rootTreeLocation->depth, downstreamPath.end() ); - set( ScenePlug::scenePathContextName, upstreamPath ); + ScenePlug::stringToPath( collectScenes->sourceRootPlug()->getValue(), m_upstreamPath ); + m_upstreamPath.insert( m_upstreamPath.end(), downstreamPath.begin() + m_rootTreeLocation->depth, downstreamPath.end() ); + set( ScenePlug::scenePathContextName, &m_upstreamPath ); } else { - set( ScenePlug::scenePathContextName, downstreamPath ); + set( ScenePlug::scenePathContextName, &downstreamPath ); } } @@ -241,6 +240,7 @@ class CollectScenes::SourcePathScope : public SourceScope private : + ScenePlug::ScenePath m_upstreamPath; ConstRootTreePtr m_rootTree; const RootTree::Location *m_rootTreeLocation; @@ -579,13 +579,13 @@ void CollectScenes::hashGlobals( const Gaffer::Context *context, const ScenePlug SceneProcessor::hashGlobals( context, parent, h ); for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); inPlug()->globalsPlug()->hash( h ); } } else { - sourceScope.setRoot( rootTree->roots()[0] ); + sourceScope.setRoot( &rootTree->roots()[0] ); h = inPlug()->globalsPlug()->hash(); } } @@ -605,7 +605,7 @@ IECore::ConstCompoundObjectPtr CollectScenes::computeGlobals( const Gaffer::Cont CompoundObjectPtr result = new CompoundObject; for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); ConstCompoundObjectPtr globals = inPlug()->globalsPlug()->getValue(); for( const auto &m : globals->members() ) { @@ -616,7 +616,7 @@ IECore::ConstCompoundObjectPtr CollectScenes::computeGlobals( const Gaffer::Cont } else { - sourceScope.setRoot( rootTree->roots()[0] ); + sourceScope.setRoot( &rootTree->roots()[0] ); return inPlug()->globalsPlug()->getValue(); } } @@ -631,7 +631,7 @@ void CollectScenes::hashSetNames( const Gaffer::Context *context, const ScenePlu SourceScope sourceScope( context, rootNameVariablePlug()->getValue() ); for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); inSetNamesPlug->hash( h ); } } @@ -648,7 +648,7 @@ IECore::ConstInternedStringVectorDataPtr CollectScenes::computeSetNames( const G SourceScope sourceScope( context, rootNameVariablePlug()->getValue() ); for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); ConstInternedStringVectorDataPtr inSetNamesData = inSetNamesPlug->getValue(); for( const auto &setName : inSetNamesData->readable() ) { @@ -678,7 +678,7 @@ void CollectScenes::hashSet( const IECore::InternedString &setName, const Gaffer SourceScope sourceScope( context, rootNameVariablePlug()->getValue() ); for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); inSetPlug->hash( h ); sourceRootPlug->hash( h ); h.append( root ); @@ -703,7 +703,7 @@ IECore::ConstPathMatcherDataPtr CollectScenes::computeSet( const IECore::Interne ScenePlug::ScenePath prefix; for( const auto &root : rootTree->roots() ) { - sourceScope.setRoot( root ); + sourceScope.setRoot( &root ); ConstPathMatcherDataPtr inSetData = inSetPlug->getValue(); const PathMatcher &inSet = inSetData->readable(); if( !inSet.isEmpty() ) @@ -723,4 +723,3 @@ IECore::ConstPathMatcherDataPtr CollectScenes::computeSet( const IECore::Interne return setData; } - diff --git a/src/GafferScene/CollectTransforms.cpp b/src/GafferScene/CollectTransforms.cpp index 8bccc4a4e93..8f62983c197 100644 --- a/src/GafferScene/CollectTransforms.cpp +++ b/src/GafferScene/CollectTransforms.cpp @@ -183,7 +183,7 @@ void CollectTransforms::hash( const Gaffer::ValuePlug *output, const Gaffer::Con InternedString attributeContextVariableName( attributeContextVariablePlug()->getValue() ); for( const std::string &name : names ) { - scope.set( attributeContextVariableName, name ); + scope.set( attributeContextVariableName, &name ); IECore::MurmurHash collectedHash; if( worldMatrix ) { @@ -254,7 +254,7 @@ void CollectTransforms::compute( Gaffer::ValuePlug *output, const Gaffer::Contex InternedString attributeContextVariableName( attributeContextVariablePlug()->getValue() ); for( const std::string &name : names ) { - scope.set( attributeContextVariableName, name ); + scope.set( attributeContextVariableName, &name ); if( worldMatrix ) { IECore::MurmurHash collectedTransformHash = inPlug()->fullTransformHash( scenePath ); @@ -348,5 +348,3 @@ IECore::ConstCompoundObjectPtr CollectTransforms::computeProcessedAttributes( co return result; } - - diff --git a/src/GafferScene/Constraint.cpp b/src/GafferScene/Constraint.cpp index 0ad7f7a0a45..8bf03fb9ffe 100644 --- a/src/GafferScene/Constraint.cpp +++ b/src/GafferScene/Constraint.cpp @@ -253,4 +253,3 @@ boost::optional Constraint::target() const return Target( { targetPath, targetScene } ); } - diff --git a/src/GafferScene/CoordinateSystem.cpp b/src/GafferScene/CoordinateSystem.cpp index da3871c18c2..677622f0680 100644 --- a/src/GafferScene/CoordinateSystem.cpp +++ b/src/GafferScene/CoordinateSystem.cpp @@ -72,4 +72,3 @@ IECore::ConstInternedStringVectorDataPtr CoordinateSystem::computeStandardSetNam result->writable().push_back( g_coordinateSystemsSetName ); return result; } - diff --git a/src/GafferScene/CopyAttributes.cpp b/src/GafferScene/CopyAttributes.cpp index e67b3995500..a89a2e26e0a 100644 --- a/src/GafferScene/CopyAttributes.cpp +++ b/src/GafferScene/CopyAttributes.cpp @@ -153,7 +153,7 @@ void CopyAttributes::hashAttributes( const ScenePath &path, const Gaffer::Contex { ScenePlug::ScenePath sourceLocationPath; ScenePlug::stringToPath( sourceLocation, sourceLocationPath ); - ScenePlug::PathScope pathScope( context, sourceLocationPath ); + ScenePlug::PathScope pathScope( context, &sourceLocationPath ); if( sourcePlug()->exists() ) { sourcePlug()->attributesPlug()->hash( h ); @@ -190,7 +190,7 @@ IECore::ConstCompoundObjectPtr CopyAttributes::computeAttributes( const ScenePat { ScenePlug::ScenePath sourceLocationPath; ScenePlug::stringToPath( sourceLocation, sourceLocationPath ); - ScenePlug::PathScope pathScope( context, sourceLocationPath ); + ScenePlug::PathScope pathScope( context, &sourceLocationPath ); if( sourcePlug()->exists() ) { sourceAttributes = sourcePlug()->attributesPlug()->getValue(); diff --git a/src/GafferScene/CurveSampler.cpp b/src/GafferScene/CurveSampler.cpp index 5c842d2a736..b1aabd9e73b 100644 --- a/src/GafferScene/CurveSampler.cpp +++ b/src/GafferScene/CurveSampler.cpp @@ -152,4 +152,3 @@ PrimitiveSampler::SamplingFunction CurveSampler::computeSamplingFunction( const ); }; } - diff --git a/src/GafferScene/Deformer.cpp b/src/GafferScene/Deformer.cpp index 0634071fd6c..340d1d94f15 100644 --- a/src/GafferScene/Deformer.cpp +++ b/src/GafferScene/Deformer.cpp @@ -89,7 +89,7 @@ void Deformer::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outpu if( input == adjustBoundsPlug() || input == filterPlug() || - input == outPlug()->objectPlug() || + affectsProcessedObjectBound( input ) || input == inPlug()->objectPlug() || input == outPlug()->childBoundsPlug() || input == inPlug()->childBoundsPlug() || @@ -105,6 +105,21 @@ bool Deformer::adjustBounds() const return adjustBoundsPlug()->getValue(); } +bool Deformer::affectsProcessedObjectBound( const Gaffer::Plug *input ) const +{ + return input == outPlug()->objectPlug(); +} + +void Deformer::hashProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + outPlug()->objectPlug()->hash( h ); +} + +Imath::Box3f Deformer::computeProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context ) const +{ + return SceneAlgo::bound( outPlug()->objectPlug()->getValue().get() ); +} + void Deformer::hashBound( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { bool adjustBounds; @@ -121,7 +136,7 @@ void Deformer::hashBound( const ScenePath &path, const Gaffer::Context *context, ObjectProcessor::hashBound( path, context, parent, h ); if( m & PathMatcher::ExactMatch ) { - outPlug()->objectPlug()->hash( h ); + hashProcessedObjectBound( path, context, h ); } else { @@ -168,10 +183,7 @@ Imath::Box3f Deformer::computeBound( const ScenePath &path, const Gaffer::Contex if( m & PathMatcher::ExactMatch ) { // Get bounds from deformed output object. - /// \todo Some derived classes may be able to compute an output bound - /// for the object without computing the full deformation. We could add - /// virtual methods to allow that. - result.extendBy( SceneAlgo::bound( outPlug()->objectPlug()->getValue().get() ) ); + result.extendBy( computeProcessedObjectBound( path, context ) ); } else { diff --git a/src/GafferScene/DeleteCurves.cpp b/src/GafferScene/DeleteCurves.cpp index ae39c0fc9ea..2c178706cf9 100644 --- a/src/GafferScene/DeleteCurves.cpp +++ b/src/GafferScene/DeleteCurves.cpp @@ -126,9 +126,7 @@ IECore::ConstObjectPtr DeleteCurves::computeProcessedObject( const ScenePath &pa std::string deletePrimVarName = curvesPlug()->getValue(); - /// \todo Remove. We take values verbatim everywhere else in Gaffer, and I don't - /// see any good reason to differ here. - if( boost::trim_copy( deletePrimVarName ).empty() ) + if( deletePrimVarName.empty() ) { return inputObject; } diff --git a/src/GafferScene/DeleteFaces.cpp b/src/GafferScene/DeleteFaces.cpp index 92e2349ed1b..b255467ebb5 100644 --- a/src/GafferScene/DeleteFaces.cpp +++ b/src/GafferScene/DeleteFaces.cpp @@ -125,9 +125,7 @@ IECore::ConstObjectPtr DeleteFaces::computeProcessedObject( const ScenePath &pat std::string deletePrimVarName = facesPlug()->getValue(); - /// \todo Remove. We take values verbatim everywhere else in Gaffer, and I don't - /// see any good reason to differ here. - if( boost::trim_copy( deletePrimVarName ).empty() ) + if( deletePrimVarName.empty() ) { return inputObject; } @@ -143,5 +141,5 @@ IECore::ConstObjectPtr DeleteFaces::computeProcessedObject( const ScenePath &pat throw InvalidArgumentException( boost::str( boost::format( "DeleteFaces : No primitive variable \"%s\" found" ) % deletePrimVarName ) ); } - return MeshAlgo::deleteFaces( mesh, it->second, invertPlug()->getValue() ); + return MeshAlgo::deleteFaces( mesh, it->second, invertPlug()->getValue(), context->canceller() ); } diff --git a/src/GafferScene/DeleteObject.cpp b/src/GafferScene/DeleteObject.cpp index 75049e25d15..5cf27ad2d11 100644 --- a/src/GafferScene/DeleteObject.cpp +++ b/src/GafferScene/DeleteObject.cpp @@ -169,4 +169,3 @@ Imath::Box3f DeleteObject::computeBound( const ScenePath &path, const Gaffer::Co return inPlug()->boundPlug()->getValue(); } - diff --git a/src/GafferScene/DeletePoints.cpp b/src/GafferScene/DeletePoints.cpp index 3b53ebb0d84..15d1ed68ba1 100644 --- a/src/GafferScene/DeletePoints.cpp +++ b/src/GafferScene/DeletePoints.cpp @@ -126,9 +126,7 @@ IECore::ConstObjectPtr DeletePoints::computeProcessedObject( const ScenePath &pa std::string deletePrimVarName = pointsPlug()->getValue(); - /// \todo Remove. We take values verbatim everywhere else in Gaffer, and I don't - /// see any good reason to differ here. - if( boost::trim_copy( deletePrimVarName ).empty() ) + if( deletePrimVarName.empty() ) { return inputObject; } diff --git a/src/GafferScene/Duplicate.cpp b/src/GafferScene/Duplicate.cpp index fb8d70c159a..c6e67acc052 100644 --- a/src/GafferScene/Duplicate.cpp +++ b/src/GafferScene/Duplicate.cpp @@ -40,13 +40,108 @@ #include "Gaffer/StringPlug.h" +#include "IECore/NullObject.h" #include "IECore/StringAlgo.h" +#include + using namespace std; using namespace IECore; using namespace Gaffer; using namespace GafferScene; +class Duplicate::DuplicatesData : public IECore::Data +{ + + public : + + DuplicatesData( const Duplicate *node, const Context *context ) + { + const ScenePath &source = context->get( ScenePlug::scenePathContextName ); + + // The names of the duplicates are composed of a stem and possibly a + // numeric suffix. + + std::string stem; + int suffix; + + const std::string name = node->namePlug()->getValue(); + const int copies = node->copiesPlug()->getValue(); + + if( name.size() ) + { + const int nameSuffix = StringAlgo::numericSuffix( name, &stem ); + suffix = copies == 1 ? nameSuffix : max( nameSuffix, 1 ); + } + else + { + // No explicit name provided. Derive stem and suffix from source. + suffix = StringAlgo::numericSuffix( source.back(), 0, &stem ); + suffix++; + } + + // Generate names, and at the same time, the transforms associated with them. + + m_names = new InternedStringVectorData; + std::vector &names = m_names->writable(); + names.reserve( copies ); + + const Imath::M44f matrix = node->transformPlug()->matrix(); + + if( suffix == -1 ) + { + assert( copies == 1 ); + names.push_back( stem ); + m_transforms[stem] = matrix; + } + else + { + Imath::M44f m = matrix; + for( int i = 0; i < copies; ++i ) + { + InternedString name = stem + std::to_string( suffix++ ); + names.push_back( name ); + m_transforms[name] = m; + m = m * matrix; + } + } + } + + static bool affectedBy( const Duplicate *node, const Plug *input ) + { + return + input == node->copiesPlug() || + input == node->namePlug() || + node->transformPlug()->isAncestorOf( input ) + ; + } + + static void hash( const Duplicate *node, const Context *context, IECore::MurmurHash &h ) + { + node->copiesPlug()->hash( h ); + node->namePlug()->hash( h ); + const ScenePath &source = context->get( ScenePlug::scenePathContextName ); + h.append( source.back() ); + node->transformPlug()->hash( h ); + } + + ConstInternedStringVectorDataPtr names() const + { + return m_names; + } + + const Imath::M44f &transform( const IECore::InternedString &name ) const + { + return m_transforms.at( name ); + } + + private : + + InternedStringVectorDataPtr m_names; + unordered_map m_transforms; + +}; + GAFFER_NODE_DEFINE_TYPE( Duplicate ); size_t Duplicate::g_firstPlugIndex = 0; @@ -61,18 +156,13 @@ Duplicate::Duplicate( const std::string &name ) addChild( new IntPlug( "copies", Plug::In, 1, 0 ) ); addChild( new StringPlug( "name" ) ); addChild( new TransformPlug( "transform" ) ); - addChild( new StringPlug( "__outParent", Plug::Out ) ); - addChild( new InternedStringVectorDataPlug( "__outChildNames", Plug::Out, inPlug()->childNamesPlug()->defaultValue() ) ); + addChild( new ObjectPlug( "__duplicates", Plug::Out, NullObject::defaultNullObject() ) ); - parentPlug()->setInput( outParentPlug() ); + parentPlug()->setInput( targetPlug() ); parentPlug()->setFlags( Plug::Serialisable, false ); - // Make the filter plug private. We do want to support this one - // day, but the filter should be specifying the objects to duplicate, - // not the parent locations to duplicate them under. Until we implement - // that, its best not to allow folks to become dependent upon behaviour - // that will change. - filterPlug()->setName( "__filter" ); + destinationPlug()->setValue( "${scene:path}/.." ); + destinationPlug()->resetDefault(); // Since we don't introduce any new sets, but just duplicate parts // of existing ones, we can save the BranchCreator base class some @@ -124,43 +214,23 @@ const Gaffer::TransformPlug *Duplicate::transformPlug() const return getChild( g_firstPlugIndex + 3 ); } -Gaffer::StringPlug *Duplicate::outParentPlug() -{ - return getChild( g_firstPlugIndex + 4 ); -} - -const Gaffer::StringPlug *Duplicate::outParentPlug() const -{ - return getChild( g_firstPlugIndex + 4 ); -} - -Gaffer::InternedStringVectorDataPlug *Duplicate::childNamesPlug() +Gaffer::ObjectPlug *Duplicate::duplicatesPlug() { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 4 ); } -const Gaffer::InternedStringVectorDataPlug *Duplicate::childNamesPlug() const +const Gaffer::ObjectPlug *Duplicate::duplicatesPlug() const { - return getChild( g_firstPlugIndex + 5 ); + return getChild( g_firstPlugIndex + 4 ); } void Duplicate::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const { BranchCreator::affects( input, outputs ); - if( input == targetPlug() ) - { - outputs.push_back( outParentPlug() ); - } - - if( - input == inPlug()->existsPlug() || - input == targetPlug() || - input == copiesPlug() || - input == namePlug() - ) + if( DuplicatesData::affectedBy( this, input ) ) { - outputs.push_back( childNamesPlug() ); + outputs.push_back( duplicatesPlug() ); } } @@ -168,96 +238,17 @@ void Duplicate::hash( const ValuePlug *output, const Context *context, IECore::M { BranchCreator::hash( output, context, h ); - if( output == outParentPlug() ) + if( output == duplicatesPlug() ) { - targetPlug()->hash( h ); - } - else if( output == childNamesPlug() ) - { - ScenePath target; - ScenePlug::stringToPath( targetPlug()->getValue(), target ); - if( !inPlug()->exists( target ) ) - { - h = childNamesPlug()->defaultValue()->Object::hash(); - return; - } - h.append( target.data(), target.size() ); - copiesPlug()->hash( h ); - namePlug()->hash( h ); + DuplicatesData::hash( this, context, h ); } } void Duplicate::compute( ValuePlug *output, const Context *context ) const { - if( output == outParentPlug() ) + if( output == duplicatesPlug() ) { - ScenePath target; - ScenePlug::stringToPath( targetPlug()->getValue(), target ); - string parent; - for( size_t i = 0; i < target.size(); ++i ) - { - parent += "/"; - if( i < target.size() - 1 ) - { - parent += target[i]; - } - } - static_cast( output )->setValue( parent ); - return; - } - else if( output == childNamesPlug() ) - { - // Get the path to our target, and check it exists. - ScenePath target; - ScenePlug::stringToPath( targetPlug()->getValue(), target ); - if( !inPlug()->exists( target ) ) - { - output->setToDefault(); - return; - } - - // go ahead and generate our childnames. - // these are composed of a stem and possibly - // a numeric suffix. we default to deriving - // these from the name of the target. - - std::string stem; - int suffix = StringAlgo::numericSuffix( target.back(), 0, &stem ); - suffix++; - - const int copies = copiesPlug()->getValue(); - const std::string name = namePlug()->getValue(); - - // if a name is provided explicitly, then - // it overrides the name and suffix derived - // from the target. - if( name.size() ) - { - std::string nameStem; - const int nameSuffix = StringAlgo::numericSuffix( name, &nameStem ); - stem = nameStem; - suffix = copies == 1 ? nameSuffix : max( nameSuffix, 1 ); - } - - InternedStringVectorDataPtr childNamesData = new InternedStringVectorData; - std::vector &childNames = childNamesData->writable(); - childNames.reserve( copies ); - - if( suffix == -1 ) - { - assert( copies == 1 ); - childNames.push_back( stem ); - } - else - { - boost::format formatter( "%s%d" ); - for( int i = 0; i < copies; ++i ) - { - childNames.push_back( boost::str( formatter % stem % suffix++ ) ); - } - } - - static_cast( output )->setValue( childNamesData ); + static_cast( output )->setValue( new DuplicatesData( this, context ) ); return; } @@ -269,17 +260,17 @@ bool Duplicate::affectsBranchBound( const Gaffer::Plug *input ) const return input == inPlug()->boundPlug(); } -void Duplicate::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); h = inPlug()->boundHash( source ); } -Imath::Box3f Duplicate::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::Box3f Duplicate::computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); return inPlug()->bound( source ); } @@ -287,47 +278,41 @@ bool Duplicate::affectsBranchTransform( const Gaffer::Plug *input ) const { return input == inPlug()->transformPlug() || - transformPlug()->isAncestorOf( input ) || - input == childNamesPlug() + input == duplicatesPlug() ; } -void Duplicate::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - ScenePath source; - sourcePath( branchPath, source ); if( branchPath.size() == 1 ) { - BranchCreator::hashBranchTransform( parentPath, branchPath, context, h ); - h.append( inPlug()->transformHash( source ) ); - transformPlug()->hash( h ); - childNamesPlug()->hash( h ); + BranchCreator::hashBranchTransform( sourcePath, branchPath, context, h ); + ScenePlug::PathScope s( context, &sourcePath ); + duplicatesPlug()->hash( h ); h.append( branchPath[0] ); } else { + ScenePath source; + branchSource( sourcePath, branchPath, source ); h = inPlug()->transformHash( source ); } } -Imath::M44f Duplicate::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::M44f Duplicate::computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - ScenePath source; - sourcePath( branchPath, source ); - Imath::M44f result = inPlug()->transform( source ); if( branchPath.size() == 1 ) { - const Imath::M44f matrix = transformPlug()->matrix(); - ConstInternedStringVectorDataPtr childNamesData = childNamesPlug()->getValue(); - const vector &childNames = childNamesData->readable(); - - size_t i = 0; - do - { - result = result * matrix; - } while( i < childNames.size() && branchPath[0] != childNames[i++] ); + ScenePlug::PathScope s( context, &sourcePath ); + ConstDuplicatesDataPtr duplicates = static_pointer_cast( duplicatesPlug()->getValue() ); + return duplicates->transform( branchPath[0] ); + } + else + { + ScenePath source; + branchSource( sourcePath, branchPath, source ); + return inPlug()->transform( source ); } - return result; } bool Duplicate::affectsBranchAttributes( const Gaffer::Plug *input ) const @@ -335,17 +320,17 @@ bool Duplicate::affectsBranchAttributes( const Gaffer::Plug *input ) const return input == inPlug()->attributesPlug(); } -void Duplicate::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); h = inPlug()->attributesHash( source ); } -IECore::ConstCompoundObjectPtr Duplicate::computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstCompoundObjectPtr Duplicate::computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); return inPlug()->attributes( source ); } @@ -354,49 +339,53 @@ bool Duplicate::affectsBranchObject( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Duplicate::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); h = inPlug()->objectHash( source ); } -IECore::ConstObjectPtr Duplicate::computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstObjectPtr Duplicate::computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); return inPlug()->object( source ); } bool Duplicate::affectsBranchChildNames( const Gaffer::Plug *input ) const { - return input == inPlug()->childNamesPlug() || input == childNamesPlug(); + return input == inPlug()->childNamesPlug() || input == duplicatesPlug(); } -void Duplicate::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 0 ) { - h = childNamesPlug()->hash(); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); + ScenePlug::PathScope s( context, &sourcePath ); + duplicatesPlug()->hash( h ); } else { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); h = inPlug()->childNamesHash( source ); } } -IECore::ConstInternedStringVectorDataPtr Duplicate::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Duplicate::computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { - return childNamesPlug()->getValue(); + ScenePlug::PathScope s( context, &sourcePath ); + ConstDuplicatesDataPtr duplicates = static_pointer_cast( duplicatesPlug()->getValue() ); + return duplicates->names(); } else { ScenePath source; - sourcePath( branchPath, source ); + branchSource( sourcePath, branchPath, source ); return inPlug()->childNames( source ); } } @@ -406,15 +395,15 @@ bool Duplicate::affectsBranchSetNames( const Gaffer::Plug *input ) const return input == inPlug()->setNamesPlug(); } -void Duplicate::hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - assert( parentPath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` + assert( sourcePath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` h = inPlug()->setNamesPlug()->hash(); } -IECore::ConstInternedStringVectorDataPtr Duplicate::computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Duplicate::computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { - assert( parentPath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` + assert( sourcePath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` return inPlug()->setNamesPlug()->getValue(); } @@ -422,19 +411,19 @@ bool Duplicate::affectsBranchSet( const Gaffer::Plug *input ) const { return input == inPlug()->setPlug() || - input == targetPlug() || - input == childNamesPlug() + input == duplicatesPlug() ; } -void Duplicate::hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Duplicate::hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const { h.append( inPlug()->setHash( setName ) ); - targetPlug()->hash( h ); - childNamesPlug()->hash( h ); + h.append( sourcePath.data(), sourcePath.size() ); + ScenePlug::PathScope s( context, &sourcePath ); + duplicatesPlug()->hash( h ); } -IECore::ConstPathMatcherDataPtr Duplicate::computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const +IECore::ConstPathMatcherDataPtr Duplicate::computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const { ConstPathMatcherDataPtr inputSetData = inPlug()->set( setName ); const PathMatcher &inputSet = inputSetData->readable(); @@ -443,30 +432,33 @@ IECore::ConstPathMatcherDataPtr Duplicate::computeBranchSet( const ScenePath &pa return outPlug()->setPlug()->defaultValue(); } - PathMatcher subTree = inputSet.subTree( targetPlug()->getValue() ); + PathMatcher subTree = inputSet.subTree( sourcePath ); if( subTree.isEmpty() ) { return outPlug()->setPlug()->defaultValue(); } - ConstInternedStringVectorDataPtr childNamesData = childNamesPlug()->getValue(); - const vector &childNames = childNamesData->readable(); + ConstDuplicatesDataPtr duplicates; + { + ScenePlug::PathScope s( context, &sourcePath ); + duplicates = static_pointer_cast( duplicatesPlug()->getValue() ); + } PathMatcherDataPtr resultData = new PathMatcherData; PathMatcher &result = resultData->writable(); ScenePath prefix( 1 ); - for( vector::const_iterator it = childNames.begin(), eIt = childNames.end(); it != eIt; ++it ) + for( const auto &name : duplicates->names()->readable() ) { - prefix.back() = *it; + prefix.back() = name; result.addPaths( subTree, prefix ); } return resultData; } -void Duplicate::sourcePath( const ScenePath &branchPath, ScenePath &source ) const +void Duplicate::branchSource( const ScenePath &sourcePath, const ScenePath &branchPath, ScenePath &source ) const { assert( branchPath.size() ); - ScenePlug::stringToPath( targetPlug()->getValue(), source ); + source = sourcePath; copy( ++branchPath.begin(), branchPath.end(), back_inserter( source ) ); } diff --git a/src/GafferScene/Encapsulate.cpp b/src/GafferScene/Encapsulate.cpp index da536ffd39a..c0d805494a0 100644 --- a/src/GafferScene/Encapsulate.cpp +++ b/src/GafferScene/Encapsulate.cpp @@ -217,7 +217,7 @@ IECore::ConstPathMatcherDataPtr Encapsulate::computeSet( const IECore::InternedS for( PathMatcher::RawIterator pIt = inputSet.begin(), peIt = inputSet.end(); pIt != peIt; ) { - sceneScope.set( ScenePlug::scenePathContextName, *pIt ); + sceneScope.set( ScenePlug::scenePathContextName, &(*pIt) ); const int m = filterPlug()->getValue(); if( m & ( IECore::PathMatcher::ExactMatch | IECore::PathMatcher::AncestorMatch ) ) { diff --git a/src/GafferScene/ExistenceQuery.cpp b/src/GafferScene/ExistenceQuery.cpp new file mode 100644 index 00000000000..915e4489fc2 --- /dev/null +++ b/src/GafferScene/ExistenceQuery.cpp @@ -0,0 +1,245 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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/ExistenceQuery.h" + +#include + +namespace GafferScene +{ + +size_t ExistenceQuery::g_firstPlugIndex = 0; + +GAFFER_NODE_DEFINE_TYPE( ExistenceQuery ); + +ExistenceQuery::ExistenceQuery( const std::string& name ) +: Gaffer::ComputeNode( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new ScenePlug( "scene" ) ); + addChild( new Gaffer::StringPlug( "location" ) ); + addChild( new Gaffer::BoolPlug( "exists", Gaffer::Plug::Out, false ) ); + addChild( new Gaffer::StringPlug( "closestAncestor", Gaffer::Plug::Out ) ); +} + +ExistenceQuery::~ExistenceQuery() +{} + +ScenePlug* ExistenceQuery::scenePlug() +{ + return const_cast< ScenePlug* >( + static_cast< const ExistenceQuery* >( this )->scenePlug() ); +} + +const ScenePlug* ExistenceQuery::scenePlug() const +{ + return getChild< ScenePlug >( g_firstPlugIndex ); +} + +Gaffer::StringPlug* ExistenceQuery::locationPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< const ExistenceQuery* >( this )->locationPlug() ); +} + +const Gaffer::StringPlug* ExistenceQuery::locationPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 1 ); +} + +Gaffer::BoolPlug* ExistenceQuery::existsPlug() +{ + return const_cast< Gaffer::BoolPlug* >( + static_cast< const ExistenceQuery* >( this )->existsPlug() ); +} + +const Gaffer::BoolPlug* ExistenceQuery::existsPlug() const +{ + return getChild< Gaffer::BoolPlug >( g_firstPlugIndex + 2 ); +} + +Gaffer::StringPlug* ExistenceQuery::closestAncestorPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< const ExistenceQuery* >( this )->closestAncestorPlug() ); +} + +const Gaffer::StringPlug* ExistenceQuery::closestAncestorPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 3 ); +} + +void ExistenceQuery::affects( const Gaffer::Plug* const input, AffectedPlugsContainer& outputs ) const +{ + ComputeNode::affects( input, outputs ); + + if( + ( input == locationPlug() ) || + ( input == scenePlug()->existsPlug() ) ) + { + outputs.push_back( existsPlug() ); + outputs.push_back( closestAncestorPlug() ); + } +} + +void ExistenceQuery::hash( const Gaffer::ValuePlug* const output, const Gaffer::Context* const context, IECore::MurmurHash& h ) const +{ + ComputeNode::hash( output, context, h ); + + if( output == existsPlug() ) + { + const std::string loc = locationPlug()->getValue(); + + if( ! loc.empty() ) + { + const Gaffer::BoolPlug* const eplug = scenePlug()->existsPlug(); + + // NOTE : scene exists plug returns true by default when there is no input scene. See issue #4245 + + if( eplug->getInput() ) + { + const ScenePlug::ScenePath path = ScenePlug::stringToPath( loc ); + const ScenePlug::PathScope scope( context, & path ); + h = eplug->hash(); + } + } + } + else if( output == closestAncestorPlug() ) + { + const std::string loc = locationPlug()->getValue(); + + if( ! loc.empty() ) + { + const Gaffer::BoolPlug* const eplug = scenePlug()->existsPlug(); + + // NOTE : scene exists plug returns true by default when there is no input scene. See issue #4245 + + if( eplug->getInput() ) + { + ScenePlug::ScenePath path = ScenePlug::stringToPath( loc ); + ScenePlug::PathScope scope( context ); + + while ( ! path.empty() ) + { + scope.setPath( & path ); + + if( eplug->getValue() ) + { + break; + } + + path.pop_back(); + } + + h.append( path.data(), path.size() ); + // `append( path )` is a no-op if path is empty, so we must append + // `size` to distinguish between this and the `loc.empty()` case. + h.append( (uint64_t)path.size() ); + } + else + { + h.append( "/" ); + } + } + } +} + +void ExistenceQuery::compute( Gaffer::ValuePlug* const output, const Gaffer::Context* const context ) const +{ + if( output == existsPlug() ) + { + bool exists = false; + + const std::string loc = locationPlug()->getValue(); + + if( ! loc.empty() ) + { + const Gaffer::BoolPlug* const eplug = scenePlug()->existsPlug(); + + // NOTE : scene exists plug returns true by default when there is no input scene. See issue #4245 + + if( eplug->getInput() ) + { + const ScenePlug::ScenePath path = ScenePlug::stringToPath( loc ); + ScenePlug::PathScope scope( context, & path ); + exists = eplug->getValue(); + } + } + + IECore::assertedStaticCast< Gaffer::BoolPlug >( output )->setValue( exists ); + } + else if( output == closestAncestorPlug() ) + { + std::string loc = locationPlug()->getValue(); + + if( ! loc.empty() ) + { + const Gaffer::BoolPlug* const eplug = scenePlug()->existsPlug(); + + // NOTE : scene exists plug returns true by default when there is no input scene. See issue #4245 + + if( eplug->getInput() ) + { + ScenePlug::ScenePath path = ScenePlug::stringToPath( loc ); + ScenePlug::PathScope scope( context ); + + while( ! path.empty() ) + { + scope.setPath( & path ); + + if( eplug->getValue() ) + { + break; + } + + path.pop_back(); + } + + ScenePlug::pathToString( path, loc ); + } + else + { + loc = "/"; + } + } + + IECore::assertedStaticCast< Gaffer::StringPlug >( output )->setValue( loc ); + } + + ComputeNode::compute( output, context ); +} + +} // GafferScene diff --git a/src/GafferScene/FilterPlug.cpp b/src/GafferScene/FilterPlug.cpp index 4a7c02b2fea..39be32ad531 100644 --- a/src/GafferScene/FilterPlug.cpp +++ b/src/GafferScene/FilterPlug.cpp @@ -147,7 +147,7 @@ void FilterPlug::sceneAffects( const Gaffer::Plug *scenePlugChild, Gaffer::Depen { // Switch with context-varying input. Any input branch could be // relevant. - for( InputFilterPlugIterator it( switchNode->inPlugs() ); !it.done(); ++it ) + for( FilterPlug::InputIterator it( switchNode->inPlugs() ); !it.done(); ++it ) { (*it)->sceneAffects( scenePlugChild, outputs ); } @@ -164,5 +164,6 @@ unsigned FilterPlug::match( const ScenePlug *scene ) const FilterPlug::SceneScope::SceneScope( const Gaffer::Context *context, const ScenePlug *scenePlug ) : EditableScope( context ) { - set( inputSceneContextName, (uint64_t)scenePlug ); + m_scenePlug = scenePlug; + set( inputSceneContextName, (uint64_t*)&m_scenePlug ); } diff --git a/src/GafferScene/FilterProcessor.cpp b/src/GafferScene/FilterProcessor.cpp index f4b76656698..825fe54b95d 100644 --- a/src/GafferScene/FilterProcessor.cpp +++ b/src/GafferScene/FilterProcessor.cpp @@ -112,7 +112,7 @@ void FilterProcessor::affects( const Gaffer::Plug *input, AffectedPlugsContainer { if( const ArrayPlug *arrayIn = this->inPlugs() ) { - for( InputFilterPlugIterator it( arrayIn ); !it.done(); ++it ) + for( FilterPlug::InputIterator it( arrayIn ); !it.done(); ++it ) { (*it)->sceneAffects( input, outputs ); } diff --git a/src/GafferScene/FilterQuery.cpp b/src/GafferScene/FilterQuery.cpp new file mode 100644 index 00000000000..c374745186c --- /dev/null +++ b/src/GafferScene/FilterQuery.cpp @@ -0,0 +1,363 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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/FilterQuery.h" + +#include "GafferScene/Filter.h" +#include "GafferScene/ScenePlug.h" + +using namespace std; +using namespace IECore; +using namespace Gaffer; +using namespace GafferScene; + +size_t FilterQuery::g_firstPlugIndex = 0; + +GAFFER_NODE_DEFINE_TYPE( FilterQuery ) + +FilterQuery::FilterQuery( const std::string &name ) + : ComputeNode( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new ScenePlug( "scene" ) ); + addChild( new FilterPlug( "filter" ) ); + addChild( new StringPlug( "location" ) ); + addChild( new BoolPlug( "exactMatch", Gaffer::Plug::Out ) ); + addChild( new BoolPlug( "descendantMatch", Gaffer::Plug::Out ) ); + addChild( new BoolPlug( "ancestorMatch", Gaffer::Plug::Out ) ); + addChild( new StringPlug( "closestAncestor", Gaffer::Plug::Out ) ); + addChild( new IntPlug( "__match", Gaffer::Plug::Out ) ); + addChild( new StringPlug( "__closestAncestorInternal", Gaffer::Plug::Out ) ); +} + +FilterQuery::~FilterQuery() +{ +} + +ScenePlug *FilterQuery::scenePlug() +{ + return getChild( g_firstPlugIndex ); +} + +const ScenePlug *FilterQuery::scenePlug() const +{ + return getChild( g_firstPlugIndex ); +} + +FilterPlug *FilterQuery::filterPlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const FilterPlug *FilterQuery::filterPlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + +Gaffer::StringPlug *FilterQuery::locationPlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const Gaffer::StringPlug *FilterQuery::locationPlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + +Gaffer::BoolPlug *FilterQuery::exactMatchPlug() +{ + return getChild( g_firstPlugIndex + 3 ); +} + +const Gaffer::BoolPlug *FilterQuery::exactMatchPlug() const +{ + return getChild( g_firstPlugIndex + 3 ); +} + +Gaffer::BoolPlug *FilterQuery::descendantMatchPlug() +{ + return getChild( g_firstPlugIndex + 4 ); +} + +const Gaffer::BoolPlug *FilterQuery::descendantMatchPlug() const +{ + return getChild( g_firstPlugIndex + 4 ); +} + +Gaffer::BoolPlug *FilterQuery::ancestorMatchPlug() +{ + return getChild( g_firstPlugIndex + 5 ); +} + +const Gaffer::BoolPlug *FilterQuery::ancestorMatchPlug() const +{ + return getChild( g_firstPlugIndex + 5 ); +} + +Gaffer::StringPlug *FilterQuery::closestAncestorPlug() +{ + return getChild( g_firstPlugIndex + 6 ); +} + +const Gaffer::StringPlug *FilterQuery::closestAncestorPlug() const +{ + return getChild( g_firstPlugIndex + 6 ); +} + +Gaffer::IntPlug *FilterQuery::matchPlug() +{ + return getChild( g_firstPlugIndex + 7 ); +} + +const Gaffer::IntPlug *FilterQuery::matchPlug() const +{ + return getChild( g_firstPlugIndex + 7 ); +} + +Gaffer::StringPlug *FilterQuery::closestAncestorInternalPlug() +{ + return getChild( g_firstPlugIndex + 8 ); +} + +const Gaffer::StringPlug *FilterQuery::closestAncestorInternalPlug() const +{ + return getChild( g_firstPlugIndex + 8 ); +} + +void FilterQuery::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const +{ + ComputeNode::affects( input, outputs ); + + if( input->parent() == scenePlug() ) + { + filterPlug()->sceneAffects( input, outputs ); + } + + if( + input == locationPlug() || + input == scenePlug()->existsPlug() || + input == filterPlug() + ) + { + outputs.push_back( matchPlug() ); + } + + if( input == matchPlug() ) + { + outputs.push_back( exactMatchPlug() ); + outputs.push_back( descendantMatchPlug() ); + outputs.push_back( ancestorMatchPlug() ); + } + + if( + input == filterPlug() + ) + { + outputs.push_back( closestAncestorInternalPlug() ); + } + + if( + input == locationPlug() || + input == scenePlug()->existsPlug() || + input == closestAncestorInternalPlug() + ) + { + outputs.push_back( closestAncestorPlug() ); + } +} + +void FilterQuery::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const +{ + if( output == matchPlug() ) + { + ComputeNode::hash( output, context, h ); + const string location = locationPlug()->getValue(); + if( !location.empty() ) + { + const ScenePlug::ScenePath locationPath = ScenePlug::stringToPath( location ); + ScenePlug::PathScope scope( context, &locationPath ); + if( scenePlug()->existsPlug()->getValue() ) + { + filterPlug()->hash( h ); + } + } + } + else if( output == exactMatchPlug() ) + { + ComputeNode::hash( output, context, h ); + matchPlug()->hash( h ); + } + else if( output == descendantMatchPlug() ) + { + ComputeNode::hash( output, context, h ); + matchPlug()->hash( h ); + } + else if( output == ancestorMatchPlug() ) + { + ComputeNode::hash( output, context, h ); + matchPlug()->hash( h ); + } + else if( output == closestAncestorPlug() ) + { + const string location = locationPlug()->getValue(); + if( !location.empty() ) + { + const ScenePlug::ScenePath locationPath = ScenePlug::stringToPath( location ); + ScenePlug::PathScope scope( context, &locationPath ); + if( scenePlug()->existsPlug()->getValue() ) + { + h = closestAncestorInternalPlug()->hash(); + return; + } + } + h = output->defaultHash(); + } + else if( output == closestAncestorInternalPlug() ) + { + const int m = filterPlug()->match( scenePlug() ); + if( m & PathMatcher::ExactMatch ) + { + ComputeNode::hash( output, context, h ); + const ScenePlug::ScenePath path = context->get( ScenePlug::scenePathContextName ); + h.append( path.data(), path.size() ); + return; + } + else if( m & PathMatcher::AncestorMatch ) + { + ScenePlug::ScenePath path = context->get( ScenePlug::scenePathContextName ); + if( path.size() ) + { + path.pop_back(); + ScenePlug::PathScope scope( context, &path ); + h = closestAncestorInternalPlug()->hash(); + return; + } + } + h = output->defaultHash(); + } +} + +void FilterQuery::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const +{ + if( output == matchPlug() ) + { + unsigned match = IECore::PathMatcher::NoMatch; + const string location = locationPlug()->getValue(); + if( !location.empty() ) + { + const ScenePlug::ScenePath locationPath = ScenePlug::stringToPath( location ); + ScenePlug::PathScope scope( context, &locationPath ); + if( scenePlug()->existsPlug()->getValue() ) + { + match = filterPlug()->match( scenePlug() ); + } + } + static_cast( output )->setValue( match ); + } + else if( output == exactMatchPlug() ) + { + static_cast( output )->setValue( + matchPlug()->getValue() & IECore::PathMatcher::ExactMatch + ); + } + else if( output == descendantMatchPlug() ) + { + static_cast( output )->setValue( + matchPlug()->getValue() & IECore::PathMatcher::DescendantMatch + ); + } + else if( output == ancestorMatchPlug() ) + { + static_cast( output )->setValue( + matchPlug()->getValue() & IECore::PathMatcher::AncestorMatch + ); + } + else if( output == closestAncestorPlug() ) + { + const string location = locationPlug()->getValue(); + if( !location.empty() ) + { + const ScenePlug::ScenePath locationPath = ScenePlug::stringToPath( location ); + ScenePlug::PathScope scope( context, &locationPath ); + if( scenePlug()->existsPlug()->getValue() ) + { + output->setFrom( closestAncestorInternalPlug() ); + return; + } + } + output->setToDefault(); + } + else if( output == closestAncestorInternalPlug() ) + { + string result; + const int m = filterPlug()->match( scenePlug() ); + if( m & PathMatcher::ExactMatch ) + { + const ScenePlug::ScenePath path = context->get( ScenePlug::scenePathContextName ); + ScenePlug::pathToString( path, result ); + } + else if( m & PathMatcher::AncestorMatch ) + { + // Ancestor match, but we don't know where exactly. Make recursive + // calls using the parent location until we find the ancestor with + // an exact match. + ScenePlug::ScenePath path = context->get( ScenePlug::scenePathContextName ); + if( path.size() ) + { + path.pop_back(); + ScenePlug::PathScope scope( context, &path ); + result = closestAncestorInternalPlug()->getValue(); + } + } + static_cast( output )->setValue( result ); + } + + ComputeNode::compute( output, context ); +} + +Gaffer::ValuePlug::CachePolicy FilterQuery::computeCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( + output == exactMatchPlug() || + output == descendantMatchPlug() || + output == ancestorMatchPlug() + ) + { + // Not much point caching these since they are so trivial. + return ValuePlug::CachePolicy::Uncached; + } + return ComputeNode::computeCachePolicy( output ); +} diff --git a/src/GafferScene/FilterResults.cpp b/src/GafferScene/FilterResults.cpp index 3629e7cc37e..4ea9374282b 100644 --- a/src/GafferScene/FilterResults.cpp +++ b/src/GafferScene/FilterResults.cpp @@ -54,6 +54,7 @@ FilterResults::FilterResults( const std::string &name ) storeIndexOfNextChild( g_firstPlugIndex ); addChild( new ScenePlug( "scene" ) ); addChild( new FilterPlug( "filter" ) ); + addChild( new StringPlug( "root" ) ); addChild( new PathMatcherDataPlug( "__internalOut", Gaffer::Plug::Out, new PathMatcherData ) ); addChild( new PathMatcherDataPlug( "out", Gaffer::Plug::Out, new PathMatcherData ) ); addChild( new StringVectorDataPlug( "outStrings", Gaffer::Plug::Out, new StringVectorData ) ); @@ -83,34 +84,44 @@ const FilterPlug *FilterResults::filterPlug() const return getChild( g_firstPlugIndex + 1 ); } +Gaffer::StringPlug *FilterResults::rootPlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const Gaffer::StringPlug *FilterResults::rootPlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + Gaffer::PathMatcherDataPlug *FilterResults::internalOutPlug() { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 3 ); } const Gaffer::PathMatcherDataPlug *FilterResults::internalOutPlug() const { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 3 ); } Gaffer::PathMatcherDataPlug *FilterResults::outPlug() { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } const Gaffer::PathMatcherDataPlug *FilterResults::outPlug() const { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } Gaffer::StringVectorDataPlug *FilterResults::outStringsPlug() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const Gaffer::StringVectorDataPlug *FilterResults::outStringsPlug() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } void FilterResults::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const @@ -124,6 +135,7 @@ void FilterResults::affects( const Gaffer::Plug *input, AffectedPlugsContainer & if( input == filterPlug() || + input == rootPlug() || input == scenePlug()->childNamesPlug() ) { @@ -147,7 +159,9 @@ void FilterResults::hash( const Gaffer::ValuePlug *output, const Gaffer::Context if( output == internalOutPlug() ) { - h.append( SceneAlgo::matchingPathsHash( filterPlug(), scenePlug() ) ); + ScenePlug::ScenePath rootPath; + ScenePlug::stringToPath( rootPlug()->getValue(), rootPath ); + h.append( SceneAlgo::matchingPathsHash( filterPlug(), scenePlug(), rootPath ) ); } else if( output == outPlug() ) { @@ -165,8 +179,10 @@ void FilterResults::compute( Gaffer::ValuePlug *output, const Gaffer::Context *c { if( output == internalOutPlug() ) { + ScenePlug::ScenePath rootPath; + ScenePlug::stringToPath( rootPlug()->getValue(), rootPath ); PathMatcherDataPtr data = new PathMatcherData; - SceneAlgo::matchingPaths( filterPlug(), scenePlug(), data->writable() ); + SceneAlgo::matchingPaths( filterPlug(), scenePlug(), rootPath, data->writable() ); static_cast( output )->setValue( data ); return; } diff --git a/src/GafferScene/Group.cpp b/src/GafferScene/Group.cpp index 37efced94c0..3cd09a0ad27 100644 --- a/src/GafferScene/Group.cpp +++ b/src/GafferScene/Group.cpp @@ -54,6 +54,13 @@ using namespace IECore; using namespace Gaffer; using namespace GafferScene; +namespace +{ + +const ScenePlug::ScenePath g_root; + +} // namespace + GAFFER_NODE_DEFINE_TYPE( Group ); size_t Group::g_firstPlugIndex = 0; @@ -121,7 +128,7 @@ void Group::affects( const Plug *input, AffectedPlugsContainer &outputs ) const if( input == namePlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } @@ -147,7 +154,7 @@ void Group::affects( const Plug *input, AffectedPlugsContainer &outputs ) const else if( input == mappingPlug() ) { // the mapping affects everything about the output - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } @@ -161,7 +168,7 @@ void Group::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *contex if( output == mappingPlug() ) { - ScenePlug::PathScope scope( context, ScenePath() ); + ScenePlug::PathScope scope( context, &g_root ); for( const auto &p : ScenePlug::Range( *inPlugs() ) ) { p->childNamesPlug()->hash( h ); @@ -174,7 +181,7 @@ void Group::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) if( output == mappingPlug() ) { vector inputChildNames; inputChildNames.reserve( inPlugs()->children().size() ); - ScenePlug::PathScope scope( context, ScenePath() ); + ScenePlug::PathScope scope( context, &g_root ); for( const auto &p : ScenePlug::Range( *inPlugs() ) ) { inputChildNames.push_back( p->childNamesPlug()->getValue() ); @@ -191,7 +198,7 @@ void Group::hashBound( const ScenePath &path, const Gaffer::Context *context, co if( path.size() == 0 ) // "/" { SceneProcessor::hashBound( path, context, parent, h ); - for( ScenePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ScenePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->boundPlug()->hash( h ); } @@ -200,8 +207,8 @@ void Group::hashBound( const ScenePath &path, const Gaffer::Context *context, co else if( path.size() == 1 ) // "/group" { SceneProcessor::hashBound( path, context, parent, h ); - ScenePlug::PathScope scope( context, ScenePath() ); - for( ScenePlugIterator it( inPlugs() ); !it.done(); ++it ) + ScenePlug::PathScope scope( context, &g_root ); + for( ScenePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->boundPlug()->hash( h ); } @@ -221,7 +228,7 @@ Imath::Box3f Group::computeBound( const ScenePath &path, const Gaffer::Context * { // either / or /groupName Box3f combinedBound; - for( ScenePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ScenePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { // we don't need to transform these bounds, because the SceneNode // guarantees that the transform for root nodes is always identity. @@ -384,7 +391,7 @@ IECore::ConstInternedStringVectorDataPtr Group::computeChildNames( const ScenePa void Group::hashSetNames( const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { SceneProcessor::hashSetNames( context, parent, h ); - for( ScenePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ScenePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { (*it)->setNamesPlug()->hash( h ); } @@ -394,7 +401,7 @@ IECore::ConstInternedStringVectorDataPtr Group::computeSetNames( const Gaffer::C { InternedStringVectorDataPtr resultData = new InternedStringVectorData; vector &result = resultData->writable(); - for( ScenePlugIterator it( inPlugs() ); !it.done(); ++it ) + for( ScenePlug::Iterator it( inPlugs() ); !it.done(); ++it ) { // This naive approach to merging set names preserves the order of the incoming names, // but at the expense of using linear search. We assume that the number of sets is small diff --git a/src/GafferScene/IECoreScenePreview/CapturingRenderer.cpp b/src/GafferScene/IECoreScenePreview/CapturingRenderer.cpp index cdfdd38ce5e..d197471ab01 100644 --- a/src/GafferScene/IECoreScenePreview/CapturingRenderer.cpp +++ b/src/GafferScene/IECoreScenePreview/CapturingRenderer.cpp @@ -239,6 +239,16 @@ const std::vector &CapturingRenderer::CapturedObject::capturedSampleTimes return m_capturedSampleTimes; } +const std::vector &CapturingRenderer::CapturedObject::capturedTransforms() const +{ + return m_capturedTransforms; +} + +const std::vector &CapturingRenderer::CapturedObject::capturedTransformTimes() const +{ + return m_capturedTransformTimes; +} + const CapturingRenderer::CapturedAttributes *CapturingRenderer::CapturedObject::capturedAttributes() const { return m_capturedAttributes.get(); @@ -273,13 +283,16 @@ int CapturingRenderer::CapturedObject::numLinkEdits( const IECore::InternedStrin void CapturingRenderer::CapturedObject::transform( const Imath::M44f &transform ) { m_renderer->checkPaused(); - /// \todo Implement + m_capturedTransforms.clear(); + m_capturedTransforms.push_back( transform ); + m_capturedTransformTimes.clear(); } void CapturingRenderer::CapturedObject::transform( const std::vector &samples, const std::vector × ) { m_renderer->checkPaused(); - /// \todo Implement + m_capturedTransforms = samples; + m_capturedTransformTimes = times; } bool CapturingRenderer::CapturedObject::attributes( const AttributesInterface *attributes ) diff --git a/src/GafferScene/IECoreScenePreview/Geometry.cpp b/src/GafferScene/IECoreScenePreview/Geometry.cpp index 4a2d757dda8..beb40537f21 100644 --- a/src/GafferScene/IECoreScenePreview/Geometry.cpp +++ b/src/GafferScene/IECoreScenePreview/Geometry.cpp @@ -154,4 +154,3 @@ void Geometry::memoryUsage( IECore::Object::MemoryAccumulator &accumulator ) con accumulator.accumulate( sizeof( m_bound ) ); accumulator.accumulate( m_parameters.get() ); } - diff --git a/src/GafferScene/Instancer.cpp b/src/GafferScene/Instancer.cpp index 8409fd10d90..34a1840f34d 100644 --- a/src/GafferScene/Instancer.cpp +++ b/src/GafferScene/Instancer.cpp @@ -184,7 +184,7 @@ struct AccessPrototypeContextVariable { T raw = PrimitiveVariable::IndexedView( *v.primVar )[index]; T value = quantize( raw, v.quantize ); - scope.set( v.name, value ); + scope.setAllocated( v.name, value ); } void operator()( const TypedData> *data, const PrototypeContextVariable &v, int index, Context::EditableScope &scope ) @@ -194,11 +194,11 @@ struct AccessPrototypeContextVariable if( v.offsetMode ) { - scope.set( v.name, value + scope.context()->get( v.name ) ); + scope.setAllocated( v.name, value + scope.context()->get( v.name ) ); } else { - scope.set( v.name, value ); + scope.setAllocated( v.name, value ); } } @@ -209,11 +209,11 @@ struct AccessPrototypeContextVariable if( v.offsetMode ) { - scope.set( v.name, float(value) + scope.context()->get( v.name ) ); + scope.setAllocated( v.name, float(value) + scope.context()->get( v.name ) ); } else { - scope.set( v.name, value ); + scope.setAllocated( v.name, value ); } } @@ -447,9 +447,11 @@ class Instancer::EngineData : public Data } } - const ScenePlug::ScenePath &prototypeRoot( const InternedString &name ) const + // Return a pointer since this is for internal use only, and it helps communicate that we + // are responsible for holding the storage for this scene path when it gets put in the context + const ScenePlug::ScenePath *prototypeRoot( const InternedString &name ) const { - return runTimeCast( m_roots[m_names->input( name ).index] )->readable(); + return &( m_roots[m_names->input( name ).index]->readable() ); } const InternedStringVectorData *prototypeNames() const @@ -490,15 +492,13 @@ class Instancer::EngineData : public Data h.append( (uint64_t)pointIndex ); } - CompoundObjectPtr instanceAttributes( size_t pointIndex ) const + void instanceAttributes( size_t pointIndex, CompoundObject &result ) const { - CompoundObjectPtr result = new CompoundObject; - CompoundObject::ObjectMap &writableResult = result->members(); + CompoundObject::ObjectMap &writableResult = result.members(); for( const auto &attributeCreator : m_attributeCreators ) { writableResult[attributeCreator.first] = attributeCreator.second( pointIndex ); } - return result; } typedef std::map< InternedString, boost::unordered_set< IECore::MurmurHash > > PrototypeHashes; @@ -565,7 +565,7 @@ class Instancer::EngineData : public Data if( v.seedMode ) { - scope.set( v.name, seedForPoint( index, v.primVar, v.numSeeds, v.seedScramble ) ); + scope.setAllocated( v.name, seedForPoint( index, v.primVar, v.numSeeds, v.seedScramble ) ); continue; } @@ -922,6 +922,10 @@ Instancer::Instancer( const std::string &name ) addChild( new ScenePlug( "__capsuleScene", Plug::Out ) ); addChild( new PathMatcherDataPlug( "__setCollaborate", Plug::Out, new IECore::PathMatcherData() ) ); + // Hide `destination` plug until we resolve issues surrounding `processesRootObject()`. + // See `BranchCreator::computeObject()`. + destinationPlug()->setName( "__destination" ); + capsuleScenePlug()->boundPlug()->setInput( outPlug()->boundPlug() ); capsuleScenePlug()->transformPlug()->setInput( outPlug()->transformPlug() ); capsuleScenePlug()->attributesPlug()->setInput( outPlug()->attributesPlug() ); @@ -1330,9 +1334,9 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co } else if( output == setCollaboratePlug() ) { - const ScenePath &parentPath = context->get( ScenePlug::scenePathContextName ); + const ScenePath &sourcePath = context->get( ScenePlug::scenePathContextName ); - ConstEngineDataPtr engine = this->engine( parentPath, context ); + ConstEngineDataPtr engine = this->engine( sourcePath, context ); if( !engine->hasContextVariables() ) { // We use a slightly approximate version of hasContextVariables in hashBranchSet, to @@ -1347,14 +1351,16 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co // We could always hash this stuff in hashBranchSet, but we would lose out a benefit of a more // accurate hash when we do actually have context variables: the slower hash won't change // if point locations change, unlike the engineHash which includes all changes - engineHash( parentPath, context, h ); - prototypeChildNamesHash( parentPath, context, h ); + engineHash( sourcePath, context, h ); + prototypeChildNamesHash( sourcePath, context, h ); prototypesPlug()->setPlug()->hash( h ); namePlug()->hash( h ); return; } - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( parentPath, context ); + IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); + + tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); for( const auto &prototypeName : engine->prototypeNames()->readable() ) { @@ -1365,7 +1371,7 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co tbb::parallel_for( tbb::blocked_range( 0, childNames.size() ), [&]( const tbb::blocked_range &r ) { Context::EditableScope scope( threadState ); - // As part of the setCollaborate plug machinery, we put the parentPath in the context. + // As part of the setCollaborate plug machinery, we put the sourcePath in the context. // Need to remove it before evaluating the prototype sets scope.remove( ScenePlug::scenePathContextName ); for( size_t i = r.begin(); i != r.end(); ++i ) @@ -1378,12 +1384,13 @@ void Instancer::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *co h1Accum += instanceH.h1(); h2Accum += instanceH.h2(); } - } + }, + taskGroupContext ); - const ScenePlug::ScenePath &prototypeRoot = engine->prototypeRoot( prototypeName ); + const ScenePlug::ScenePath *prototypeRoot = engine->prototypeRoot( prototypeName ); h.append( prototypeName ); - h.append( &prototypeRoot[0], prototypeRoot.size() ); + h.append( &(*prototypeRoot)[0], prototypeRoot->size() ); h.append( IECore::MurmurHash( h1Accum, h2Accum ) ); } } @@ -1626,22 +1633,24 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte } else if( output == setCollaboratePlug() ) { - const ScenePath &parentPath = context->get( ScenePlug::scenePathContextName ); + const ScenePath &sourcePath = context->get( ScenePlug::scenePathContextName ); - ConstEngineDataPtr engine = this->engine( parentPath, context ); + ConstEngineDataPtr engine = this->engine( sourcePath, context ); - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( parentPath, context ); + IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); PathMatcherDataPtr outputSetData = new PathMatcherData; PathMatcher &outputSet = outputSetData->writable(); vector branchPath( { namePlug()->getValue() } ); + tbb::task_group_context taskGroupContext( tbb::task_group_context::isolated ); + for( const auto &prototypeName : engine->prototypeNames()->readable() ) { branchPath.resize( 2 ); branchPath.back() = prototypeName; - const ScenePlug::ScenePath &prototypeRoot = engine->prototypeRoot( prototypeName ); + const ScenePlug::ScenePath *prototypeRoot = engine->prototypeRoot( prototypeName ); const vector &childNames = prototypeChildNames->member( prototypeName )->readable(); @@ -1651,7 +1660,7 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte tbb::parallel_for( tbb::blocked_range( 0, childNames.size() ), [&]( const tbb::blocked_range &r ) { Context::EditableScope scope( threadState ); - // As part of the setCollaborate plug machinery, we put the parentPath in the context. + // As part of the setCollaborate plug machinery, we put the sourcePath in the context. // Need to remove it before evaluating the prototype sets scope.remove( ScenePlug::scenePathContextName ); @@ -1660,13 +1669,14 @@ void Instancer::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte const size_t pointIndex = engine->pointIndex( childNames[i] ); engine->setPrototypeContextVariables( pointIndex, scope ); ConstPathMatcherDataPtr instanceSet = prototypesPlug()->setPlug()->getValue(); - PathMatcher pointInstanceSet = instanceSet->readable().subTree( prototypeRoot ); + PathMatcher pointInstanceSet = instanceSet->readable().subTree( *prototypeRoot ); tbb::spin_mutex::scoped_lock lock( instanceMutex ); branchPath.back() = childNames[i]; outputSet.addPaths( pointInstanceSet, branchPath ); } - } + }, + taskGroupContext ); } @@ -1715,12 +1725,12 @@ bool Instancer::affectsBranchBound( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() < 2 ) { // "/" or "/instances" - ScenePath path = parentPath; + ScenePath path = sourcePath; path.insert( path.end(), branchPath.begin(), branchPath.end() ); if( branchPath.size() == 0 ) { @@ -1731,14 +1741,14 @@ void Instancer::hashBranchBound( const ScenePath &parentPath, const ScenePath &b else if( branchPath.size() == 2 ) { // "/instances/" - BranchCreator::hashBranchBound( parentPath, branchPath, context, h ); + BranchCreator::hashBranchBound( sourcePath, branchPath, context, h ); - engineHash( parentPath, context, h ); - prototypeChildNamesHash( parentPath, context, h ); + engineHash( sourcePath, context, h ); + prototypeChildNamesHash( sourcePath, context, h ); h.append( branchPath.back() ); { - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); prototypesPlug()->transformPlug()->hash( h ); prototypesPlug()->boundPlug()->hash( h ); } @@ -1746,17 +1756,17 @@ void Instancer::hashBranchBound( const ScenePath &parentPath, const ScenePath &b else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); h = prototypesPlug()->boundPlug()->hash(); } } -Imath::Box3f Instancer::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::Box3f Instancer::computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() < 2 ) { // "/" or "/instances" - ScenePath path = parentPath; + ScenePath path = sourcePath; path.insert( path.end(), branchPath.begin(), branchPath.end() ); if( branchPath.size() == 0 ) { @@ -1772,14 +1782,14 @@ Imath::Box3f Instancer::computeBranchBound( const ScenePath &parentPath, const S // because we have direct access to the engine, we can implement this // more efficiently than `ScenePlug::childBounds()`. - ConstEngineDataPtr e = engine( parentPath, context ); - ConstCompoundDataPtr ic = prototypeChildNames( parentPath, context ); + ConstEngineDataPtr e = engine( sourcePath, context ); + ConstCompoundDataPtr ic = prototypeChildNames( sourcePath, context ); const vector &childNames = ic->member( branchPath.back() )->readable(); M44f childTransform; Box3f childBound; { - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( e.get(), context, &sourcePath, &branchPath ); childTransform = prototypesPlug()->transformPlug()->getValue(); childBound = prototypesPlug()->boundPlug()->getValue(); } @@ -1815,7 +1825,7 @@ Imath::Box3f Instancer::computeBranchBound( const ScenePath &parentPath, const S else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); return prototypesPlug()->boundPlug()->getValue(); } } @@ -1828,33 +1838,33 @@ bool Instancer::affectsBranchTransform( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() <= 2 ) { // "/" or "/instances" or "/instances/" - BranchCreator::hashBranchTransform( parentPath, branchPath, context, h ); + BranchCreator::hashBranchTransform( sourcePath, branchPath, context, h ); } else if( branchPath.size() == 3 ) { // "/instances//" - BranchCreator::hashBranchTransform( parentPath, branchPath, context, h ); + BranchCreator::hashBranchTransform( sourcePath, branchPath, context, h ); { - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); prototypesPlug()->transformPlug()->hash( h ); } - engineHash( parentPath, context, h ); + engineHash( sourcePath, context, h ); h.append( branchPath[2] ); } else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); h = prototypesPlug()->transformPlug()->hash(); } } -Imath::M44f Instancer::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::M44f Instancer::computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() <= 2 ) { @@ -1865,11 +1875,11 @@ Imath::M44f Instancer::computeBranchTransform( const ScenePath &parentPath, cons { // "/instances//" M44f result; + ConstEngineDataPtr e = engine( sourcePath, context ); { - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( e.get(), context, &sourcePath, &branchPath ); result = prototypesPlug()->transformPlug()->getValue(); } - ConstEngineDataPtr e = engine( parentPath, context ); const size_t pointIndex = e->pointIndex( branchPath[2] ); result = result * e->instanceTransform( pointIndex ); return result; @@ -1877,7 +1887,7 @@ Imath::M44f Instancer::computeBranchTransform( const ScenePath &parentPath, cons else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); return prototypesPlug()->transformPlug()->getValue(); } } @@ -1890,69 +1900,63 @@ bool Instancer::affectsBranchAttributes( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - if( branchPath.size() <= 1 ) + if( branchPath.size() <= 2 ) { - // "/" or "/instances" + // "/" or "/instances" or "/instances/" h = outPlug()->attributesPlug()->defaultValue()->Object::hash(); } - else if( branchPath.size() == 2 ) - { - // "/instances/" - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); - h = prototypesPlug()->attributesPlug()->hash(); - } else if( branchPath.size() == 3 ) { // "/instances//" - BranchCreator::hashBranchAttributes( parentPath, branchPath, context, h ); + BranchCreator::hashBranchAttributes( sourcePath, branchPath, context, h ); + ConstEngineDataPtr e = engine( sourcePath, context ); + if( e->numInstanceAttributes() ) { - ConstEngineDataPtr e = engine( parentPath, context ); - if( e->numInstanceAttributes() ) - { - e->instanceAttributesHash( e->pointIndex( branchPath[2] ), h ); - } + e->instanceAttributesHash( e->pointIndex( branchPath[2] ), h ); } + PrototypeScope scope( e.get(), context, &sourcePath, &branchPath ); + prototypesPlug()->attributesPlug()->hash( h ); } else { // "/instances///... - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); h = prototypesPlug()->attributesPlug()->hash(); } } -IECore::ConstCompoundObjectPtr Instancer::computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstCompoundObjectPtr Instancer::computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - if( branchPath.size() <= 1 ) + if( branchPath.size() <= 2 ) { - // "/" or "/instances" + // "/" or "/instances" or "/instances/" return outPlug()->attributesPlug()->defaultValue(); } - else if( branchPath.size() == 2 ) - { - // "/instances/" - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); - return prototypesPlug()->attributesPlug()->getValue(); - } else if( branchPath.size() == 3 ) { // "/instances//" - ConstEngineDataPtr e = engine( parentPath, context ); + ConstEngineDataPtr e = engine( sourcePath, context ); + PrototypeScope scope( e.get(), context, &sourcePath, &branchPath ); + ConstCompoundObjectPtr prototypeAttrs = prototypesPlug()->attributesPlug()->getValue(); if( e->numInstanceAttributes() ) { - return e->instanceAttributes( e->pointIndex( branchPath[2] ) ); + CompoundObjectPtr result = new CompoundObject; + result->members() = prototypeAttrs->members(); + + e->instanceAttributes( e->pointIndex( branchPath[2] ), *result ); + return result; } else { - return outPlug()->attributesPlug()->defaultValue(); + return prototypeAttrs; } } else { // "/instances///... - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); return prototypesPlug()->attributesPlug()->getValue(); } } @@ -1970,7 +1974,7 @@ bool Instancer::affectsBranchObject( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() <= 2 ) { @@ -1980,12 +1984,12 @@ void Instancer::hashBranchObject( const ScenePath &parentPath, const ScenePath & else { // "/instances///... - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); h = prototypesPlug()->objectPlug()->hash(); } } -IECore::ConstObjectPtr Instancer::computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstObjectPtr Instancer::computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() <= 2 ) { @@ -1995,7 +1999,7 @@ IECore::ConstObjectPtr Instancer::computeBranchObject( const ScenePath &parentPa else { // "/instances///... - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); return prototypesPlug()->objectPlug()->getValue(); } } @@ -2004,16 +2008,16 @@ void Instancer::hashObject( const ScenePath &path, const Gaffer::Context *contex { if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) { - // Handling this special case here means an extra call to parentAndBranchPaths + // Handling this special case here means an extra call to sourceAndBranchPaths // when we're encapsulating and we're not inside a branch - this is a small // unnecessary cost, but by falling back to just using BranchCreator hashObject // when branchPath.size() != 2, we are able to just use all the logic from // BranchCreator, without exposing any new API surface - ScenePath parentPath, branchPath; - parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + parentAndBranchPaths( path, sourcePath, branchPath ); if( branchPath.size() == 2 ) { - BranchCreator::hashBranchObject( parentPath, branchPath, context, h ); + BranchCreator::hashBranchObject( sourcePath, branchPath, context, h ); h.append( reinterpret_cast( this ) ); /// We need to include anything that will affect how the capsule will expand. /// \todo Once we fix motion blur behaviour so that Capsules don't @@ -2021,7 +2025,7 @@ void Instancer::hashObject( const ScenePath &path, const Gaffer::Context *contex /// the `dirtyCount` for `prototypesPlug()->globalsPlug()`, by summing the /// count for its siblings instead. h.append( prototypesPlug()->dirtyCount() ); - engineHash( parentPath, context, h ); + engineHash( sourcePath, context, h ); h.append( context->hash() ); outPlug()->boundPlug()->hash( h ); return; @@ -2035,8 +2039,8 @@ IECore::ConstObjectPtr Instancer::computeObject( const ScenePath &path, const Ga { if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) { - ScenePath parentPath, branchPath; - parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + parentAndBranchPaths( path, sourcePath, branchPath ); if( branchPath.size() == 2 ) { return new Capsule( @@ -2063,36 +2067,36 @@ bool Instancer::affectsBranchChildNames( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 0 ) { // "/" - BranchCreator::hashBranchChildNames( parentPath, branchPath, context, h ); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); namePlug()->hash( h ); } else if( branchPath.size() == 1 ) { // "/instances" - BranchCreator::hashBranchChildNames( parentPath, branchPath, context, h ); - engineHash( parentPath, context, h ); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); + engineHash( sourcePath, context, h ); } else if( branchPath.size() == 2 ) { // "/instances/" - BranchCreator::hashBranchChildNames( parentPath, branchPath, context, h ); - prototypeChildNamesHash( parentPath, context, h ); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); + prototypeChildNamesHash( sourcePath, context, h ); h.append( branchPath.back() ); } else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); h = prototypesPlug()->childNamesPlug()->hash(); } } -IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { @@ -2109,18 +2113,18 @@ IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( con else if( branchPath.size() == 1 ) { // "/instances" - return engine( parentPath, context )->prototypeNames(); + return engine( sourcePath, context )->prototypeNames(); } else if( branchPath.size() == 2 ) { // "/instances/" - IECore::ConstCompoundDataPtr ic = prototypeChildNames( parentPath, context ); + IECore::ConstCompoundDataPtr ic = prototypeChildNames( sourcePath, context ); return ic->member( branchPath.back() ); } else { // "/instances///..." - PrototypeScope scope( enginePlug(), context, parentPath, branchPath ); + PrototypeScope scope( enginePlug(), context, &sourcePath, &branchPath ); return prototypesPlug()->childNamesPlug()->getValue(); } } @@ -2129,8 +2133,8 @@ void Instancer::hashChildNames( const ScenePath &path, const Gaffer::Context *co { if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) { - ScenePath parentPath, branchPath; - parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + parentAndBranchPaths( path, sourcePath, branchPath ); if( branchPath.size() == 2 ) { h = outPlug()->childNamesPlug()->defaultValue()->Object::hash(); @@ -2145,8 +2149,8 @@ IECore::ConstInternedStringVectorDataPtr Instancer::computeChildNames( const Sce { if( parent != capsuleScenePlug() && encapsulateInstanceGroupsPlug()->getValue() ) { - ScenePath parentPath, branchPath; - parentAndBranchPaths( path, parentPath, branchPath ); + ScenePath sourcePath, branchPath; + parentAndBranchPaths( path, sourcePath, branchPath ); if( branchPath.size() == 2 ) { return outPlug()->childNamesPlug()->defaultValue(); @@ -2161,15 +2165,15 @@ bool Instancer::affectsBranchSetNames( const Gaffer::Plug *input ) const return input == prototypesPlug()->setNamesPlug(); } -void Instancer::hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - assert( parentPath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` + assert( sourcePath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` h = prototypesPlug()->setNamesPlug()->hash(); } -IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { - assert( parentPath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` + assert( sourcePath.size() == 0 ); // Expectation driven by `constantBranchSetNames() == true` return prototypesPlug()->setNamesPlug()->getValue(); } @@ -2184,9 +2188,9 @@ bool Instancer::affectsBranchSet( const Gaffer::Plug *input ) const ; } -void Instancer::hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchSet( parentPath, setName, context, h ); + BranchCreator::hashBranchSet( sourcePath, setName, context, h ); // If we have context variables, we need to do a much more expensive evaluation of the prototype set // plug in every instance context. We allow task collaboration on this expensive evaluation by redirecting @@ -2209,34 +2213,34 @@ void Instancer::hashBranchSet( const ScenePath &parentPath, const IECore::Intern if( hasContextVariables ) { Context::EditableScope scope( context ); - scope.set( ScenePlug::scenePathContextName, parentPath ); + scope.set( ScenePlug::scenePathContextName, &sourcePath ); setCollaboratePlug()->hash( h ); } else { - engineHash( parentPath, context, h ); - prototypeChildNamesHash( parentPath, context, h ); + engineHash( sourcePath, context, h ); + prototypeChildNamesHash( sourcePath, context, h ); prototypesPlug()->setPlug()->hash( h ); namePlug()->hash( h ); } } -IECore::ConstPathMatcherDataPtr Instancer::computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const +IECore::ConstPathMatcherDataPtr Instancer::computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const { - ConstEngineDataPtr engine = this->engine( parentPath, context ); + ConstEngineDataPtr engine = this->engine( sourcePath, context ); if( engine->hasContextVariables() ) { // When doing the much expensive work required when we have context variables, we try to share the // work between multiple threads using an internal PathMatcher plug with a TaskCollaborate policy. - // The setCollaborate plug does all the heavy work. It is evaluated with the parentPath in the + // The setCollaborate plug does all the heavy work. It is evaluated with the sourcePath in the // context's scenePath, and it returns a PathMatcher for the set contents of one branch. Context::EditableScope scope( context ); - scope.set( ScenePlug::scenePathContextName, parentPath ); + scope.set( ScenePlug::scenePathContextName, &sourcePath ); return setCollaboratePlug()->getValue(); } - IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( parentPath, context ); + IECore::ConstCompoundDataPtr prototypeChildNames = this->prototypeChildNames( sourcePath, context ); ConstPathMatcherDataPtr inputSet = prototypesPlug()->setPlug()->getValue(); PathMatcherDataPtr outputSetData = new PathMatcherData; @@ -2249,7 +2253,7 @@ IECore::ConstPathMatcherDataPtr Instancer::computeBranchSet( const ScenePath &pa branchPath.resize( 2 ); branchPath.back() = prototypeName; - PathMatcher instanceSet = inputSet->readable().subTree( engine->prototypeRoot( prototypeName ) ); + PathMatcher instanceSet = inputSet->readable().subTree( *engine->prototypeRoot( prototypeName ) ); const vector &childNames = prototypeChildNames->member( prototypeName )->readable(); @@ -2285,51 +2289,63 @@ IECore::ConstPathMatcherDataPtr Instancer::computeSet( const IECore::InternedStr return BranchCreator::computeSet( setName, context, parent ); } -Instancer::ConstEngineDataPtr Instancer::engine( const ScenePath &parentPath, const Gaffer::Context *context ) const +Instancer::ConstEngineDataPtr Instancer::engine( const ScenePath &sourcePath, const Gaffer::Context *context ) const { - ScenePlug::PathScope scope( context, parentPath ); + ScenePlug::PathScope scope( context, &sourcePath ); return boost::static_pointer_cast( enginePlug()->getValue() ); } -void Instancer::engineHash( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::engineHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - ScenePlug::PathScope scope( context, parentPath ); + ScenePlug::PathScope scope( context, &sourcePath ); enginePlug()->hash( h ); } -IECore::ConstCompoundDataPtr Instancer::prototypeChildNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstCompoundDataPtr Instancer::prototypeChildNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { - ScenePlug::PathScope scope( context, parentPath ); + ScenePlug::PathScope scope( context, &sourcePath ); return prototypeChildNamesPlug()->getValue(); } -void Instancer::prototypeChildNamesHash( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Instancer::prototypeChildNamesHash( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - ScenePlug::PathScope scope( context, parentPath ); + ScenePlug::PathScope scope( context, &sourcePath ); prototypeChildNamesPlug()->hash( h ); } -Instancer::PrototypeScope::PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath &parentPath, const ScenePath &branchPath ) +Instancer::PrototypeScope::PrototypeScope( const Gaffer::ObjectPlug *enginePlug, const Gaffer::Context *context, const ScenePath *sourcePath, const ScenePath *branchPath ) : Gaffer::Context::EditableScope( context ) { - assert( branchPath.size() >= 2 ); - - set( ScenePlug::scenePathContextName, parentPath ); + set( ScenePlug::scenePathContextName, sourcePath ); ConstEngineDataPtr engine = boost::static_pointer_cast( enginePlug->getValue() ); - const ScenePlug::ScenePath &prototypeRoot = engine->prototypeRoot( branchPath[1] ); - if( branchPath.size() >= 3 && engine->hasContextVariables() ) + setPrototype( engine.get(), branchPath ); +} + +Instancer::PrototypeScope::PrototypeScope( const EngineData *engine, const Gaffer::Context *context, const ScenePath *sourcePath, const ScenePath *branchPath ) + : Gaffer::Context::EditableScope( context ) +{ + setPrototype( engine, branchPath ); +} + +void Instancer::PrototypeScope::setPrototype( const EngineData *engine, const ScenePath *branchPath ) +{ + assert( branchPath->size() >= 2 ); + + const ScenePlug::ScenePath *prototypeRoot = engine->prototypeRoot( (*branchPath)[1] ); + + if( branchPath->size() >= 3 && engine->hasContextVariables() ) { - const size_t pointIndex = engine->pointIndex( branchPath[2] ); + const size_t pointIndex = engine->pointIndex( (*branchPath)[2] ); engine->setPrototypeContextVariables( pointIndex, *this ); } - if( branchPath.size() > 3 ) + if( branchPath->size() > 3 ) { - ScenePlug::ScenePath prototypePath( prototypeRoot ); - prototypePath.reserve( prototypeRoot.size() + branchPath.size() - 3 ); - prototypePath.insert( prototypePath.end(), branchPath.begin() + 3, branchPath.end() ); - set( ScenePlug::scenePathContextName, prototypePath ); + m_prototypePath = *prototypeRoot; + m_prototypePath.reserve( prototypeRoot->size() + branchPath->size() - 3 ); + m_prototypePath.insert( m_prototypePath.end(), branchPath->begin() + 3, branchPath->end() ); + set( ScenePlug::scenePathContextName, &m_prototypePath ); } else { diff --git a/src/GafferScene/InteractiveRender.cpp b/src/GafferScene/InteractiveRender.cpp index 76b44aeae3d..59d54863a03 100644 --- a/src/GafferScene/InteractiveRender.cpp +++ b/src/GafferScene/InteractiveRender.cpp @@ -168,7 +168,7 @@ InteractiveRender::InteractiveRender( const IECore::InternedString &rendererType // Incremented when new messages are received, triggering a dirty signal for the output plug. addChild( new IntPlug( "__messageUpdateCount", Plug::In, 0, 0, Imath::limits::max(), Plug::Default & ~Plug::Serialisable ) ); - SceneProcessorPtr adaptors = RendererAlgo::createAdaptors(); + SceneProcessorPtr adaptors = SceneAlgo::createRenderAdaptors(); setChild( "__adaptors", adaptors ); adaptors->inPlug()->setInput( inPlug() ); adaptedInPlug()->setInput( adaptors->outPlug() ); diff --git a/src/GafferScene/Isolate.cpp b/src/GafferScene/Isolate.cpp index 7d8aa0cfef8..53f362f8e0e 100644 --- a/src/GafferScene/Isolate.cpp +++ b/src/GafferScene/Isolate.cpp @@ -266,7 +266,7 @@ void Isolate::hashChildNames( const ScenePath &path, const Gaffer::Context *cont const unsigned m = setsToKeep.match( childPath ); if( m == IECore::PathMatcher::NoMatch ) { - sceneScope.set( ScenePlug::scenePathContextName, childPath ); + sceneScope.set( ScenePlug::scenePathContextName, &childPath ); filterPlug()->hash( h ); } else @@ -305,7 +305,7 @@ IECore::ConstInternedStringVectorDataPtr Isolate::computeChildNames( const Scene unsigned m = setsToKeep.match( childPath ); if( m == IECore::PathMatcher::NoMatch ) { - sceneScope.set( ScenePlug::scenePathContextName, childPath ); + sceneScope.set( ScenePlug::scenePathContextName, &childPath ); m |= filterPlug()->getValue(); } if( m != IECore::PathMatcher::NoMatch ) @@ -396,7 +396,7 @@ IECore::ConstPathMatcherDataPtr Isolate::computeSet( const IECore::InternedStrin for( PathMatcher::RawIterator pIt = inputSet.begin(), peIt = inputSet.end(); pIt != peIt; ) { - sceneScope.set( ScenePlug::scenePathContextName, *pIt ); + sceneScope.set( ScenePlug::scenePathContextName, &(*pIt) ); const int m = filterPlug()->getValue() | setsToKeep.match( *pIt ); if( m & ( IECore::PathMatcher::ExactMatch | IECore::PathMatcher::AncestorMatch ) ) { diff --git a/src/GafferScene/LightToCamera.cpp b/src/GafferScene/LightToCamera.cpp index 2950226168a..893cfbbb26b 100644 --- a/src/GafferScene/LightToCamera.cpp +++ b/src/GafferScene/LightToCamera.cpp @@ -460,7 +460,7 @@ IECore::ConstPathMatcherDataPtr LightToCamera::computeSet( const IECore::Interne /// work for us. for( PathMatcher::Iterator pIt = lightSet.begin(), peIt = lightSet.end(); pIt != peIt; ++pIt ) { - sceneScope.set( ScenePlug::scenePathContextName, *pIt ); + sceneScope.set( ScenePlug::scenePathContextName, &(*pIt) ); const int m = filterPlug()->getValue(); if( m & IECore::PathMatcher::ExactMatch ) { @@ -477,4 +477,3 @@ IECore::ConstPathMatcherDataPtr LightToCamera::computeSet( const IECore::Interne return outputSetData; } - diff --git a/src/GafferScene/MergeScenes.cpp b/src/GafferScene/MergeScenes.cpp index 496688583a3..bb6f7b9d1aa 100644 --- a/src/GafferScene/MergeScenes.cpp +++ b/src/GafferScene/MergeScenes.cpp @@ -313,7 +313,7 @@ void MergeScenes::hashActiveInputs( const Gaffer::Context *context, IECore::Murm InputMask parentActiveInputs; { ScenePath parentPath = scenePath; parentPath.pop_back(); - ScenePlug::PathScope parentScope( context, parentPath ); + ScenePlug::PathScope parentScope( context, &parentPath ); parentActiveInputs = activeInputsPlug()->getValue(); } @@ -355,7 +355,7 @@ int MergeScenes::computeActiveInputs( const Gaffer::Context *context ) const InputMask parentActiveInputs; { ScenePath parentPath = scenePath; parentPath.pop_back(); - ScenePlug::PathScope parentScope( context, parentPath ); + ScenePlug::PathScope parentScope( context, &parentPath ); parentActiveInputs = activeInputsPlug()->getValue(); } @@ -413,7 +413,7 @@ void MergeScenes::hashMergedDescendantsBound( const Gaffer::Context *context, IE for( const auto &childName : childNamesData->readable() ) { childPath.back() = childName; - childScope.setPath( childPath ); + childScope.setPath( &childPath ); const InputMask childActiveInputs( activeInputsPlug()->getValue() ); if( childActiveInputs.count() == 1 && childActiveInputs[firstActiveIndex] ) { @@ -461,7 +461,7 @@ const Imath::Box3f MergeScenes::computeMergedDescendantsBound( const Gaffer::Con for( const auto &childName : childNamesData->readable() ) { childPath.back() = childName; - childScope.setPath( childPath ); + childScope.setPath( &childPath ); const InputMask childActiveInputs( activeInputsPlug()->getValue() ); if( childActiveInputs.count() == 1 && childActiveInputs[firstActiveIndex] ) { @@ -854,7 +854,7 @@ IECore::ConstInternedStringVectorDataPtr MergeScenes::computeSetNames( const Gaf { if( std::find( merged->readable().begin(), merged->readable().end(), setName ) == merged->readable().end() ) { - merged->writable().push_back( setName ); + merged->writable().push_back( setName ); } } } diff --git a/src/GafferScene/MeshDistortion.cpp b/src/GafferScene/MeshDistortion.cpp index 635b65a56b1..10341ae90c7 100644 --- a/src/GafferScene/MeshDistortion.cpp +++ b/src/GafferScene/MeshDistortion.cpp @@ -164,7 +164,8 @@ IECore::ConstObjectPtr MeshDistortion::computeProcessedObject( const ScenePath & mesh, uvSet, referencePosition, - position + position, + context->canceller() ); MeshPrimitivePtr result = mesh->copy(); diff --git a/src/GafferScene/MeshTangents.cpp b/src/GafferScene/MeshTangents.cpp index e3ea167c7ed..57b7c3552a4 100644 --- a/src/GafferScene/MeshTangents.cpp +++ b/src/GafferScene/MeshTangents.cpp @@ -222,7 +222,7 @@ IECore::ConstObjectPtr MeshTangents::computeProcessedObject( const ScenePath &pa std::string uTangent = uTangentPlug()->getValue(); std::string vTangent = vTangentPlug()->getValue(); - tangentPrimvars = MeshAlgo::calculateTangentsFromUV( mesh, uvSet, position, ortho, leftHanded ); + tangentPrimvars = MeshAlgo::calculateTangentsFromUV( mesh, uvSet, position, ortho, leftHanded, context->canceller() ); meshWithTangents->variables[uTangent] = tangentPrimvars.first; meshWithTangents->variables[vTangent] = tangentPrimvars.second; @@ -235,15 +235,15 @@ IECore::ConstObjectPtr MeshTangents::computeProcessedObject( const ScenePath &pa if ( mode == Mode::FirstEdge ) { - tangentPrimvars = MeshAlgo::calculateTangentsFromFirstEdge( mesh, position, normal, ortho, leftHanded ); + tangentPrimvars = MeshAlgo::calculateTangentsFromFirstEdge( mesh, position, normal, ortho, leftHanded, context->canceller() ); } else if ( mode == Mode::TwoEdges ) { - tangentPrimvars = MeshAlgo::calculateTangentsFromTwoEdges( mesh, position, normal, ortho, leftHanded ); + tangentPrimvars = MeshAlgo::calculateTangentsFromTwoEdges( mesh, position, normal, ortho, leftHanded, context->canceller() ); } else { - tangentPrimvars = MeshAlgo::calculateTangentsFromPrimitiveCentroid( mesh, position, normal, ortho, leftHanded ); + tangentPrimvars = MeshAlgo::calculateTangentsFromPrimitiveCentroid( mesh, position, normal, ortho, leftHanded, context->canceller() ); } meshWithTangents->variables[tangent] = tangentPrimvars.first; diff --git a/src/GafferScene/MeshType.cpp b/src/GafferScene/MeshType.cpp index 1ee564a2c7c..249bb4d1d34 100644 --- a/src/GafferScene/MeshType.cpp +++ b/src/GafferScene/MeshType.cpp @@ -38,7 +38,7 @@ #include "Gaffer/StringPlug.h" -#include "IECoreScene/MeshNormalsOp.h" +#include "IECoreScene/MeshAlgo.h" #include "IECoreScene/MeshPrimitive.h" using namespace IECore; @@ -119,7 +119,7 @@ IECore::ConstObjectPtr MeshType::computeProcessedObject( const ScenePath &path, return inputObject; } - std::string meshType = meshTypePlug()->getValue(); + std::string meshType = meshTypePlug()->getValue(); if( meshType == "" ) { // unchanged @@ -153,10 +153,7 @@ IECore::ConstObjectPtr MeshType::computeProcessedObject( const ScenePath &path, if( doNormals ) { - IECoreScene::MeshNormalsOpPtr normalOp = new IECoreScene::MeshNormalsOp(); - normalOp->inputParameter()->setValue( result ); - normalOp->copyParameter()->setTypedValue( false ); - normalOp->operate(); + result->variables[ "N" ] = MeshAlgo::calculateNormals( result.get(), PrimitiveVariable::Interpolation::Vertex, "P", context->canceller() ); } return result; diff --git a/src/GafferScene/OpenGLRender.cpp b/src/GafferScene/OpenGLRender.cpp index 722203998d1..01f993fad1c 100644 --- a/src/GafferScene/OpenGLRender.cpp +++ b/src/GafferScene/OpenGLRender.cpp @@ -54,4 +54,3 @@ OpenGLRender::OpenGLRender( const std::string &name ) OpenGLRender::~OpenGLRender() { } - diff --git a/src/GafferScene/Options.cpp b/src/GafferScene/Options.cpp index b9462838f2f..c965b9432dc 100644 --- a/src/GafferScene/Options.cpp +++ b/src/GafferScene/Options.cpp @@ -116,7 +116,7 @@ IECore::ConstCompoundObjectPtr Options::computeProcessedGlobals( const Gaffer::C const std::string prefix = computePrefix( context ); std::string name; - for( NameValuePlugIterator it( p ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( p ); !it.done(); ++it ) { IECore::DataPtr d = p->memberDataAndName( it->get(), name ); if( d ) diff --git a/src/GafferScene/Outputs.cpp b/src/GafferScene/Outputs.cpp index 6bb554a0ea0..e9c5455bca1 100644 --- a/src/GafferScene/Outputs.cpp +++ b/src/GafferScene/Outputs.cpp @@ -188,7 +188,7 @@ IECore::ConstCompoundObjectPtr Outputs::computeProcessedGlobals( const Gaffer::C result->members() = inputGlobals->members(); // add our outputs to the result - for( InputValuePlugIterator it( dsp ); !it.done(); ++it ) + for( ValuePlug::InputIterator it( dsp ); !it.done(); ++it ) { const ValuePlug *outputPlug = it->get(); if( outputPlug->getChild( "active" )->getValue() ) diff --git a/src/GafferScene/Parent.cpp b/src/GafferScene/Parent.cpp index 5b55c286392..332e688c1b7 100644 --- a/src/GafferScene/Parent.cpp +++ b/src/GafferScene/Parent.cpp @@ -38,6 +38,8 @@ #include "GafferScene/Private/ChildNamesMap.h" +#include "Gaffer/StringPlug.h" + #include "IECore/NullObject.h" using namespace std; @@ -46,6 +48,83 @@ using namespace IECore; using namespace Gaffer; using namespace GafferScene; +namespace +{ + +const ScenePlug::ScenePath g_root; + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// Scope classes +////////////////////////////////////////////////////////////////////////// + +// Context scope that manages the context variable defined by the +// `Parent.parentVariable` plug. This derives from `GlobalScope` because we want +// to evaluate the `parentVariable` plug in a clean context, which is also +// convenient in `SourceScope` where we want to do the same for the `__mapping` +// plug. It is less convenient in `hashSet()/computeSet()` where we need to +// reintroduce the `scene:setName` variable, but on balance this approach is a +// win because it keeps context creation to a minimum. +class Parent::ParentScope : public ScenePlug::GlobalScope +{ + + public : + + ParentScope( const Parent *parent, const ScenePlug::ScenePath &sourcePath, const Context *context ) + : ScenePlug::GlobalScope( context ) + { + const string parentVariable = parent->parentVariablePlug()->getValue(); + if( !parentVariable.empty() ) + { + ScenePlug::pathToString( sourcePath, m_sourceString ); + set( parentVariable, &m_sourceString ); + } + } + + private : + + std::string m_sourceString; + +}; + +// Context scope used for evaluating the `children` plugs. +class Parent::SourceScope : public ParentScope +{ + + public : + + SourceScope( const Parent *parent, const ScenePlug::ScenePath &sourcePath, const ScenePlug::ScenePath &branchPath, const Context *context ) + : ParentScope( parent, sourcePath, context ) + { + Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( parent->mappingPlug()->getValue() ); + + const Private::ChildNamesMap::Input &input = mapping->input( branchPath[0] ); + m_sourcePlug = parent->childrenPlug()->getChild( input.index ); + + m_sourcePath.reserve( branchPath.size() ); + m_sourcePath.push_back( input.name ); + m_sourcePath.insert( m_sourcePath.end(), branchPath.begin() + 1, branchPath.end() ); + + set( ScenePlug::scenePathContextName, &m_sourcePath ); + } + + const ScenePlug *sourcePlug() const + { + return m_sourcePlug; + } + + private : + + const ScenePlug *m_sourcePlug; + ScenePlug::ScenePath m_sourcePath; + +}; + +////////////////////////////////////////////////////////////////////////// +// Parent node +////////////////////////////////////////////////////////////////////////// + GAFFER_NODE_DEFINE_TYPE( Parent ); size_t Parent::g_firstPlugIndex = 0; @@ -55,6 +134,7 @@ Parent::Parent( const std::string &name ) { storeIndexOfNextChild( g_firstPlugIndex ); addChild( new ArrayPlug( "children", Plug::In, new ScenePlug( "child0" ) ) ); + addChild( new StringPlug( "parentVariable", Plug::In, "" ) ); addChild( new Gaffer::ObjectPlug( "__mapping", Gaffer::Plug::Out, IECore::NullObject::defaultNullObject() ) ); } @@ -72,14 +152,24 @@ const Gaffer::ArrayPlug *Parent::childrenPlug() const return getChild( g_firstPlugIndex ); } +Gaffer::StringPlug *Parent::parentVariablePlug() +{ + return getChild( g_firstPlugIndex + 1 ); +} + +const Gaffer::StringPlug *Parent::parentVariablePlug() const +{ + return getChild( g_firstPlugIndex + 1 ); +} + Gaffer::ObjectPlug *Parent::mappingPlug() { - return getChild( g_firstPlugIndex + 1 ); + return getChild( g_firstPlugIndex + 2 ); } const Gaffer::ObjectPlug *Parent::mappingPlug() const { - return getChild( g_firstPlugIndex + 1 ); + return getChild( g_firstPlugIndex + 2 ); } void Parent::affects( const Plug *input, AffectedPlugsContainer &outputs ) const @@ -104,7 +194,7 @@ void Parent::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *conte if( output == mappingPlug() ) { - ScenePlug::PathScope scope( context, ScenePath() ); + ScenePlug::PathScope scope( context, &g_root ); for( const auto &child : ScenePlug::Range( *childrenPlug() ) ) { child->childNamesPlug()->hash( h ); @@ -116,7 +206,7 @@ void Parent::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context { if( output == mappingPlug() ) { - ScenePlug::PathScope scope( context, ScenePath() ); + ScenePlug::PathScope scope( context, &g_root ); vector childNames; for( const auto &child : ScenePlug::Range( *childrenPlug() ) ) { @@ -131,15 +221,17 @@ void Parent::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context bool Parent::affectsBranchBound( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->boundPlug()->getName() ); + return affectsSourceScope( input ) || isChildrenPlug( input, inPlug()->boundPlug()->getName() ); } -void Parent::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 0 ) { - BranchCreator::hashBranchBound( parentPath, branchPath, context, h ); - ScenePlug::PathScope scope( context, ScenePath() ); + BranchCreator::hashBranchBound( sourcePath, branchPath, context, h ); + ParentScope s( this, sourcePath, context ); + s.set( ScenePlug::scenePathContextName, &g_root ); + for( auto &p : ScenePlug::Range( *childrenPlug() ) ) { p->boundPlug()->hash( h ); @@ -148,13 +240,12 @@ void Parent::hashBranchBound( const ScenePath &parentPath, const ScenePath &bran else { // pass through - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - h = sourcePlug->boundHash( source ); + SourceScope s( this, sourcePath, branchPath, context ); + h = s.sourcePlug()->boundPlug()->hash(); } } -Imath::Box3f Parent::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::Box3f Parent::computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { @@ -162,135 +253,132 @@ Imath::Box3f Parent::computeBranchBound( const ScenePath &parentPath, const Scen // inside a branch ( at the top level, it assumes it needs to just merge all the child bounds anyway ). // Perhaps in the future, some of the use cases of BranchCreator could be optimized if we changed it so // it did use this path. + ParentScope s( this, sourcePath, context ); + s.set( ScenePlug::scenePathContextName, &g_root ); + Box3f combinedBound; for( auto &p : ScenePlug::Range( *childrenPlug() ) ) { // we don't need to transform these bounds, because the SceneNode // guarantees that the transform for root nodes is always identity. - Box3f bound = p->bound( ScenePath() ); - combinedBound.extendBy( bound ); + combinedBound.extendBy( p->boundPlug()->getValue() ); } return combinedBound; } else { // pass through - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - return sourcePlug->bound( source ); + SourceScope s( this, sourcePath, branchPath, context ); + return s.sourcePlug()->boundPlug()->getValue(); } } bool Parent::affectsBranchTransform( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->transformPlug()->getName() ); + return affectsSourceScope( input ) || isChildrenPlug( input, inPlug()->transformPlug()->getName() ); } -void Parent::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - h = sourcePlug->transformHash( source ); + SourceScope s( this, sourcePath, branchPath, context ); + h = s.sourcePlug()->transformPlug()->hash(); } -Imath::M44f Parent::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::M44f Parent::computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - return sourcePlug->transform( source ); + SourceScope s( this, sourcePath, branchPath, context ); + return s.sourcePlug()->transformPlug()->getValue(); } bool Parent::affectsBranchAttributes( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->attributesPlug()->getName() ); + return affectsSourceScope( input ) || isChildrenPlug( input, inPlug()->attributesPlug()->getName() ); } -void Parent::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - h = sourcePlug->attributesHash( source ); + SourceScope s( this, sourcePath, branchPath, context ); + h = s.sourcePlug()->attributesPlug()->hash(); } -IECore::ConstCompoundObjectPtr Parent::computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstCompoundObjectPtr Parent::computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - return sourcePlug->attributes( source ); + SourceScope s( this, sourcePath, branchPath, context ); + return s.sourcePlug()->attributesPlug()->getValue(); } bool Parent::affectsBranchObject( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->objectPlug()->getName() ); + return affectsSourceScope( input ) || isChildrenPlug( input, inPlug()->objectPlug()->getName() ); } -void Parent::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - h = sourcePlug->objectHash( source ); + SourceScope s( this, sourcePath, branchPath, context ); + h = s.sourcePlug()->objectPlug()->hash(); } -IECore::ConstObjectPtr Parent::computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstObjectPtr Parent::computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - return sourcePlug->object( source ); + SourceScope s( this, sourcePath, branchPath, context ); + return s.sourcePlug()->objectPlug()->getValue(); } bool Parent::affectsBranchChildNames( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->childNamesPlug()->getName() ); + return affectsSourceScope( input ) || isChildrenPlug( input, inPlug()->childNamesPlug()->getName() ); } -void Parent::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 0 ) { - BranchCreator::hashBranchChildNames( parentPath, branchPath, context, h ); - ScenePlug::GlobalScope s( context ); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); + ParentScope s( this, sourcePath, context ); mappingPlug()->hash( h ); } else { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - h = sourcePlug->childNamesHash( source ); + SourceScope s( this, sourcePath, branchPath, context ); + h = s.sourcePlug()->childNamesPlug()->hash(); } } -IECore::ConstInternedStringVectorDataPtr Parent::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Parent::computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { - ScenePlug::GlobalScope s( context ); + ParentScope s( this, sourcePath, context ); Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); return mapping->outputChildNames(); } else { - const ScenePlug *sourcePlug = nullptr; - ScenePath source = sourcePath( branchPath, &sourcePlug ); - return sourcePlug->childNames( source ); + SourceScope s( this, sourcePath, branchPath, context ); + return s.sourcePlug()->childNamesPlug()->getValue(); } } bool Parent::affectsBranchSetNames( const Gaffer::Plug *input ) const { - return isChildrenPlug( input, inPlug()->setNamesPlug()->getName() ); + return affectsParentScope( input ) || isChildrenPlug( input, inPlug()->setNamesPlug()->getName() ); } -void Parent::hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchSetNames( parentPath, context, h ); + BranchCreator::hashBranchSetNames( sourcePath, context, h ); + + ParentScope s( this, sourcePath, context ); for( auto &p : ScenePlug::Range( *childrenPlug() ) ) { p->setNamesPlug()->hash( h ); } } -IECore::ConstInternedStringVectorDataPtr Parent::computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Parent::computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { + ParentScope s( this, sourcePath, context ); + InternedStringVectorDataPtr resultData = new InternedStringVectorData; vector &result = resultData->writable(); for( auto &p : ScenePlug::Range( *childrenPlug() ) ) @@ -311,33 +399,44 @@ IECore::ConstInternedStringVectorDataPtr Parent::computeBranchSetNames( const Sc return resultData; } +bool Parent::constantBranchSetNames() const +{ + return parentVariablePlug()->isSetToDefault() || parentVariablePlug()->getValue().empty(); +} + bool Parent::affectsBranchSet( const Gaffer::Plug *input ) const { - return input == mappingPlug() || isChildrenPlug( input, inPlug()->setPlug()->getName() ); + return affectsParentScope( input ) || isChildrenPlug( input, inPlug()->setPlug()->getName() ); } -void Parent::hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Parent::hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchSet( parentPath, setName, context, h ); + BranchCreator::hashBranchSet( sourcePath, setName, context, h ); + + ParentScope s( this, sourcePath, context ); + s.set( ScenePlug::setNameContextName, &setName ); for( auto &p : ScenePlug::Range( *childrenPlug() ) ) { p->setPlug()->hash( h ); } - ScenePlug::GlobalScope s( context ); + s.remove( ScenePlug::setNameContextName ); mappingPlug()->hash( h ); } -IECore::ConstPathMatcherDataPtr Parent::computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const +IECore::ConstPathMatcherDataPtr Parent::computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const { + ParentScope s( this, sourcePath, context ); + s.set( ScenePlug::setNameContextName, &setName ); + vector inputSets; inputSets.reserve( childrenPlug()->children().size() ); for( auto &p : ScenePlug::Range( *childrenPlug() ) ) { inputSets.push_back( p->setPlug()->getValue() ); } - ScenePlug::GlobalScope s( context ); + s.remove( ScenePlug::setNameContextName ); Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); PathMatcherDataPtr resultData = new PathMatcherData; @@ -346,6 +445,16 @@ IECore::ConstPathMatcherDataPtr Parent::computeBranchSet( const ScenePath &paren return resultData; } +bool Parent::affectsParentScope( const Gaffer::Plug *input ) const +{ + return input == parentVariablePlug(); +} + +bool Parent::affectsSourceScope( const Gaffer::Plug *input ) const +{ + return affectsParentScope( input ) || input == mappingPlug(); +} + bool Parent::isChildrenPlug( const Gaffer::Plug *input, const IECore::InternedString &scenePlugChildName ) const { const auto scene = input->parent(); @@ -356,18 +465,3 @@ bool Parent::isChildrenPlug( const Gaffer::Plug *input, const IECore::InternedSt return input->getName() == scenePlugChildName; } - -SceneNode::ScenePath Parent::sourcePath( const ScenePath &branchPath, const ScenePlug **source ) const -{ - ScenePlug::GlobalScope s( Context::current() ); - Private::ConstChildNamesMapPtr mapping = boost::static_pointer_cast( mappingPlug()->getValue() ); - - const Private::ChildNamesMap::Input &input = mapping->input( branchPath[0] ); - *source = childrenPlug()->getChild( input.index ); - - ScenePath result; - result.reserve( branchPath.size() ); - result.push_back( input.name ); - result.insert( result.end(), branchPath.begin() + 1, branchPath.end() ); - return result; -} diff --git a/src/GafferScene/PathFilter.cpp b/src/GafferScene/PathFilter.cpp index 9feb65fb156..de316f08970 100644 --- a/src/GafferScene/PathFilter.cpp +++ b/src/GafferScene/PathFilter.cpp @@ -201,10 +201,9 @@ void PathFilter::compute( Gaffer::ValuePlug *output, const Gaffer::Context *cont void PathFilter::hashMatch( const ScenePlug *scene, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - typedef IECore::TypedData ScenePathData; - const ScenePathData *pathData = context->get( ScenePlug::scenePathContextName, nullptr ); + const ScenePlug::ScenePath *path = context->getIfExists( ScenePlug::scenePathContextName ); - if( !pathData ) + if( !path ) { // This is a special case used by the Prune and Isolate nodes // to request a hash representing the effects of the filter @@ -229,8 +228,7 @@ void PathFilter::hashMatch( const ScenePlug *scene, const Gaffer::Context *conte // Standard case - const ScenePlug::ScenePath &path = pathData->readable(); - h.append( path.data(), path.size() ); + h.append( path->data(), path->size() ); if( m_pathMatcher ) { @@ -285,7 +283,8 @@ void PathFilter::hashRootSizes( const Gaffer::Context *context, IECore::MurmurHa const ScenePlug::ScenePath &path = context->get( ScenePlug::scenePathContextName ); if( path.size() ) { - ScenePlug::PathScope parentScope( context, ScenePlug::ScenePath( path.begin(), path.begin() + path.size() - 1 ) ); + ScenePlug::ScenePath parentPath( path.begin(), path.begin() + path.size() - 1 ); + ScenePlug::PathScope parentScope( context, &parentPath ); rootSizesPlug()->hash( h ); } rootsPlug()->hash( h ); @@ -301,7 +300,8 @@ ConstIntVectorDataPtr PathFilter::computeRootSizes( const Gaffer::Context *conte ConstIntVectorDataPtr parentRootSizes; if( path.size() ) { - ScenePlug::PathScope parentScope( context, ScenePlug::ScenePath( path.begin(), path.begin() + path.size() - 1 ) ); + ScenePlug::ScenePath parentPath( path.begin(), path.begin() + path.size() - 1 ); + ScenePlug::PathScope parentScope( context, &parentPath ); parentRootSizes = rootSizesPlug()->getValue(); // If the parent has no descendant roots, then we already have // all the roots we need. diff --git a/src/GafferScene/Plane.cpp b/src/GafferScene/Plane.cpp index a1de8e29a2a..39fc79c8f1c 100644 --- a/src/GafferScene/Plane.cpp +++ b/src/GafferScene/Plane.cpp @@ -100,5 +100,5 @@ void Plane::hashSource( const Gaffer::Context *context, IECore::MurmurHash &h ) IECore::ConstObjectPtr Plane::computeSource( const Context *context ) const { V2f dimensions = dimensionsPlug()->getValue(); - return MeshPrimitive::createPlane( Box2f( -dimensions / 2.0f, dimensions / 2.0f ), divisionsPlug()->getValue() ); + return MeshPrimitive::createPlane( Box2f( -dimensions / 2.0f, dimensions / 2.0f ), divisionsPlug()->getValue(), context->canceller() ); } diff --git a/src/GafferScene/PrimitiveSampler.cpp b/src/GafferScene/PrimitiveSampler.cpp index 83e6e01f7b2..461e3e8db40 100644 --- a/src/GafferScene/PrimitiveSampler.cpp +++ b/src/GafferScene/PrimitiveSampler.cpp @@ -38,8 +38,6 @@ #include "GafferScene/SceneAlgo.h" -#include "Gaffer/Private/IECorePreview/ParallelAlgo.h" - #include "IECoreScene/MeshAlgo.h" #include "IECoreScene/MeshPrimitive.h" #include "IECoreScene/PrimitiveEvaluator.h" @@ -311,7 +309,7 @@ IECore::ConstObjectPtr PrimitiveSampler::computeProcessedObject( const ScenePath ConstPrimitivePtr preprocessedSourcePrimitive = sourcePrimitive; if( auto mesh = runTimeCast( preprocessedSourcePrimitive.get() ) ) { - preprocessedSourcePrimitive = MeshAlgo::triangulate( mesh ); + preprocessedSourcePrimitive = MeshAlgo::triangulate( mesh, context->canceller() ); } PrimitiveEvaluatorPtr evaluator = PrimitiveEvaluator::create( preprocessedSourcePrimitive ); if( !evaluator ) @@ -398,4 +396,3 @@ bool PrimitiveSampler::affectsSamplingFunction( const Gaffer::Plug *input ) cons void PrimitiveSampler::hashSamplingFunction( IECore::MurmurHash &h ) const { } - diff --git a/src/GafferScene/PrimitiveVariableExists.cpp b/src/GafferScene/PrimitiveVariableExists.cpp index e5383d19f82..c8b9acd80b6 100644 --- a/src/GafferScene/PrimitiveVariableExists.cpp +++ b/src/GafferScene/PrimitiveVariableExists.cpp @@ -108,7 +108,7 @@ void PrimitiveVariableExists::hash( const ValuePlug *output, const Context *cont ComputeNode::hash( output, context, h ); if( output == outPlug() ) { - if( context->get( ScenePlug::scenePathContextName, nullptr ) ) + if( context->getIfExists< ScenePlug::ScenePath >( ScenePlug::scenePathContextName ) ) { h.append( primitiveVariablePlug()->hash() ); h.append( inPlug()->objectPlug()->hash() ); @@ -125,7 +125,7 @@ void PrimitiveVariableExists::compute( ValuePlug *output, const Context *context if( output == outPlug() ) { bool exists = false; - if( context->get( ScenePlug::scenePathContextName, nullptr ) ) + if( context->getIfExists< ScenePlug::ScenePath >( ScenePlug::scenePathContextName ) ) { ConstObjectPtr inObject = inPlug()->objectPlug()->getValue(); diff --git a/src/GafferScene/PrimitiveVariables.cpp b/src/GafferScene/PrimitiveVariables.cpp index 946fa70e3f7..2dae5d54c47 100644 --- a/src/GafferScene/PrimitiveVariables.cpp +++ b/src/GafferScene/PrimitiveVariables.cpp @@ -106,7 +106,7 @@ IECore::ConstObjectPtr PrimitiveVariables::computeProcessedObject( const ScenePa PrimitivePtr result = inputPrimitive->copy(); std::string name; - for( NameValuePlugIterator it( p ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( p ); !it.done(); ++it ) { IECore::DataPtr d = p->memberDataAndName( it->get(), name ); if( d ) diff --git a/src/GafferScene/Prune.cpp b/src/GafferScene/Prune.cpp index 6214bd1e859..8ecd7d8eb65 100644 --- a/src/GafferScene/Prune.cpp +++ b/src/GafferScene/Prune.cpp @@ -178,7 +178,7 @@ void Prune::hashChildNames( const ScenePath &path, const Gaffer::Context *contex for( vector::const_iterator it = inputChildNames.begin(), eIt = inputChildNames.end(); it != eIt; ++it ) { childPath[path.size()] = *it; - sceneScope.set( ScenePlug::scenePathContextName, childPath ); + sceneScope.set( ScenePlug::scenePathContextName, &childPath ); filterPlug()->hash( h ); } } @@ -213,7 +213,7 @@ IECore::ConstInternedStringVectorDataPtr Prune::computeChildNames( const ScenePa for( vector::const_iterator it = inputChildNames.begin(), eIt = inputChildNames.end(); it != eIt; ++it ) { childPath[path.size()] = *it; - sceneScope.set( ScenePlug::scenePathContextName, childPath ); + sceneScope.set( ScenePlug::scenePathContextName, &childPath ); if( !(filterPlug()->getValue() & IECore::PathMatcher::ExactMatch) ) { outputChildNames.push_back( *it ); @@ -268,7 +268,7 @@ IECore::ConstPathMatcherDataPtr Prune::computeSet( const IECore::InternedString for( PathMatcher::RawIterator pIt = inputSet.begin(), peIt = inputSet.end(); pIt != peIt; ) { - sceneScope.set( ScenePlug::scenePathContextName, *pIt ); + sceneScope.set( ScenePlug::scenePathContextName, &(*pIt) ); const int m = filterPlug()->getValue(); if( m & ( IECore::PathMatcher::ExactMatch | IECore::PathMatcher::AncestorMatch ) ) { diff --git a/src/GafferScene/Render.cpp b/src/GafferScene/Render.cpp index 19a41ade755..2189780821d 100644 --- a/src/GafferScene/Render.cpp +++ b/src/GafferScene/Render.cpp @@ -37,7 +37,8 @@ #include "GafferScene/Render.h" #include "GafferScene/Private/IECoreScenePreview/Renderer.h" -#include "GafferScene/RendererAlgo.h" +#include "GafferScene/Private/RendererAlgo.h" +#include "GafferScene/SceneAlgo.h" #include "GafferScene/SceneNode.h" #include "GafferScene/ScenePlug.h" #include "GafferScene/SceneProcessor.h" @@ -69,9 +70,9 @@ struct RenderScope : public Context::EditableScope RenderScope( const Context *context ) : EditableScope( context ), m_sceneTranslationOnly( false ) { - if( auto d = context->get( g_sceneTranslationOnlyContextName, nullptr ) ) + if( const bool *d = context->getIfExists( g_sceneTranslationOnlyContextName ) ) { - m_sceneTranslationOnly = d->readable(); + m_sceneTranslationOnly = *d; // Don't leak variable upstream. remove( g_sceneTranslationOnlyContextName ); } @@ -112,7 +113,7 @@ Render::Render( const IECore::InternedString &rendererType, const std::string &n addChild( new ScenePlug( "out", Plug::Out, Plug::Default & ~Plug::Serialisable ) ); addChild( new ScenePlug( "__adaptedIn", Plug::In, Plug::Default & ~Plug::Serialisable ) ); - SceneProcessorPtr adaptors = GafferScene::RendererAlgo::createAdaptors(); + SceneProcessorPtr adaptors = GafferScene::SceneAlgo::createRenderAdaptors(); setChild( "__adaptors", adaptors ); adaptors->inPlug()->setInput( inPlug() ); adaptedInPlug()->setInput( adaptors->outPlug() ); @@ -230,6 +231,27 @@ IECore::MurmurHash Render::hash( const Gaffer::Context *context ) const } void Render::execute() const +{ + executeInternal( /* flushCaches = */ true ); +} + +void Render::executeSequence( const std::vector &frames ) const +{ + Context::EditableScope frameScope( Context::current() ); + + for( auto frame : frames ) + { + frameScope.setFrame( frame ); + // We don't flush Gaffer's caches when rendering batches of frames, + // because that would mean starting scene generation from scratch + // each time. We assume that if renders have been batched, they are + // lightweight in the first place (otherwise there is little benefit + // in sharing the startup cost between several of them). + executeInternal( /* flushCaches = */ frames.size() == 1 ); + } +} + +void Render::executeInternal( bool flushCaches ) const { if( inPlug()->source()->direction() != Plug::Out ) { @@ -244,7 +266,7 @@ void Render::execute() const return; } - renderScope.set( g_rendererContextName, rendererType ); + renderScope.set( g_rendererContextName, &rendererType ); const Mode mode = static_cast( modePlug()->getValue() ); const std::string fileName = fileNamePlug()->getValue(); @@ -278,7 +300,7 @@ void Render::execute() const ConstCompoundObjectPtr globals = adaptedInPlug()->globalsPlug()->getValue(); if( !renderScope.sceneTranslationOnly() ) { - GafferScene::RendererAlgo::createOutputDirectories( globals.get() ); + GafferScene::Private::RendererAlgo::createOutputDirectories( globals.get() ); } PerformanceMonitorPtr performanceMonitor; @@ -291,20 +313,20 @@ void Render::execute() const } Monitor::Scope performanceMonitorScope( performanceMonitor ); - RendererAlgo::outputOptions( globals.get(), renderer.get() ); - RendererAlgo::outputOutputs( inPlug(), globals.get(), renderer.get() ); + GafferScene::Private::RendererAlgo::outputOptions( globals.get(), renderer.get() ); + GafferScene::Private::RendererAlgo::outputOutputs( inPlug(), globals.get(), renderer.get() ); { // Using nested scope so that we free the memory used by `renderSets` // and `lightLinks` before we call `render()`. - RendererAlgo::RenderSets renderSets( adaptedInPlug() ); - RendererAlgo::LightLinks lightLinks; + GafferScene::Private::RendererAlgo::RenderSets renderSets( adaptedInPlug() ); + GafferScene::Private::RendererAlgo::LightLinks lightLinks; - RendererAlgo::outputCameras( adaptedInPlug(), globals.get(), renderSets, renderer.get() ); - RendererAlgo::outputLights( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); - RendererAlgo::outputLightFilters( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputCameras( adaptedInPlug(), globals.get(), renderSets, renderer.get() ); + GafferScene::Private::RendererAlgo::outputLights( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputLightFilters( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); lightLinks.outputLightFilterLinks( adaptedInPlug() ); - RendererAlgo::outputObjects( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); + GafferScene::Private::RendererAlgo::outputObjects( adaptedInPlug(), globals.get(), renderSets, &lightLinks, renderer.get() ); } if( renderScope.sceneTranslationOnly() ) @@ -312,17 +334,17 @@ void Render::execute() const return; } - // Now we have generated the scene, flush Cortex and Gaffer caches to - // provide more memory to the renderer. - /// \todo This is not ideal. If dispatch is batched then multiple - /// renders in the same process might actually benefit from sharing - /// the cache. And if executing directly within the gui app - /// flushing the caches is definitely not wanted. Since these - /// scenarios are currently uncommon, we prioritise the common - /// case of performing a single render from within `gaffer execute`, - /// but it would be good to do better. - ObjectPool::defaultObjectPool()->clear(); - ValuePlug::clearCache(); + if( flushCaches ) + { + // Now we have generated the scene, flush Cortex and Gaffer caches to + // provide more memory to the renderer. + /// \todo If executing directly within the gui app flushing the caches + /// is definitely not wanted. Since this scenario is currently uncommon, + /// we prioritise the common case of performing a single render from within + /// `gaffer execute`, but it would be good to do better. + ObjectPool::defaultObjectPool()->clear(); + ValuePlug::clearCache(); + } renderer->render(); renderer.reset(); diff --git a/src/GafferScene/RenderController.cpp b/src/GafferScene/RenderController.cpp index f2a9ca3511e..913ddfc2ac2 100644 --- a/src/GafferScene/RenderController.cpp +++ b/src/GafferScene/RenderController.cpp @@ -44,12 +44,12 @@ #include "IECoreScene/Transform.h" #include "IECoreScene/VisibleRenderable.h" +#include "IECore/Interpolator.h" #include "IECore/NullObject.h" #include "boost/algorithm/string/predicate.hpp" #include "boost/bind.hpp" #include "boost/container/flat_set.hpp" -#include "boost/make_unique.hpp" #include "tbb/task.h" @@ -59,7 +59,7 @@ using namespace IECore; using namespace IECoreScene; using namespace Gaffer; using namespace GafferScene; -using namespace GafferScene::RendererAlgo; +using namespace GafferScene::Private::RendererAlgo; ////////////////////////////////////////////////////////////////////////// // Private utilities @@ -68,13 +68,17 @@ using namespace GafferScene::RendererAlgo; namespace { -InternedString g_openGLRendererName( "OpenGL" ); +const InternedString g_openGLRendererName( "OpenGL" ); -InternedString g_cameraGlobalName( "option:render:camera" ); +// Copied from RendererAlgo.cpp -InternedString g_visibleAttributeName( "scene:visible" ); -InternedString g_setsAttributeName( "sets" ); -InternedString g_rendererContextName( "scene:renderer" ); +const InternedString g_cameraGlobalName( "option:render:camera" ); +const InternedString g_transformBlurOptionName( "option:render:transformBlur" ); +const InternedString g_deformationBlurOptionName( "option:render:deformationBlur" ); + +const InternedString g_visibleAttributeName( "scene:visible" ); +const InternedString g_setsAttributeName( "sets" ); +const InternedString g_rendererContextName( "scene:renderer" ); bool visible( const CompoundObject *attributes ) { @@ -90,8 +94,8 @@ bool cameraGlobalsChanged( const CompoundObject *globals, const CompoundObject * } CameraPtr camera1 = new Camera; CameraPtr camera2 = new Camera; - RendererAlgo::applyCameraGlobals( camera1.get(), globals, scene ); - RendererAlgo::applyCameraGlobals( camera2.get(), previousGlobals, scene ); + SceneAlgo::applyCameraGlobals( camera1.get(), globals, scene ); + SceneAlgo::applyCameraGlobals( camera2.get(), previousGlobals, scene ); return *camera1 != *camera2; } @@ -317,6 +321,23 @@ class RenderController::SceneGraph m_changedComponents |= AttributesComponent; } } + + // If attributes have changed, need to check if this has affected our motion sample times + if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & TransformBlurGlobalComponent ) ) + { + if( Private::RendererAlgo::transformMotionTimes( controller->m_motionBlurOptions.transformBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_transformTimes ) ) + { + m_dirtyComponents |= TransformComponent; + } + } + + if( ( m_changedComponents & AttributesComponent ) || ( changedGlobals & DeformationBlurGlobalComponent ) ) + { + if( Private::RendererAlgo::deformationMotionTimes( controller->m_motionBlurOptions.deformationBlur, controller->m_motionBlurOptions.shutter, m_fullAttributes.get(), m_deformationTimes ) ) + { + m_dirtyComponents |= ObjectComponent; + } + } } if( !::visible( m_fullAttributes.get() ) ) @@ -345,7 +366,7 @@ class RenderController::SceneGraph const bool parentTransformChanged = m_parent && ( m_parent->m_changedComponents & TransformComponent ); if( ( m_dirtyComponents & TransformComponent ) || parentTransformChanged ) { - if( updateTransform( controller->m_scene->transformPlug(), parentTransformChanged ) ) + if( updateTransform( controller->m_scene->transformPlug(), parentTransformChanged, controller->m_motionBlurOptions ) ) { m_changedComponents |= TransformComponent; } @@ -355,7 +376,7 @@ class RenderController::SceneGraph // Object - if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( ( m_dirtyComponents & ObjectComponent ) && updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get(), controller->m_motionBlurOptions ) ) { m_changedComponents |= ObjectComponent; } @@ -379,7 +400,7 @@ class RenderController::SceneGraph { // Failed to apply attributes - must replace entire object. m_objectHash = MurmurHash(); - if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get() ) ) + if( updateObject( controller->m_scene->objectPlug(), type, controller->m_renderer.get(), controller->m_globals.get(), controller->m_scene.get(), controller->m_lightLinks.get(), controller->m_motionBlurOptions ) ) { m_changedComponents |= ObjectComponent; controller->m_failedAttributeEdits++; @@ -392,7 +413,15 @@ class RenderController::SceneGraph // the apply the transform. if( m_changedComponents & ( ObjectComponent | TransformComponent ) ) { - m_objectInterface->transform( m_fullTransform ); + assert( m_fullTransform.size() ); + if( !m_transformTimesOutput ) + { + m_objectInterface->transform( m_fullTransform[0] ); + } + else + { + m_objectInterface->transform( m_fullTransform, *m_transformTimesOutput ); + } } if( type == ObjectType && controller->m_lightLinks ) @@ -423,6 +452,7 @@ class RenderController::SceneGraph m_changedComponents |= ExpansionComponent; } + bool newBound = false; if( ( m_changedComponents & ( ExpansionComponent | ChildNamesComponent ) ) || ( m_dirtyComponents & BoundComponent ) @@ -452,7 +482,7 @@ class RenderController::SceneGraph m_boundInterface = controller->m_renderer->object( boundName, boundCurves.get(), controller->m_boundAttributes.get() ); if( m_boundInterface ) { - m_boundInterface->transform( m_fullTransform ); + newBound = true; } } else @@ -460,10 +490,19 @@ class RenderController::SceneGraph m_boundInterface = nullptr; } } - else if( m_boundInterface && ( m_changedComponents & TransformComponent ) ) + + if( newBound || ( m_boundInterface && ( m_changedComponents & TransformComponent ) ) ) { - // Apply new transform to existing bounding box - m_boundInterface->transform( m_fullTransform ); + // Apply transform to bounding box + assert( m_fullTransform.size() ); + if( !m_transformTimesOutput ) + { + m_boundInterface->transform( m_fullTransform[0] ); + } + else + { + m_boundInterface->transform( m_fullTransform, *m_transformTimesOutput ); + } } clean( ExpansionComponent | BoundComponent ); @@ -545,7 +584,6 @@ class RenderController::SceneGraph m_attributesInterface = nullptr; // Will be updated lazily in attributesInterface() m_attributesHash = attributesHash; - return true; } @@ -578,7 +616,7 @@ class RenderController::SceneGraph return true; } - bool updateRenderSets( const ScenePlug::ScenePath &path, const RendererAlgo::RenderSets &renderSets ) + bool updateRenderSets( const ScenePlug::ScenePath &path, const Private::RendererAlgo::RenderSets &renderSets ) { m_fullAttributes->members()[g_setsAttributeName] = boost::const_pointer_cast( renderSets.setsAttribute( path ) @@ -597,30 +635,54 @@ class RenderController::SceneGraph } // Returns true if the transform changed. - bool updateTransform( const M44fPlug *transformPlug, bool parentTransformChanged ) + bool updateTransform( const M44fPlug *transformPlug, bool parentTransformChanged, const MotionBlurOptions &motionBlurOptions ) { - const IECore::MurmurHash transformHash = transformPlug->hash(); - if( transformHash == m_transformHash && !parentTransformChanged ) + if( parentTransformChanged ) + { + // We don't store the local transform - if the parent has changed, wipe the hash + // to ensure that we recompute the local transform so we can redo the concatenation + m_transformHash = IECore::MurmurHash(); + } + + vector samples; + if( !Private::RendererAlgo::transformSamples( transformPlug, m_transformTimes, samples, &m_transformHash ) ) { return false; } - const M44f transform = transformPlug->getValue( &transformHash ); - if( m_parent ) + if( !m_parent ) { - m_fullTransform = transform * m_parent->m_fullTransform; + m_fullTransform = samples; + m_transformTimesOutput = samples.size() > 1 ? &m_transformTimes : nullptr; } else { - m_fullTransform = transform; - } + m_fullTransform.clear(); + if( samples.size() == 1 ) + { + m_fullTransform.reserve( m_parent->m_fullTransform.size() ); + for( const M44f& it : m_parent->m_fullTransform ) + { + m_fullTransform.push_back( samples.front() * it ); + } + m_transformTimesOutput = m_parent->m_transformTimesOutput; + } + else + { + m_transformTimesOutput = &m_transformTimes; + m_fullTransform.reserve( samples.size() ); - m_transformHash = transformHash; + for( size_t i = 0; i < samples.size(); i++ ) + { + m_fullTransform.push_back( samples[i] * m_parent->fullTransform( m_transformTimes[i] ) ); + } + } + } return true; } // Returns true if the object changed. - bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const ScenePlug *scene, LightLinks *lightLinks ) + bool updateObject( const ObjectPlug *objectPlug, Type type, IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const ScenePlug *scene, LightLinks *lightLinks, const MotionBlurOptions &motionBlurOptions ) { const bool hadObjectInterface = static_cast( m_objectInterface ); if( type == NoType ) @@ -629,17 +691,83 @@ class RenderController::SceneGraph return hadObjectInterface; } - const IECore::MurmurHash objectHash = objectPlug->hash(); - if( objectHash == m_objectHash ) + // Types that don't support motion blur + if( type == LightType || type == LightFilterType ) + { + const IECore::MurmurHash objectHash = objectPlug->hash(); + if( objectHash == m_objectHash ) + { + return false; + } + + IECore::ConstObjectPtr object = objectPlug->getValue( &objectHash ); + m_objectHash = objectHash; + + const IECore::NullObject *nullObject = runTimeCast( object.get() ); + if( renderer->name() != g_openGLRendererName ) + { + m_objectInterface = nullptr; + } + + std::string name; + ScenePlug::pathToString( Context::current()->get >( ScenePlug::scenePathContextName ), name ); + if( type == LightType ) + { + auto light = renderer->light( name, nullObject ? nullptr : object.get(), attributesInterface( renderer ) ); + if( light && lightLinks ) + { + lightLinks->addLight( name, light ); + m_objectInterface.assign( + light, + [name, lightLinks]() { + lightLinks->removeLight( name ); + } + ); + } + else + { + m_objectInterface = light; + } + } + else + { + auto lightFilter = renderer->lightFilter( name, nullObject ? nullptr : object.get(), attributesInterface( renderer ) ); + if( lightFilter && lightLinks ) + { + lightLinks->addLightFilter( lightFilter, m_fullAttributes.get() ); + m_objectInterface.assign( + lightFilter, + [lightFilter, lightLinks]() { + lightLinks->removeLightFilter( lightFilter ); + } + ); + } + else + { + m_objectInterface = lightFilter; + } + } + + return true; + + } + + vector samples; + if( !Private::RendererAlgo::objectSamples( objectPlug, m_deformationTimes, samples, &m_objectHash ) ) { return false; } - IECore::ConstObjectPtr object = objectPlug->getValue( &objectHash ); - m_objectHash = objectHash; + bool isNull = true; + for( ConstObjectPtr &i : samples ) + { + if( !runTimeCast( i.get() ) ) + { + isNull = false; + } + } - const IECore::NullObject *nullObject = runTimeCast( object.get() ); - if( (type != LightType && type != LightFilterType) && nullObject ) + if( (type != LightType && type != LightFilterType) && isNull ) { m_objectInterface = nullptr; return hadObjectInterface; @@ -671,57 +799,77 @@ class RenderController::SceneGraph ScenePlug::pathToString( Context::current()->get >( ScenePlug::scenePathContextName ), name ); if( type == CameraType ) { - if( const IECoreScene::Camera *camera = runTimeCast( object.get() ) ) - { - IECoreScene::CameraPtr cameraCopy = camera->copy(); - RendererAlgo::applyCameraGlobals( cameraCopy.get(), globals, scene ); - m_objectInterface = renderer->camera( name, cameraCopy.get(), attributesInterface( renderer ) ); - } - else + vector cameraSamples; cameraSamples.reserve( samples.size() ); + for( const auto &sample : samples ) { - m_objectInterface = nullptr; + if( auto cameraSample = runTimeCast( sample.get() ) ) + { + IECoreScene::CameraPtr cameraSampleCopy = cameraSample->copy(); + SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), globals, scene ); + cameraSamples.push_back( cameraSampleCopy ); + } } - } - else if( type == LightType ) - { - auto light = renderer->light( name, nullObject ? nullptr : object.get(), attributesInterface( renderer ) ); - if( light && lightLinks ) + + // Create ObjectInterface + + if( !samples.size() || cameraSamples.size() != samples.size() ) { - lightLinks->addLight( name, light ); - m_objectInterface.assign( - light, - [name, lightLinks]() { - lightLinks->removeLight( name ); - } + IECore::msg( + IECore::Msg::Warning, + "RenderController::updateObject", + boost::format( "Camera missing for location \"%1%\" at frame %2%" ) + % name + % Context::current()->getFrame() ); } else { - m_objectInterface = light; + if( cameraSamples.size() == 1 ) + { + m_objectInterface = renderer->camera( + name, + cameraSamples[0].get(), + attributesInterface( renderer ) + ); + } + else + { + vector rawCameraSamples; rawCameraSamples.reserve( cameraSamples.size() ); + for( auto &c : cameraSamples ) + { + rawCameraSamples.push_back( c.get() ); + } + m_objectInterface = renderer->camera( + name, + rawCameraSamples, + m_deformationTimes, + attributesInterface( renderer ) + ); + } } } - else if( type == LightFilterType ) + else { - auto lightFilter = renderer->lightFilter( name, nullObject ? nullptr : object.get(), attributesInterface( renderer ) ); - if( lightFilter && lightLinks ) + if( !samples.size() ) { - lightLinks->addLightFilter( lightFilter, m_fullAttributes.get() ); - m_objectInterface.assign( - lightFilter, - [lightFilter, lightLinks]() { - lightLinks->removeLightFilter( lightFilter ); - } - ); + return true; + } + + if( samples.size() == 1 ) + { + m_objectInterface = renderer->object( name, samples[0].get(), attributesInterface( renderer ) ); } else { - m_objectInterface = lightFilter; + /// \todo Can we rejig things so this conversion isn't necessary? + vector objectsVector; objectsVector.reserve( samples.size() ); + for( const auto &sample : samples ) + { + objectsVector.push_back( sample.get() ); + } + m_objectInterface = renderer->object( name, objectsVector, m_deformationTimes, attributesInterface( renderer ) ); } } - else - { - m_objectInterface = renderer->object( name, object.get(), attributesInterface( renderer ) ); - } return true; } @@ -824,12 +972,41 @@ class RenderController::SceneGraph m_dirtyComponents &= ~components; } + M44f fullTransform( float time ) const + { + if( m_fullTransform.empty() ) + { + return M44f(); + } + if( !m_transformTimesOutput ) + { + return m_fullTransform[0]; + } + + vector::const_iterator t1 = lower_bound( m_transformTimesOutput->begin(), m_transformTimesOutput->end(), time ); + if( t1 == m_transformTimesOutput->begin() || *t1 == time ) + { + return m_fullTransform[t1 - m_transformTimesOutput->begin()]; + } + else + { + vector::const_iterator t0 = t1 - 1; + const float l = lerpfactor( time, *t0, *t1 ); + const M44f &s0 = m_fullTransform[t0 - m_transformTimesOutput->begin()]; + const M44f &s1 = m_fullTransform[t1 - m_transformTimesOutput->begin()]; + M44f result; + LinearInterpolator()( s0, s1, l, result ); + return result; + } + } + IECore::InternedString m_name; const SceneGraph *m_parent; IECore::MurmurHash m_objectHash; ObjectInterfaceHandle m_objectInterface; + std::vector m_deformationTimes; IECore::MurmurHash m_attributesHash; IECore::CompoundObjectPtr m_fullAttributes; @@ -837,7 +1014,13 @@ class RenderController::SceneGraph IECore::MurmurHash m_lightLinksHash; IECore::MurmurHash m_transformHash; - Imath::M44f m_fullTransform; + std::vector m_fullTransform; + std::vector m_transformTimes; + + // The m_transformTimes represents what times we sample the transform at. The actual + // times we output at may differ due to the transform samples turning out to not vary, + // or inheriting parent samples + std::vector *m_transformTimesOutput; IECore::MurmurHash m_childNamesHash; std::vector> m_children; @@ -922,7 +1105,7 @@ class RenderController::SceneGraphUpdateTask : public tbb::task // Set up a context to compute the scene at the right // location. - ScenePlug::PathScope pathScope( m_threadState, m_scenePath ); + ScenePlug::PathScope pathScope( m_threadState, &m_scenePath ); // Update the scene graph at this location. @@ -1030,6 +1213,7 @@ RenderController::RenderController( const ConstScenePlugPtr &scene, const Gaffer m_updateRequested( false ), m_failedAttributeEdits( 0 ), m_dirtyGlobalComponents( NoGlobalComponent ), + m_changedGlobalComponents( NoGlobalComponent ), m_globals( new CompoundObject ) { for( int i = SceneGraph::FirstType; i <= SceneGraph::LastType; ++i ) @@ -1041,7 +1225,7 @@ RenderController::RenderController( const ConstScenePlugPtr &scene, const Gaffer { // We avoid light linking overhead for the GL renderer, // because we know it doesn't support it. - m_lightLinks = boost::make_unique(); + m_lightLinks = std::make_unique(); } CompoundObjectPtr boundAttributes = new CompoundObject; @@ -1241,6 +1425,14 @@ void RenderController::dirtySceneGraphs( unsigned components ) { sg->dirty( components ); } + + if( components & SceneGraph::ObjectComponent ) + { + // We don't track dirtiness of different SceneGraphs separately anyway, + // so just recheck if a camera has changed a shutter override whenever + // any object is dirtied + m_changedGlobalComponents |= CameraShutterGlobalComponent; + } } void RenderController::update( const ProgressCallback &callback ) @@ -1253,7 +1445,7 @@ void RenderController::update( const ProgressCallback &callback ) m_updateRequested = false; Context::EditableScope scopedContext( m_context.get() ); - scopedContext.set( "scene:renderer", m_renderer->name().string() ); + scopedContext.set( "scene:renderer", &m_renderer->name().string() ); updateInternal( callback ); } @@ -1269,7 +1461,7 @@ std::shared_ptr RenderController::updateInBackground( co cancelBackgroundTask(); Context::EditableScope scopedContext( m_context.get() ); - scopedContext.set( "scene:renderer", m_renderer->name().string() ); + scopedContext.set( "scene:renderer", &m_renderer->name().string() ); m_backgroundTask = ParallelAlgo::callOnBackgroundThread( // Subject @@ -1294,7 +1486,7 @@ void RenderController::updateMatchingPaths( const IECore::PathMatcher &pathsToUp } Context::EditableScope scopedContext( m_context.get() ); - scopedContext.set( "scene:renderer", m_renderer->name().string() ); + scopedContext.set( "scene:renderer", &m_renderer->name().string() ); updateInternal( callback, &pathsToUpdate ); } @@ -1304,12 +1496,11 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I try { // Update globals - if( m_dirtyGlobalComponents & GlobalsGlobalComponent ) { ConstCompoundObjectPtr globals = m_scene->globalsPlug()->getValue(); - RendererAlgo::outputOptions( globals.get(), m_globals.get(), m_renderer.get() ); - RendererAlgo::outputOutputs( m_scene.get(), globals.get(), m_globals.get(), m_renderer.get() ); + Private::RendererAlgo::outputOptions( globals.get(), m_globals.get(), m_renderer.get() ); + Private::RendererAlgo::outputOutputs( m_scene.get(), globals.get(), m_globals.get(), m_renderer.get() ); if( !m_globals || *m_globals != *globals ) { m_changedGlobalComponents |= GlobalsGlobalComponent; @@ -1321,9 +1512,37 @@ void RenderController::updateInternal( const ProgressCallback &callback, const I m_globals = globals; } + // Update motion blur options + if( m_changedGlobalComponents & ( GlobalsGlobalComponent | CameraShutterGlobalComponent ) ) + { + const BoolData *transformBlurData = m_globals->member( g_transformBlurOptionName ); + bool transformBlur = transformBlurData ? transformBlurData->readable() : false; + + const BoolData *deformationBlurData = m_globals->member( g_deformationBlurOptionName ); + bool deformationBlur = deformationBlurData ? deformationBlurData->readable() : false; + + V2f shutter = SceneAlgo::shutter( m_globals.get(), m_scene.get() ); + + if( shutter != m_motionBlurOptions.shutter || transformBlur != m_motionBlurOptions.transformBlur ) + { + m_changedGlobalComponents |= TransformBlurGlobalComponent; + } + if( shutter != m_motionBlurOptions.shutter || deformationBlur != m_motionBlurOptions.deformationBlur ) + { + m_changedGlobalComponents |= DeformationBlurGlobalComponent; + } + if( shutter != m_motionBlurOptions.shutter ) + { + m_changedGlobalComponents |= CameraOptionsGlobalComponent; + } + m_motionBlurOptions.transformBlur = transformBlur; + m_motionBlurOptions.deformationBlur = deformationBlur; + m_motionBlurOptions.shutter = shutter; + } + if( m_dirtyGlobalComponents & SetsGlobalComponent ) { - if( m_renderSets.update( m_scene.get() ) & RendererAlgo::RenderSets::RenderSetsChanged ) + if( m_renderSets.update( m_scene.get() ) & Private::RendererAlgo::RenderSets::RenderSetsChanged ) { m_changedGlobalComponents |= RenderSetsGlobalComponent; } @@ -1432,7 +1651,7 @@ void RenderController::updateDefaultCamera() } CameraPtr defaultCamera = new IECoreScene::Camera; - RendererAlgo::applyCameraGlobals( defaultCamera.get(), m_globals.get(), m_scene.get() ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), m_globals.get(), m_scene.get() ); IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = m_renderer->attributes( m_scene->attributesPlug()->defaultValue() ); ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" ); m_defaultCamera = m_renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() ); diff --git a/src/GafferScene/RendererAlgo.cpp b/src/GafferScene/RendererAlgo.cpp index 1c41921b24e..91162c4ec64 100644 --- a/src/GafferScene/RendererAlgo.cpp +++ b/src/GafferScene/RendererAlgo.cpp @@ -34,7 +34,7 @@ // ////////////////////////////////////////////////////////////////////////// -#include "GafferScene/RendererAlgo.h" +#include "GafferScene/Private/RendererAlgo.h" #include "GafferScene/Private/IECoreScenePreview/Renderer.h" #include "GafferScene/SceneAlgo.h" @@ -75,30 +75,25 @@ using namespace Gaffer; using namespace GafferScene; ////////////////////////////////////////////////////////////////////////// -// Internal utilities +// RendererAlgo implementation ////////////////////////////////////////////////////////////////////////// namespace { -void motionTimes( size_t segments, const V2f &shutter, std::vector × ) -{ - times.reserve( segments + 1 ); - for( size_t i = 0; i &samples, std::vector &sampleTimes ) +bool motionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, const InternedString &attributeName, const InternedString &segmentsAttributeName, std::vector × ) { - // Static case + unsigned int segments = 0; + if( motionBlur ) + { + const BoolData *enabled = attributes->member( attributeName ); + if( !enabled || enabled->readable() ) // Default enabled if not found + { + const IntData *d = attributes->member( segmentsAttributeName ); + segments = d ? std::max( 0, d->readable() ) : 1; + } + } - if( !segments ) + bool changed = false; + if( segments == 0 ) { - samples.push_back( scene->transformPlug()->getValue() ); - return; + changed = times.size() != 0; + times.clear(); + return changed; } - // Motion case + if( times.size() != segments + 1 ) + { + changed = true; + times.resize( segments + 1 ); + } + for( size_t i = 0; i < segments + 1; ++i ) + { + float t = lerp( shutter[0], shutter[1], (float)i / (float)segments ); + if( times[i] != t ) + { + changed = true; + times[i] = t; + } + } + return changed; +} + +bool transformMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × ) +{ + return motionTimes( motionBlur, shutter, attributes, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName, times ); +} - motionTimes( segments, shutter, sampleTimes ); +bool deformationMotionTimes( bool motionBlur, const V2f &shutter, const CompoundObject *attributes, std::vector × ) +{ + return motionTimes( motionBlur, shutter, attributes, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName, times ); +} + +bool transformSamples( const M44fPlug *transformPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash ) +{ + std::vector< IECore::MurmurHash > sampleHashes; + if( !sampleTimes.size() ) + { + sampleHashes.push_back( transformPlug->hash() ); + } + else + { + Context::EditableScope timeContext( Context::current() ); + + bool moving = false; + sampleHashes.reserve( sampleTimes.size() ); + for( const float sampleTime : sampleTimes ) + { + timeContext.setFrame( sampleTime ); + IECore::MurmurHash h = transformPlug->hash(); + if( !moving && !sampleHashes.empty() && h != sampleHashes.front() ) + { + moving = true; + } + sampleHashes.push_back( h ); + } + + if( !moving ) + { + sampleHashes.resize( 1 ); + } + } + + if( hash ) + { + IECore::MurmurHash combinedHash; + if( sampleHashes.size() == 1 ) + { + combinedHash = sampleHashes[0]; + } + else + { + for( const IECore::MurmurHash &h : sampleHashes ) + { + combinedHash.append( h ); + } + } + + if( combinedHash == *hash ) + { + return false; + } + else + { + *hash = combinedHash; + } + } + + samples.clear(); + if( !sampleTimes.size() ) + { + // No shutter to sample over + samples.push_back( transformPlug->getValue( &sampleHashes[0]) ); + return true; + } Context::EditableScope timeContext( Context::current() ); + if( sampleHashes.size() == 1 ) + { + // We have a shutter, but all the samples hash the same, so just evaluate one + timeContext.setFrame( sampleTimes[0] ); + samples.push_back( transformPlug->getValue( &sampleHashes[0]) ); + return true; + } + // Motion case bool moving = false; samples.reserve( sampleTimes.size() ); - for( const float sampleTime : sampleTimes ) + for( size_t i = 0; i < sampleTimes.size(); i++ ) { - timeContext.setFrame( sampleTime ); - const M44f m = scene->transformPlug()->getValue(); + timeContext.setFrame( sampleTimes[i] ); + M44f m; + m = transformPlug->getValue( &sampleHashes[i] ); if( !moving && !samples.empty() && m != samples.front() ) { moving = true; @@ -151,20 +252,85 @@ void transformSamples( const ScenePlug *scene, size_t segments, const Imath::V2f if( !moving ) { samples.resize( 1 ); - sampleTimes.clear(); } + return true; } -void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &shutter, std::vector &samples, std::vector &sampleTimes ) +bool objectSamples( const ObjectPlug *objectPlug, const std::vector &sampleTimes, std::vector &samples, IECore::MurmurHash *hash ) { - samples.clear(); - sampleTimes.clear(); + std::vector< IECore::MurmurHash > sampleHashes; + if( !sampleTimes.size() ) + { + sampleHashes.push_back( objectPlug->hash() ); + } + else + { + const Context *frameContext = Context::current(); + Context::EditableScope timeContext( frameContext ); - // Static case + bool moving = false; + sampleHashes.reserve( sampleTimes.size() ); + for( const float sampleTime : sampleTimes ) + { + timeContext.setFrame( sampleTime ); + + const MurmurHash objectHash = objectPlug->hash(); + if( !moving && !sampleHashes.empty() && objectHash != sampleHashes.front() ) + { + moving = true; + } + sampleHashes.push_back( objectHash ); + } + + if( !moving ) + { + sampleHashes.resize( 1 ); + } + } - if( !segments ) + if( hash ) { - ConstObjectPtr object = scene->objectPlug()->getValue(); + IECore::MurmurHash combinedHash; + if( sampleHashes.size() == 1 ) + { + combinedHash = sampleHashes[0]; + } + else + { + for( const IECore::MurmurHash &h : sampleHashes ) + { + combinedHash.append( h ); + } + } + + if( combinedHash == *hash ) + { + return false; + } + else + { + *hash = combinedHash; + } + } + + // Static case + samples.clear(); + if( sampleHashes.size() == 1 ) + { + ConstObjectPtr object; + if( !sampleTimes.size() ) + { + // No shutter, just hash on frame + object = objectPlug->getValue( &sampleHashes[0]); + } + else + { + // We have a shutter, but all the samples hash the same, so just evaluate one + Context::EditableScope timeContext( Context::current() ); + timeContext.setFrame( sampleTimes[0] ); + object = objectPlug->getValue( &sampleHashes[0]); + } + if( runTimeCast( object.get() ) || runTimeCast( object.get() ) @@ -172,39 +338,28 @@ void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &s { samples.push_back( object.get() ); } - return; + + return true; } // Motion case - motionTimes( segments, shutter, sampleTimes ); - const Context *frameContext = Context::current(); Context::EditableScope timeContext( frameContext ); - bool moving = false; - MurmurHash lastHash; samples.reserve( sampleTimes.size() ); - for( const float sampleTime : sampleTimes ) + for( size_t i = 0; i < sampleTimes.size(); i++ ) { - timeContext.setFrame( sampleTime ); + timeContext.setFrame( sampleTimes[i] ); - const MurmurHash objectHash = scene->objectPlug()->hash(); - ConstObjectPtr object = scene->objectPlug()->getValue( &objectHash ); + ConstObjectPtr object = objectPlug->getValue( &sampleHashes[i] ); if( runTimeCast( object.get() ) || runTimeCast( object.get() ) ) { - // We can support multiple samples for these, so check to see - // if we actually have something moving. - if( !moving && !samples.empty() && objectHash != lastHash ) - { - moving = true; - } samples.push_back( object.get() ); - lastHash = objectHash; } else if( runTimeCast( object.get() ) ) { @@ -213,8 +368,26 @@ void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &s // open time so that non-interpolable objects appear in the right // position relative to non-blurred objects. Context::Scope frameScope( frameContext ); - objectSamples( scene, /* segments = */ 0, shutter, samples, sampleTimes ); - return; + std::vector tempTimes = {}; + + // This is a pretty weird case - we would have taken an earlier branch if the hashes + // had all matched, so it looks like this object is actual animated, despite not supporting + // animation. + // The most correct thing to do here is reset the hash, since we may not have included the + // on frame in the samples we hashed, and in theory, the on frame value could vary indepndently + // of shutter open and close. This means that an animated non-animateable object will never have + // a matching hash, and will be updated every pass. May be a performance hazard, but probably + // preferable to incorrect behaviour? Just means people need to be careful to make sure their + // heavy crowd procedurals don't have a hash that changes during the shutter? + // ( I guess in theory we could check if the on frame time is in sampleTimes, but I don't want to + // add any more special cases to this weird corner ). + // + if( hash ) + { + *hash = IECore::MurmurHash(); + } + + return objectSamples( objectPlug, tempTimes, samples ); } else { @@ -223,110 +396,15 @@ void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &s break; } } - - if( !moving ) - { - samples.resize( std::min( samples.size(), 1 ) ); - sampleTimes.clear(); - } -} - -void objectSamples( const ScenePlug *scene, size_t segments, const Imath::V2f &shutter, std::vector &samples, std::vector &sampleTimes ) -{ - vector oSamples; - vector oSampleTimes; - objectSamples( scene, segments, shutter, oSamples, oSampleTimes ); - - samples.clear(); - for( size_t i = 0; i < oSamples.size(); ++i ) - { - if( auto ts = runTimeCast( oSamples[i] ) ) - { - samples.push_back( ts ); - if( i < oSampleTimes.size() ) - { - sampleTimes.push_back( oSampleTimes[i] ); - } - } - } - - if( samples.size() < 2 ) - { - sampleTimes.clear(); - } + return true; } } // namespace RendererAlgo -} // namespace GafferScene - -////////////////////////////////////////////////////////////////////////// -// Adaptor registry -////////////////////////////////////////////////////////////////////////// - -namespace -{ - -typedef boost::container::flat_map Adaptors; - -Adaptors &adaptors() -{ - static Adaptors a; - return a; -} - -} // namespace - -namespace GafferScene -{ - -namespace RendererAlgo -{ - -void registerAdaptor( const std::string &name, Adaptor adaptor ) -{ - adaptors()[name] = adaptor; -} - -void deregisterAdaptor( const std::string &name ) -{ - adaptors().erase( name ); -} - -SceneProcessorPtr createAdaptors() -{ - SceneProcessorPtr result = new SceneProcessor; - - ScenePlug *in = result->inPlug(); - - const Adaptors &a = adaptors(); - for( Adaptors::const_iterator it = a.begin(), eIt = a.end(); it != eIt; ++it ) - { - SceneProcessorPtr adaptor = it->second(); - if( adaptor ) - { - result->addChild( adaptor ); - adaptor->inPlug()->setInput( in ); - in = adaptor->outPlug(); - } - else - { - IECore::msg( - IECore::Msg::Warning, "RendererAlgo::createAdaptors", - boost::format( "Adaptor \"%1%\" returned null" ) % it->first - ); - } - } - - result->outPlug()->setInput( in ); - return result; -} - -} // namespace RendererAlgo +} // namespace Private } // namespace GafferScene - ////////////////////////////////////////////////////////////////////////// // RenderSets class ////////////////////////////////////////////////////////////////////////// @@ -345,6 +423,9 @@ ConstInternedStringVectorDataPtr g_emptySetsAttribute = new InternedStringVector namespace GafferScene { +namespace Private +{ + namespace RendererAlgo { @@ -397,7 +478,7 @@ struct RenderSets::Updater potentialChange = LightsSetChanged; } - setScope.setSetName( n ); + setScope.setSetName( &n ); const IECore::MurmurHash &hash = m_scene->setPlug()->hash(); if( s->hash != hash ) { @@ -528,6 +609,8 @@ ConstInternedStringVectorDataPtr RenderSets::setsAttribute( const std::vectormember( g_transformBlurOptionName ); @@ -948,14 +1032,9 @@ struct LocationOutput return m_renderer; } - Imath::V2f shutter() const - { - return m_options.shutter; - } - - size_t deformationSegments() const + void deformationMotionTimes( std::vector × ) { - return motionSegments( m_options.deformationBlur, g_deformationBlurAttributeName, g_deformationBlurSegmentsAttributeName ); + GafferScene::Private::RendererAlgo::deformationMotionTimes( m_options.deformationBlur, m_options.shutter, m_attributes.get(), times ); } const IECore::CompoundObject *attributes() const @@ -992,25 +1071,6 @@ struct LocationOutput private : - size_t motionSegments( bool motionBlur, const InternedString &attributeName, const InternedString &segmentsAttributeName ) const - { - if( !motionBlur ) - { - return 0; - } - - if( const BoolData *d = m_attributes->member( attributeName ) ) - { - if( !d->readable() ) - { - return 0; - } - } - - const IntData *d = m_attributes->member( segmentsAttributeName ); - return d ? d->readable() : 1; - } - void updateAttributes( const ScenePlug *scene, const ScenePlug::ScenePath &path ) { IECore::ConstCompoundObjectPtr attributes = scene->attributesPlug()->getValue(); @@ -1039,11 +1099,12 @@ struct LocationOutput void updateTransform( const ScenePlug *scene ) { - const size_t segments = motionSegments( m_options.transformBlur, g_transformBlurAttributeName, g_transformBlurSegmentsAttributeName ); - vector samples; vector sampleTimes; - RendererAlgo::transformSamples( scene, segments, m_options.shutter, samples, sampleTimes ); + vector sampleTimes; + GafferScene::Private::RendererAlgo::transformMotionTimes( m_options.transformBlur, m_options.shutter, m_attributes.get(), sampleTimes ); + vector samples; + GafferScene::Private::RendererAlgo::transformSamples( scene->transformPlug(), sampleTimes, samples ); - if( sampleTimes.empty() ) + if( samples.size() == 1 ) { for( vector::iterator it = m_transformSamples.begin(), eIt = m_transformSamples.end(); it != eIt; ++it ) { @@ -1109,7 +1170,7 @@ struct LocationOutput Options m_options; IECore::ConstCompoundObjectPtr m_attributes; - const GafferScene::RendererAlgo::RenderSets &m_renderSets; + const GafferScene::Private::RendererAlgo::RenderSets &m_renderSets; const ScenePlug::ScenePath &m_root; std::vector m_transformSamples; @@ -1120,7 +1181,7 @@ struct LocationOutput struct CameraOutput : public LocationOutput { - CameraOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + CameraOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const ScenePlug::ScenePath &root, const ScenePlug *scene ) : LocationOutput( renderer, globals, renderSets, root, scene ), m_globals( globals ), m_cameraSet( renderSets.camerasSet() ) { } @@ -1136,9 +1197,11 @@ struct CameraOutput : public LocationOutput if( cameraMatch & IECore::PathMatcher::ExactMatch ) { // Sample cameras and apply globals + vector sampleTimes; + deformationMotionTimes( sampleTimes ); - vector samples; vector sampleTimes; - RendererAlgo::objectSamples( scene, deformationSegments(), shutter(), samples, sampleTimes ); + vector samples; + GafferScene::Private::RendererAlgo::objectSamples( scene->objectPlug(), sampleTimes, samples ); vector cameraSamples; cameraSamples.reserve( samples.size() ); for( const auto &sample : samples ) @@ -1146,7 +1209,7 @@ struct CameraOutput : public LocationOutput if( auto cameraSample = runTimeCast( sample.get() ) ) { IECoreScene::CameraPtr cameraSampleCopy = cameraSample->copy(); - GafferScene::RendererAlgo::applyCameraGlobals( cameraSampleCopy.get(), m_globals, scene ); + GafferScene::SceneAlgo::applyCameraGlobals( cameraSampleCopy.get(), m_globals, scene ); cameraSamples.push_back( cameraSampleCopy ); } } @@ -1166,7 +1229,7 @@ struct CameraOutput : public LocationOutput else { IECoreScenePreview::Renderer::ObjectInterfacePtr objectInterface; - if( !sampleTimes.size() ) + if( cameraSamples.size() == 1 ) { objectInterface = renderer()->camera( name( path ), @@ -1209,7 +1272,7 @@ struct CameraOutput : public LocationOutput struct LightOutput : public LocationOutput { - LightOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::RendererAlgo::RenderSets &renderSets, GafferScene::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + LightOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightSet( renderSets.lightsSet() ), m_lightLinks( lightLinks ) { } @@ -1248,14 +1311,14 @@ struct LightOutput : public LocationOutput } const PathMatcher &m_lightSet; - GafferScene::RendererAlgo::LightLinks *m_lightLinks; + GafferScene::Private::RendererAlgo::LightLinks *m_lightLinks; }; struct LightFiltersOutput : public LocationOutput { - LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::RendererAlgo::RenderSets &renderSets, GafferScene::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + LightFiltersOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) : LocationOutput( renderer, globals, renderSets, root, scene ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) { } @@ -1294,14 +1357,14 @@ struct LightFiltersOutput : public LocationOutput private : const PathMatcher &m_lightFiltersSet; - GafferScene::RendererAlgo::LightLinks *m_lightLinks; + GafferScene::Private::RendererAlgo::LightLinks *m_lightLinks; }; struct ObjectOutput : public LocationOutput { - ObjectOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::RendererAlgo::RenderSets &renderSets, const GafferScene::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) + ObjectOutput( IECoreScenePreview::Renderer *renderer, const IECore::CompoundObject *globals, const GafferScene::Private::RendererAlgo::RenderSets &renderSets, const GafferScene::Private::RendererAlgo::LightLinks *lightLinks, const ScenePlug::ScenePath &root, const ScenePlug *scene ) : LocationOutput( renderer, globals, renderSets, root, scene ), m_cameraSet( renderSets.camerasSet() ), m_lightSet( renderSets.lightsSet() ), m_lightFiltersSet( renderSets.lightFiltersSet() ), m_lightLinks( lightLinks ) { } @@ -1318,8 +1381,11 @@ struct ObjectOutput : public LocationOutput return true; } - vector samples; vector sampleTimes; - RendererAlgo::objectSamples( scene, deformationSegments(), shutter(), samples, sampleTimes ); + vector sampleTimes; + deformationMotionTimes( sampleTimes ); + + vector samples; + GafferScene::Private::RendererAlgo::objectSamples( scene->objectPlug(), sampleTimes, samples ); if( !samples.size() ) { return true; @@ -1357,7 +1423,7 @@ struct ObjectOutput : public LocationOutput const PathMatcher &m_cameraSet; const PathMatcher &m_lightSet; const PathMatcher &m_lightFiltersSet; - const RendererAlgo::LightLinks *m_lightLinks; + const GafferScene::Private::RendererAlgo::LightLinks *m_lightLinks; }; @@ -1386,8 +1452,8 @@ ConstOutputPtr addGafferOutputHeaders( const Output *output, const ScenePlug *sc context->names( names ); for( const auto &name : names ) { - // Output params are never mutated, so this is 'safe'... - DataPtr data = const_cast( context->get( name ) ); + DataPtr data = context->getAsData( name ); + // The requires a round-trip through the renderer's native type system, as such, it requires // bi-directional conversion in Cortex. Unsupported types result in a slew of warning messages // in render output. As many facilities employ un-supported types in their contexts as standard, @@ -1421,6 +1487,9 @@ ConstOutputPtr addGafferOutputHeaders( const Output *output, const ScenePlug *sc namespace GafferScene { +namespace Private +{ + namespace RendererAlgo { @@ -1574,7 +1643,7 @@ void outputCameras( const ScenePlug *scene, const IECore::CompoundObject *global if( !cameraOption || cameraOption->readable().empty() ) { CameraPtr defaultCamera = new IECoreScene::Camera; - RendererAlgo::applyCameraGlobals( defaultCamera.get(), globals, scene ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), globals, scene ); IECoreScenePreview::Renderer::AttributesInterfacePtr defaultAttributes = renderer->attributes( scene->attributesPlug()->defaultValue() ); ConstStringDataPtr name = new StringData( "gaffer:defaultCamera" ); renderer->camera( name->readable(), defaultCamera.get(), defaultAttributes.get() ); @@ -1602,105 +1671,8 @@ void outputObjects( const ScenePlug *scene, const IECore::CompoundObject *global SceneAlgo::parallelProcessLocations( scene, output, root ); } -void applyCameraGlobals( IECoreScene::Camera *camera, const IECore::CompoundObject *globals, const ScenePlug *scene ) -{ - // Set any camera-relevant render globals that haven't been overridden on the camera - const IntData *filmFitData = globals->member( "option:render:filmFit" ); - if( !camera->hasFilmFit() && filmFitData ) - { - camera->setFilmFit( (IECoreScene::Camera::FilmFit)filmFitData->readable() ); - } - - const V2iData *resolutionData = globals->member( "option:render:resolution" ); - if( !camera->hasResolution() && resolutionData ) - { - camera->setResolution( resolutionData->readable() ); - } - - const FloatData *resolutionMultiplierData = globals->member( "option:render:resolutionMultiplier" ); - if( !camera->hasResolutionMultiplier() && resolutionMultiplierData ) - { - camera->setResolutionMultiplier( resolutionMultiplierData->readable() ); - } - - const FloatData *pixelAspectRatioData = globals->member( "option:render:pixelAspectRatio" ); - if( !camera->hasPixelAspectRatio() && pixelAspectRatioData ) - { - camera->setPixelAspectRatio( pixelAspectRatioData->readable() ); - } - - const BoolData *overscanData = globals->member( "option:render:overscan" ); - bool overscan = overscanData && overscanData->readable(); - if( camera->hasOverscan() ) overscan = camera->getOverscan(); - if( overscan ) - { - if( !camera->hasOverscan() ) - { - camera->setOverscan( true ); - } - const FloatData *overscanLeftData = globals->member( "option:render:overscanLeft" ); - if( !camera->hasOverscanLeft() && overscanLeftData ) - { - camera->setOverscanLeft( overscanLeftData->readable() ); - } - const FloatData *overscanRightData = globals->member( "option:render:overscanRight" ); - if( !camera->hasOverscanRight() && overscanRightData ) - { - camera->setOverscanRight( overscanRightData->readable() ); - } - const FloatData *overscanTopData = globals->member( "option:render:overscanTop" ); - if( !camera->hasOverscanTop() && overscanTopData ) - { - camera->setOverscanTop( overscanTopData->readable() ); - } - const FloatData *overscanBottomData = globals->member( "option:render:overscanBottom" ); - if( !camera->hasOverscanBottom() && overscanBottomData ) - { - camera->setOverscanBottom( overscanBottomData->readable() ); - } - } - - const Box2fData *cropWindowData = globals->member( "option:render:cropWindow" ); - if( !camera->hasCropWindow() && cropWindowData ) - { - camera->setCropWindow( cropWindowData->readable() ); - } - - const BoolData *depthOfFieldData = globals->member( "option:render:depthOfField" ); - /*if( !camera->hasDepthOfField() && depthOfFieldData ) - { - camera->setDepthOfField( depthOfFieldData->readable() ); - }*/ - // \todo - switch to the form above once we have officially added the depthOfField parameter to Cortex. - // The plan then would be that the renderer backends should respect camera->getDepthOfField. - // For the moment we bake into fStop instead - bool depthOfField = false; - if( depthOfFieldData ) - { - // First set from render globals - depthOfField = depthOfFieldData->readable(); - } - if( const BoolData *d = camera->parametersData()->member( "depthOfField" ) ) - { - // Override based on camera setting - depthOfField = d->readable(); - } - if( !depthOfField ) - { - // If there is no depth of field, bake that into the fStop - camera->setFStop( 0.0f ); - } - - // Bake the shutter from the globals into the camera before passing it to the renderer backend - // - // Before this bake, the shutter is an optional render setting override, with the shutter start - // and end relative to the current frame. After baking, the shutter is currently an absolute - // shutter, with the frame added on. Feels like it might be more consistent if we switched to - // always storing a relative shutter in camera->setShutter() - camera->setShutter( SceneAlgo::shutter( globals, scene ) ); - -} - } // namespace RendererAlgo +} // namespace Private + } // namespace GafferScene diff --git a/src/GafferScene/ResamplePrimitiveVariables.cpp b/src/GafferScene/ResamplePrimitiveVariables.cpp index 627fb007825..c35117776a9 100644 --- a/src/GafferScene/ResamplePrimitiveVariables.cpp +++ b/src/GafferScene/ResamplePrimitiveVariables.cpp @@ -98,7 +98,7 @@ void ResamplePrimitiveVariables::processPrimitiveVariable( const ScenePath &path if( const MeshPrimitive *meshPrimitive = IECore::runTimeCast( inputGeometry.get() ) ) { - MeshAlgo::resamplePrimitiveVariable( meshPrimitive, variable, interpolation ); + MeshAlgo::resamplePrimitiveVariable( meshPrimitive, variable, interpolation, context->canceller() ); } else if( const CurvesPrimitive *curvesPrimitive = IECore::runTimeCast( inputGeometry.get() ) ) { diff --git a/src/GafferScene/ReverseWinding.cpp b/src/GafferScene/ReverseWinding.cpp index 83155444477..cb1f7b0af6f 100644 --- a/src/GafferScene/ReverseWinding.cpp +++ b/src/GafferScene/ReverseWinding.cpp @@ -73,6 +73,6 @@ IECore::ConstObjectPtr ReverseWinding::computeProcessedObject( const ScenePath & } MeshPrimitivePtr meshCopy = mesh->copy(); - MeshAlgo::reverseWinding( meshCopy.get() ); + MeshAlgo::reverseWinding( meshCopy.get(), context->canceller() ); return meshCopy; } diff --git a/src/GafferScene/SceneAlgo.cpp b/src/GafferScene/SceneAlgo.cpp index cab51d524df..6350c81895f 100644 --- a/src/GafferScene/SceneAlgo.cpp +++ b/src/GafferScene/SceneAlgo.cpp @@ -67,9 +67,9 @@ #include "boost/unordered_map.hpp" #include "tbb/concurrent_unordered_set.h" +#include "tbb/enumerable_thread_specific.h" #include "tbb/parallel_for.h" #include "tbb/spin_mutex.h" -#include "tbb/task.h" using namespace std; using namespace Imath; @@ -116,17 +116,27 @@ void filteredNodesWalk( Plug *filterPlug, std::unordered_set m_threadResults; }; @@ -145,7 +155,14 @@ struct ThreadablePathHashAccumulator // as good ( the only weakness I can see is that if you summed 2**64 identical hashes, they would // cancel out, but I can't see that arising here ). IECore::MurmurHash h; - h.append( &path.front(), path.size() ); + if( path.size() ) + { + h.append( path.data(), path.size() ); + } + else + { + h.append( 0 ); + } m_h1Accum += h.h1(); m_h2Accum += h.h2(); return true; @@ -168,16 +185,25 @@ void GafferScene::SceneAlgo::matchingPaths( const Filter *filter, const ScenePlu matchingPaths( filter->outPlug(), scene, paths ); } -void GafferScene::SceneAlgo::matchingPaths( const Gaffer::IntPlug *filterPlug, const ScenePlug *scene, PathMatcher &paths ) +void GafferScene::SceneAlgo::matchingPaths( const FilterPlug *filterPlug, const ScenePlug *scene, PathMatcher &paths ) { - ThreadablePathAccumulator f( paths ); + ThreadablePathAccumulator f; GafferScene::SceneAlgo::filteredParallelTraverse( scene, filterPlug, f ); + paths = f.result(); +} + +void GafferScene::SceneAlgo::matchingPaths( const FilterPlug *filterPlug, const ScenePlug *scene, const ScenePlug::ScenePath &root, IECore::PathMatcher &paths ) +{ + ThreadablePathAccumulator f; + GafferScene::SceneAlgo::filteredParallelTraverse( scene, filterPlug, f, root ); + paths = f.result(); } void GafferScene::SceneAlgo::matchingPaths( const PathMatcher &filter, const ScenePlug *scene, PathMatcher &paths ) { - ThreadablePathAccumulator f( paths ); + ThreadablePathAccumulator f; GafferScene::SceneAlgo::filteredParallelTraverse( scene, filter, f ); + paths = f.result(); } IECore::MurmurHash GafferScene::SceneAlgo::matchingPathsHash( const Filter *filter, const ScenePlug *scene ) @@ -192,6 +218,13 @@ IECore::MurmurHash GafferScene::SceneAlgo::matchingPathsHash( const GafferScene: return IECore::MurmurHash( f.m_h1Accum, f.m_h2Accum ); } +IECore::MurmurHash GafferScene::SceneAlgo::matchingPathsHash( const GafferScene::FilterPlug *filterPlug, const ScenePlug *scene, const ScenePlug::ScenePath &root ) +{ + ThreadablePathHashAccumulator f; + GafferScene::SceneAlgo::filteredParallelTraverse( scene, filterPlug, f, root ); + return IECore::MurmurHash( f.m_h1Accum, f.m_h2Accum ); +} + IECore::MurmurHash GafferScene::SceneAlgo::matchingPathsHash( const PathMatcher &filter, const ScenePlug *scene ) { ThreadablePathHashAccumulator f; @@ -228,9 +261,6 @@ IECore::ConstCompoundObjectPtr GafferScene::SceneAlgo::globalAttributes( const I Imath::V2f GafferScene::SceneAlgo::shutter( const IECore::CompoundObject *globals, const ScenePlug *scene ) { - const BoolData *cameraBlurData = globals->member( "option:render:cameraBlur" ); - const bool cameraBlur = cameraBlurData ? cameraBlurData->readable() : false; - const BoolData *transformBlurData = globals->member( "option:render:transformBlur" ); const bool transformBlur = transformBlurData ? transformBlurData->readable() : false; @@ -238,7 +268,7 @@ Imath::V2f GafferScene::SceneAlgo::shutter( const IECore::CompoundObject *global const bool deformationBlur = deformationBlurData ? deformationBlurData->readable() : false; V2f shutter( Context::current()->getFrame() ); - if( cameraBlur || transformBlur || deformationBlur ) + if( transformBlur || deformationBlur ) { ConstCameraPtr camera = nullptr; const StringData *cameraOption = globals->member( "option:render:camera" ); @@ -302,7 +332,7 @@ IECore::ConstCompoundDataPtr GafferScene::SceneAlgo::sets( const ScenePlug *scen ScenePlug::SetScope setScope( threadState ); for( size_t i=r.begin(); i!=r.end(); ++i ) { - setScope.setSetName( setNames[i] ); + setScope.setSetName( &setNames[i] ); setsVector[i] = scene->setPlug()->getValue(); } @@ -414,7 +444,7 @@ class CapturingMonitor : public Monitor IE_CORE_DECLAREPTR( CapturingMonitor ) -uint64_t g_historyID = 0; +std::atomic g_historyID( 0 ); SceneAlgo::History::Ptr historyWalk( const CapturedProcess *process, InternedString scenePlugChildName, SceneAlgo::History *parent ) { @@ -429,7 +459,7 @@ SceneAlgo::History::Ptr historyWalk( const CapturedProcess *process, InternedStr ScenePlug *scene = plug->parent(); if( scene && plug == scene->getChild( scenePlugChildName ) ) { - ContextPtr cleanContext = new Context( *process->context, Context::Copied ); + ContextPtr cleanContext = new Context( *process->context ); cleanContext->remove( SceneAlgo::historyIDContextName() ); SceneAlgo::History::Ptr history = new SceneAlgo::History( scene, cleanContext ); if( !result ) @@ -652,9 +682,10 @@ SceneAlgo::History::Ptr SceneAlgo::history( const Gaffer::ValuePlug *scenePlugCh CapturingMonitorPtr monitor = new CapturingMonitor; { - ScenePlug::PathScope pathScope( Context::current(), path ); + ScenePlug::PathScope pathScope( Context::current(), &path ); + uint64_t historyID = g_historyID++; // Trick to bypass the hash cache and get a full upstream evaluation. - pathScope.set( historyIDContextName(), g_historyID++ ); + pathScope.set( historyIDContextName(), &historyID ); Monitor::Scope monitorScope( monitor ); scenePlugChild->hash(); } @@ -890,7 +921,7 @@ IECore::PathMatcher findAttributes( const ScenePlug *scene, const AttributesPred tbb::spin_mutex resultMutex; IECore::PathMatcher result; AttributesFinder attributesFinder( predicate, resultMutex, result ); - parallelProcessLocations( scene, attributesFinder ); + SceneAlgo::parallelProcessLocations( scene, attributesFinder ); return result; } @@ -990,7 +1021,7 @@ bool GafferScene::SceneAlgo::visible( const ScenePlug *scene, const ScenePlug::S for( ScenePlug::ScenePath::const_iterator it = path.begin(), eIt = path.end(); it != eIt; ++it ) { p.push_back( *it ); - pathScope.setPath( p ); + pathScope.setPath( &p ); ConstCompoundObjectPtr attributes = scene->attributesPlug()->getValue(); const BoolData *visibilityData = attributes->member( "scene:visible" ); @@ -1030,3 +1061,161 @@ Imath::Box3f GafferScene::SceneAlgo::bound( const IECore::Object *object ) return Imath::Box3f(); } } + +////////////////////////////////////////////////////////////////////////// +// Render Adaptor Registry +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +typedef boost::container::flat_map RenderAdaptors; + +RenderAdaptors &renderAdaptors() +{ + static RenderAdaptors a; + return a; +} + +} + +void GafferScene::SceneAlgo::registerRenderAdaptor( const std::string &name, SceneAlgo::RenderAdaptor adaptor ) +{ + renderAdaptors()[name] = adaptor; +} + +void GafferScene::SceneAlgo::deregisterRenderAdaptor( const std::string &name ) +{ + renderAdaptors().erase( name ); +} + +SceneProcessorPtr GafferScene::SceneAlgo::createRenderAdaptors() +{ + SceneProcessorPtr result = new SceneProcessor; + + ScenePlug *in = result->inPlug(); + + const RenderAdaptors &a = renderAdaptors(); + for( RenderAdaptors::const_iterator it = a.begin(), eIt = a.end(); it != eIt; ++it ) + { + SceneProcessorPtr adaptor = it->second(); + if( adaptor ) + { + result->addChild( adaptor ); + adaptor->inPlug()->setInput( in ); + in = adaptor->outPlug(); + } + else + { + IECore::msg( + IECore::Msg::Warning, "SceneAlgo::createRenderAdaptors", + boost::format( "Adaptor \"%1%\" returned null" ) % it->first + ); + } + } + + result->outPlug()->setInput( in ); + return result; +} + +////////////////////////////////////////////////////////////////////////// +// Apply Camera Globals +////////////////////////////////////////////////////////////////////////// + +void GafferScene::SceneAlgo::applyCameraGlobals( IECoreScene::Camera *camera, const IECore::CompoundObject *globals, const ScenePlug *scene ) +{ + // Set any camera-relevant render globals that haven't been overridden on the camera + const IntData *filmFitData = globals->member( "option:render:filmFit" ); + if( !camera->hasFilmFit() && filmFitData ) + { + camera->setFilmFit( (IECoreScene::Camera::FilmFit)filmFitData->readable() ); + } + + const V2iData *resolutionData = globals->member( "option:render:resolution" ); + if( !camera->hasResolution() && resolutionData ) + { + camera->setResolution( resolutionData->readable() ); + } + + const FloatData *resolutionMultiplierData = globals->member( "option:render:resolutionMultiplier" ); + if( !camera->hasResolutionMultiplier() && resolutionMultiplierData ) + { + camera->setResolutionMultiplier( resolutionMultiplierData->readable() ); + } + + const FloatData *pixelAspectRatioData = globals->member( "option:render:pixelAspectRatio" ); + if( !camera->hasPixelAspectRatio() && pixelAspectRatioData ) + { + camera->setPixelAspectRatio( pixelAspectRatioData->readable() ); + } + + const BoolData *overscanData = globals->member( "option:render:overscan" ); + bool overscan = overscanData && overscanData->readable(); + if( camera->hasOverscan() ) overscan = camera->getOverscan(); + if( overscan ) + { + if( !camera->hasOverscan() ) + { + camera->setOverscan( true ); + } + const FloatData *overscanLeftData = globals->member( "option:render:overscanLeft" ); + if( !camera->hasOverscanLeft() && overscanLeftData ) + { + camera->setOverscanLeft( overscanLeftData->readable() ); + } + const FloatData *overscanRightData = globals->member( "option:render:overscanRight" ); + if( !camera->hasOverscanRight() && overscanRightData ) + { + camera->setOverscanRight( overscanRightData->readable() ); + } + const FloatData *overscanTopData = globals->member( "option:render:overscanTop" ); + if( !camera->hasOverscanTop() && overscanTopData ) + { + camera->setOverscanTop( overscanTopData->readable() ); + } + const FloatData *overscanBottomData = globals->member( "option:render:overscanBottom" ); + if( !camera->hasOverscanBottom() && overscanBottomData ) + { + camera->setOverscanBottom( overscanBottomData->readable() ); + } + } + + const Box2fData *cropWindowData = globals->member( "option:render:cropWindow" ); + if( !camera->hasCropWindow() && cropWindowData ) + { + camera->setCropWindow( cropWindowData->readable() ); + } + + const BoolData *depthOfFieldData = globals->member( "option:render:depthOfField" ); + /*if( !camera->hasDepthOfField() && depthOfFieldData ) + { + camera->setDepthOfField( depthOfFieldData->readable() ); + }*/ + // \todo - switch to the form above once we have officially added the depthOfField parameter to Cortex. + // The plan then would be that the renderer backends should respect camera->getDepthOfField. + // For the moment we bake into fStop instead + bool depthOfField = false; + if( depthOfFieldData ) + { + // First set from render globals + depthOfField = depthOfFieldData->readable(); + } + if( const BoolData *d = camera->parametersData()->member( "depthOfField" ) ) + { + // Override based on camera setting + depthOfField = d->readable(); + } + if( !depthOfField ) + { + // If there is no depth of field, bake that into the fStop + camera->setFStop( 0.0f ); + } + + // Bake the shutter from the globals into the camera before passing it to the renderer backend + // + // Before this bake, the shutter is an optional render setting override, with the shutter start + // and end relative to the current frame. After baking, the shutter is currently an absolute + // shutter, with the frame added on. Feels like it might be more consistent if we switched to + // always storing a relative shutter in camera->setShutter() + camera->setShutter( SceneAlgo::shutter( globals, scene ) ); +} diff --git a/src/GafferScene/SceneNode.cpp b/src/GafferScene/SceneNode.cpp index 9ab4dff4cca..0cb320b0dec 100644 --- a/src/GafferScene/SceneNode.cpp +++ b/src/GafferScene/SceneNode.cpp @@ -97,7 +97,7 @@ void SceneNode::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outp if( input == enabledPlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { if( (*it)->getInput() ) { @@ -139,7 +139,7 @@ void SceneNode::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outp void SceneNode::hash( const ValuePlug *output, const Context *context, IECore::MurmurHash &h ) const { const ScenePlug *scenePlug = output->parent(); - if( scenePlug && enabledPlug()->getValue() ) + if( scenePlug && enabled( context ) ) { // We don't call ComputeNode::hash() immediately here, because for subclasses which // want to pass through a specific hash in the hash*() methods it's a waste of time (the @@ -156,10 +156,8 @@ void SceneNode::hash( const ValuePlug *output, const Context *context, IECore::M const ScenePath &scenePath = context->get( ScenePlug::scenePathContextName ); if( scenePath.empty() ) { - // the result of compute() will actually be different if we're at the root, so - // we hash an identity M44fData: - h.append( IECore::M44fData::staticTypeId() ); - h.append( Imath::M44f() ); + // Scene root must have identity transform + h = output->defaultHash(); } else { @@ -276,7 +274,7 @@ void SceneNode::compute( ValuePlug *output, const Context *context ) const ScenePlug *scenePlug = output->parent(); if( scenePlug ) { - if( enabledPlug()->getValue() ) + if( enabled( context ) ) { if( output == scenePlug->boundPlug() ) { @@ -288,12 +286,17 @@ void SceneNode::compute( ValuePlug *output, const Context *context ) const else if( output == scenePlug->transformPlug() ) { const ScenePath &scenePath = context->get( ScenePlug::scenePathContextName ); - M44f transform; - if( scenePath.size() ) // scene root must have identity transform + if( scenePath.empty() ) + { + // Scene root must have identity transform + output->setToDefault(); + } + else { - transform = computeTransform( scenePath, context, scenePlug ); + static_cast( output )->setValue( + computeTransform( scenePath, context, scenePlug ) + ); } - static_cast( output )->setValue( transform ); } else if( output == scenePlug->attributesPlug() ) { @@ -436,16 +439,47 @@ Gaffer::ValuePlug::CachePolicy SceneNode::computeCachePolicy( const Gaffer::Valu IECore::MurmurHash SceneNode::hashOfTransformedChildBounds( const ScenePath &path, const ScenePlug *out, const IECore::InternedStringVectorData *childNamesData ) const { - ScenePlug::PathScope pathScope( Context::current(), path ); + ScenePlug::PathScope pathScope( Context::current(), &path ); return out->childBoundsPlug()->hash(); } Imath::Box3f SceneNode::unionOfTransformedChildBounds( const ScenePath &path, const ScenePlug *out, const IECore::InternedStringVectorData *childNamesData ) const { - ScenePlug::PathScope pathScope( Context::current(), path ); + ScenePlug::PathScope pathScope( Context::current(), &path ); return out->childBoundsPlug()->getValue(); } +bool SceneNode::enabled( const Gaffer::Context *context ) const +{ + const BoolPlug *plug = enabledPlug(); + const BoolPlug *sourcePlug = plug->source(); + if( !sourcePlug || sourcePlug->direction() == Plug::Out ) + { + // Value may be computed. We use a global scope + // for two reasons : + // + // - Because our implementation assumes the result + // is constant across the scene, and allowing it to + // vary could produce scenes which are not internally + // consistent. FilteredSceneProcessor provides the + // concept of varying enabled-ness via filters. + // - To reduce pressure on the hash cache. + // + // > Note : `sourcePlug` will be null if the source is + // > not a BoolPlug. In this case we call `getValue()` + // > on `plug` and it will perform the appropriate type + // > conversion. + ScenePlug::GlobalScope globalScope( context ); + return sourcePlug ? sourcePlug->getValue() : plug->getValue(); + } + else + { + // Value is not computed so context is irrelevant. + // Avoid overhead of context creation. + return sourcePlug->getValue(); + } +} + void SceneNode::plugInputChanged( Gaffer::Plug *plug ) { // If a node makes a pass-through connection for a `childNamesPlug()` then we @@ -486,8 +520,9 @@ void SceneNode::hashExists( const Gaffer::Context *context, const ScenePlug *par return; } - ScenePath parentPath( scenePath ); parentPath.pop_back(); - ScenePlug::PathScope parentScope( context, parentPath ); + ScenePath parentPath( scenePath ); + parentPath.pop_back(); + ScenePlug::PathScope parentScope( context, &parentPath ); if( !parent->existsPlug()->getValue() ) { h.append( false ); @@ -507,8 +542,9 @@ bool SceneNode::computeExists( const Gaffer::Context *context, const ScenePlug * return true; } - ScenePath parentPath( scenePath ); parentPath.pop_back(); - ScenePlug::PathScope parentScope( context, parentPath ); + ScenePath parentPath( scenePath ); + parentPath.pop_back(); + ScenePlug::PathScope parentScope( context, &parentPath ); if( !parent->existsPlug()->getValue() ) { // If `parentPath` doesn't exist, then neither can `scenePath` @@ -570,7 +606,7 @@ void SceneNode::hashChildBounds( const Gaffer::Context *context, const ScenePlug for( size_t i = range.begin(); i != range.end(); ++i ) { childPath.back() = childNames[i]; - pathScope.setPath( childPath ); + pathScope.setPath( &childPath ); parent->boundPlug()->hash( result ); parent->transformPlug()->hash( result ); } @@ -583,9 +619,7 @@ void SceneNode::hashChildBounds( const Gaffer::Context *context, const ScenePlug result.append( y ); return result; }, - #if TBB_INTERFACE_VERSION >= 10001 simple_partitioner(), - #endif taskGroupContext ); @@ -618,7 +652,7 @@ Imath::Box3f SceneNode::computeChildBounds( const Gaffer::Context *context, cons for( size_t i = range.begin(); i != range.end(); ++i ) { childPath.back() = childNames[i]; - pathScope.setPath( childPath ); + pathScope.setPath( &childPath ); Box3f childBound = parent->boundPlug()->getValue(); childBound = transform( childBound, parent->transformPlug()->getValue() ); result.extendBy( childBound ); diff --git a/src/GafferScene/ScenePlug.cpp b/src/GafferScene/ScenePlug.cpp index dbd0bc68a8d..5aef9338cd0 100644 --- a/src/GafferScene/ScenePlug.cpp +++ b/src/GafferScene/ScenePlug.cpp @@ -318,6 +318,12 @@ ScenePlug::PathScope::PathScope( const Gaffer::Context *context ) ScenePlug::PathScope::PathScope( const Gaffer::Context *context, const ScenePath &scenePath ) : PathScope( context ) +{ + setAllocated( scenePathContextName, scenePath ); +} + +ScenePlug::PathScope::PathScope( const Gaffer::Context *context, const ScenePath *scenePath ) + : PathScope( context ) { setPath( scenePath ); } @@ -329,11 +335,22 @@ ScenePlug::PathScope::PathScope( const Gaffer::ThreadState &threadState ) ScenePlug::PathScope::PathScope( const Gaffer::ThreadState &threadState, const ScenePath &scenePath ) : EditableScope( threadState ) +{ + setAllocated( scenePathContextName, scenePath ); +} + +ScenePlug::PathScope::PathScope( const Gaffer::ThreadState &threadState, const ScenePath *scenePath ) + : EditableScope( threadState ) { setPath( scenePath ); } void ScenePlug::PathScope::setPath( const ScenePath &scenePath ) +{ + setAllocated( scenePathContextName, scenePath ); +} + +void ScenePlug::PathScope::setPath( const ScenePath *scenePath ) { set( scenePathContextName, scenePath ); } @@ -347,6 +364,14 @@ ScenePlug::SetScope::SetScope( const Gaffer::Context *context ) ScenePlug::SetScope::SetScope( const Gaffer::Context *context, const IECore::InternedString &setName ) : EditableScope( context ) +{ + remove( Filter::inputSceneContextName ); + remove( ScenePlug::scenePathContextName ); + setAllocated( setNameContextName, setName ); +} + +ScenePlug::SetScope::SetScope( const Gaffer::Context *context, const IECore::InternedString *setName ) + : EditableScope( context ) { remove( Filter::inputSceneContextName ); remove( ScenePlug::scenePathContextName ); @@ -362,6 +387,14 @@ ScenePlug::SetScope::SetScope( const Gaffer::ThreadState &threadState ) ScenePlug::SetScope::SetScope( const Gaffer::ThreadState &threadState, const IECore::InternedString &setName ) : EditableScope( threadState ) +{ + remove( Filter::inputSceneContextName ); + remove( ScenePlug::scenePathContextName ); + setAllocated( setNameContextName, setName ); +} + +ScenePlug::SetScope::SetScope( const Gaffer::ThreadState &threadState, const IECore::InternedString *setName ) + : EditableScope( threadState ) { remove( Filter::inputSceneContextName ); remove( ScenePlug::scenePathContextName ); @@ -369,6 +402,11 @@ ScenePlug::SetScope::SetScope( const Gaffer::ThreadState &threadState, const IEC } void ScenePlug::SetScope::setSetName( const IECore::InternedString &setName ) +{ + setAllocated( setNameContextName, setName ); +} + +void ScenePlug::SetScope::setSetName( const IECore::InternedString *setName ) { set( setNameContextName, setName ); } @@ -396,19 +434,19 @@ bool ScenePlug::exists() const bool ScenePlug::exists( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return existsPlug()->getValue(); } Imath::Box3f ScenePlug::bound( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return boundPlug()->getValue(); } Imath::M44f ScenePlug::transform( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return transformPlug()->getValue(); } @@ -420,7 +458,7 @@ Imath::M44f ScenePlug::fullTransform( const ScenePath &scenePath ) const ScenePath path( scenePath ); while( path.size() ) { - pathScope.setPath( path ); + pathScope.setPath( &path ); result = result * transformPlug()->getValue(); path.pop_back(); } @@ -430,7 +468,7 @@ Imath::M44f ScenePlug::fullTransform( const ScenePath &scenePath ) const IECore::ConstCompoundObjectPtr ScenePlug::attributes( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return attributesPlug()->getValue(); } @@ -443,7 +481,7 @@ IECore::CompoundObjectPtr ScenePlug::fullAttributes( const ScenePath &scenePath ScenePath path( scenePath ); while( path.size() ) { - pathScope.setPath( path ); + pathScope.setPath( &path ); IECore::ConstCompoundObjectPtr a = attributesPlug()->getValue(); const IECore::CompoundObject::ObjectMap &aMembers = a->members(); for( IECore::CompoundObject::ObjectMap::const_iterator it = aMembers.begin(), eIt = aMembers.end(); it != eIt; it++ ) @@ -461,13 +499,13 @@ IECore::CompoundObjectPtr ScenePlug::fullAttributes( const ScenePath &scenePath IECore::ConstObjectPtr ScenePlug::object( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return objectPlug()->getValue(); } IECore::ConstInternedStringVectorDataPtr ScenePlug::childNames( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return childNamesPlug()->getValue(); } @@ -485,19 +523,19 @@ IECore::ConstInternedStringVectorDataPtr ScenePlug::setNames() const IECore::ConstPathMatcherDataPtr ScenePlug::set( const IECore::InternedString &setName ) const { - SetScope scope( Context::current(), setName ); + SetScope scope( Context::current(), &setName ); return setPlug()->getValue(); } IECore::MurmurHash ScenePlug::boundHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return boundPlug()->hash(); } IECore::MurmurHash ScenePlug::transformHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return transformPlug()->hash(); } @@ -509,7 +547,7 @@ IECore::MurmurHash ScenePlug::fullTransformHash( const ScenePath &scenePath ) co ScenePath path( scenePath ); while( path.size() ) { - pathScope.setPath( path ); + pathScope.setPath( &path ); transformPlug()->hash( result ); path.pop_back(); } @@ -519,7 +557,7 @@ IECore::MurmurHash ScenePlug::fullTransformHash( const ScenePath &scenePath ) co IECore::MurmurHash ScenePlug::attributesHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return attributesPlug()->hash(); } @@ -531,7 +569,7 @@ IECore::MurmurHash ScenePlug::fullAttributesHash( const ScenePath &scenePath ) c ScenePath path( scenePath ); while( path.size() ) { - pathScope.setPath( path ); + pathScope.setPath( &path ); attributesPlug()->hash( result ); path.pop_back(); } @@ -541,13 +579,13 @@ IECore::MurmurHash ScenePlug::fullAttributesHash( const ScenePath &scenePath ) c IECore::MurmurHash ScenePlug::objectHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return objectPlug()->hash(); } IECore::MurmurHash ScenePlug::childNamesHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return childNamesPlug()->hash(); } @@ -565,26 +603,57 @@ IECore::MurmurHash ScenePlug::setNamesHash() const IECore::MurmurHash ScenePlug::setHash( const IECore::InternedString &setName ) const { - SetScope scope( Context::current(), setName ); + SetScope scope( Context::current(), &setName ); return setPlug()->hash(); } Imath::Box3f ScenePlug::childBounds( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return childBoundsPlug()->getValue(); } IECore::MurmurHash ScenePlug::childBoundsHash( const ScenePath &scenePath ) const { - PathScope scope( Context::current(), scenePath ); + PathScope scope( Context::current(), &scenePath ); return childBoundsPlug()->hash(); } void ScenePlug::stringToPath( const std::string &s, ScenePlug::ScenePath &path ) { path.clear(); - IECore::StringAlgo::tokenize( s, '/', path ); + + size_t index = 0, size = s.size(); + while( index < size ) + { + const size_t prevIndex = index; + index = s.find( '/', index ); + index = index == std::string::npos ? size : index; + if( index > prevIndex ) + { + if( index == prevIndex + 2 && s[prevIndex] == '.' && s[prevIndex+1] == '.' ) + { + // ".." + if( path.size() ) + { + path.pop_back(); + } + } + else + { + const IECore::InternedString name( s.c_str() + prevIndex, index - prevIndex ); + path.push_back( name ); + } + } + index++; + } +} + +ScenePlug::ScenePath ScenePlug::stringToPath( const std::string &s ) +{ + ScenePath result; + stringToPath( s, result ); + return result; } void ScenePlug::pathToString( const ScenePlug::ScenePath &path, std::string &s ) @@ -603,6 +672,13 @@ void ScenePlug::pathToString( const ScenePlug::ScenePath &path, std::string &s ) } } +std::string ScenePlug::pathToString( const ScenePlug::ScenePath &path ) +{ + std::string result; + pathToString( path, result ); + return result; +} + std::ostream &operator << ( std::ostream &o, const ScenePlug::ScenePath &path ) { if( !path.size() ) diff --git a/src/GafferScene/SceneProcessor.cpp b/src/GafferScene/SceneProcessor.cpp index 3976dffbe50..2a719c89220 100644 --- a/src/GafferScene/SceneProcessor.cpp +++ b/src/GafferScene/SceneProcessor.cpp @@ -125,7 +125,7 @@ const Plug *SceneProcessor::correspondingInput( const Plug *output ) const void SceneProcessor::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *context, IECore::MurmurHash &h ) const { const ScenePlug *scenePlug = output->parent(); - if( scenePlug && !enabledPlug()->getValue() ) + if( scenePlug && !enabled( context ) ) { // if we're hashing the output scene, and we're disabled, we need to // pass through the hash from the inPlug(). @@ -141,7 +141,7 @@ void SceneProcessor::hash( const Gaffer::ValuePlug *output, const Gaffer::Contex void SceneProcessor::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context ) const { const ScenePlug *scenePlug = output->parent(); - if( scenePlug && !enabledPlug()->getValue() ) + if( scenePlug && !enabled( context ) ) { // if we're computing the output scene, and we're disabled, we need to // pass through the scene from inPlug(). diff --git a/src/GafferScene/SceneReader.cpp b/src/GafferScene/SceneReader.cpp index ec6e49221f5..593378cd5f6 100644 --- a/src/GafferScene/SceneReader.cpp +++ b/src/GafferScene/SceneReader.cpp @@ -342,7 +342,7 @@ IECore::ConstObjectPtr SceneReader::computeObject( const ScenePath &path, const return parent->objectPlug()->defaultValue(); } - return s->readObject( context->getTime() ); + return s->readObject( context->getTime(), context->canceller() ); } void SceneReader::hashChildNames( const ScenePath &path, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const @@ -457,7 +457,7 @@ void SceneReader::hashSet( const IECore::InternedString &setName, const Gaffer:: h.append( setName ); } -static void loadSetWalk( const SceneInterface *s, const InternedString &setName, PathMatcher &set, const vector &path ) +static void loadSetWalk( const SceneInterface *s, const InternedString &setName, const Gaffer::Context *context, PathMatcher &set, const vector &path ) { if( s->hasTag( setName, SceneInterface::LocalTag ) ) { @@ -480,9 +480,11 @@ static void loadSetWalk( const SceneInterface *s, const InternedString &setName, childPath.push_back( InternedString() ); // room for the child name for( SceneInterface::NameList::const_iterator it = childNames.begin(), eIt = childNames.end(); it != eIt; ++it ) { + Canceller::check( context->canceller() ); + ConstSceneInterfacePtr child = s->child( *it ); childPath.back() = *it; - loadSetWalk( child.get(), setName, set, childPath ); + loadSetWalk( child.get(), setName, context, set, childPath ); } } @@ -492,7 +494,7 @@ IECore::ConstPathMatcherDataPtr SceneReader::computeSet( const IECore::InternedS ConstSceneInterfacePtr rootScene = scene( ScenePath() ); if( rootScene ) { - loadSetWalk( rootScene.get(), setName, result->writable(), ScenePath() ); + loadSetWalk( rootScene.get(), setName, context, result->writable(), ScenePath() ); } return result; } diff --git a/src/GafferScene/Seeds.cpp b/src/GafferScene/Seeds.cpp index 0f25dfe8e2d..72b7a88052c 100644 --- a/src/GafferScene/Seeds.cpp +++ b/src/GafferScene/Seeds.cpp @@ -111,15 +111,15 @@ bool Seeds::affectsBranchBound( const Gaffer::Plug *input ) const return input == inPlug()->boundPlug(); } -void Seeds::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Seeds::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchBound( parentPath, branchPath, context, h ); - h.append( inPlug()->boundHash( parentPath ) ); + BranchCreator::hashBranchBound( sourcePath, branchPath, context, h ); + h.append( inPlug()->boundHash( sourcePath ) ); } -Imath::Box3f Seeds::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::Box3f Seeds::computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - Box3f b = inPlug()->bound( parentPath ); + Box3f b = inPlug()->bound( sourcePath ); if( !b.isEmpty() ) { // The PointsPrimitive we make has a default point width of 1, @@ -135,12 +135,12 @@ bool Seeds::affectsBranchTransform( const Gaffer::Plug *input ) const return false; } -void Seeds::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Seeds::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchTransform( parentPath, branchPath, context, h ); + BranchCreator::hashBranchTransform( sourcePath, branchPath, context, h ); } -Imath::M44f Seeds::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::M44f Seeds::computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { return M44f(); } @@ -150,12 +150,12 @@ bool Seeds::affectsBranchAttributes( const Gaffer::Plug *input ) const return false; } -void Seeds::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Seeds::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - BranchCreator::hashBranchAttributes( parentPath, branchPath, context, h ); + BranchCreator::hashBranchAttributes( sourcePath, branchPath, context, h ); } -IECore::ConstCompoundObjectPtr Seeds::computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstCompoundObjectPtr Seeds::computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { return outPlug()->attributesPlug()->defaultValue(); } @@ -170,12 +170,12 @@ bool Seeds::affectsBranchObject( const Gaffer::Plug *input ) const ; } -void Seeds::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Seeds::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 1 ) { - BranchCreator::hashBranchObject( parentPath, branchPath, context, h ); - h.append( inPlug()->objectHash( parentPath ) ); + BranchCreator::hashBranchObject( sourcePath, branchPath, context, h ); + h.append( inPlug()->objectHash( sourcePath ) ); densityPlug()->hash( h ); densityPrimitiveVariablePlug()->hash( h ); pointTypePlug()->hash( h ); @@ -185,12 +185,12 @@ void Seeds::hashBranchObject( const ScenePath &parentPath, const ScenePath &bran h = outPlug()->objectPlug()->defaultValue()->Object::hash(); } -IECore::ConstObjectPtr Seeds::computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstObjectPtr Seeds::computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 1 ) { // do what we came for - ConstMeshPrimitivePtr mesh = runTimeCast( inPlug()->object( parentPath ) ); + ConstMeshPrimitivePtr mesh = runTimeCast( inPlug()->object( sourcePath ) ); if( !mesh ) { return outPlug()->objectPlug()->defaultValue(); @@ -200,7 +200,10 @@ IECore::ConstObjectPtr Seeds::computeBranchObject( const ScenePath &parentPath, mesh.get(), densityPlug()->getValue(), V2f( 0 ), - densityPrimitiveVariablePlug()->getValue() + densityPrimitiveVariablePlug()->getValue(), + "uv", + "P", + context->canceller() ); result->variables["type"] = PrimitiveVariable( PrimitiveVariable::Constant, new StringData( pointTypePlug()->getValue() ) ); @@ -214,11 +217,11 @@ bool Seeds::affectsBranchChildNames( const Gaffer::Plug *input ) const return input == namePlug(); } -void Seeds::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Seeds::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() == 0 ) { - BranchCreator::hashBranchChildNames( parentPath, branchPath, context, h ); + BranchCreator::hashBranchChildNames( sourcePath, branchPath, context, h ); namePlug()->hash( h ); } else @@ -227,7 +230,7 @@ void Seeds::hashBranchChildNames( const ScenePath &parentPath, const ScenePath & } } -IECore::ConstInternedStringVectorDataPtr Seeds::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Seeds::computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { diff --git a/src/GafferScene/Set.cpp b/src/GafferScene/Set.cpp index 9ede99b9410..4aec86ab5de 100644 --- a/src/GafferScene/Set.cpp +++ b/src/GafferScene/Set.cpp @@ -38,6 +38,7 @@ #include "GafferScene/FilterResults.h" +#include "Gaffer/DeleteContextVariables.h" #include "Gaffer/StringPlug.h" #include "IECore/StringAlgo.h" @@ -47,7 +48,41 @@ using namespace IECore; using namespace Gaffer; using namespace GafferScene; -static InternedString g_ellipsis( "..." ); +////////////////////////////////////////////////////////////////////////// +// Internal utilities +////////////////////////////////////////////////////////////////////////// + +namespace +{ + +struct PathMatcherScope : public ScenePlug::GlobalScope +{ + + PathMatcherScope( const Context *context, const string &setVariable, const InternedString &setName ) + : ScenePlug::GlobalScope( context ), m_setName( setName ) + { + if( !setVariable.empty() ) + { + // Storing as `string` rather than `InternedString` to + // avoid confusion when referring to the variable in + // upstream expressions. + set( setVariable, &m_setName.string() ); + } + } + + private : + + const InternedString m_setName; + +}; + +InternedString g_ellipsis( "..." ); + +} // namespace + +////////////////////////////////////////////////////////////////////////// +// Set implementation +////////////////////////////////////////////////////////////////////////// GAFFER_NODE_DEFINE_TYPE( Set ); @@ -59,15 +94,26 @@ Set::Set( const std::string &name ) storeIndexOfNextChild( g_firstPlugIndex ); addChild( new Gaffer::IntPlug( "mode", Gaffer::Plug::In, Create, Create, Remove ) ); addChild( new Gaffer::StringPlug( "name", Gaffer::Plug::In, "set" ) ); + addChild( new Gaffer::StringPlug( "setVariable" ) ); addChild( new Gaffer::StringVectorDataPlug( "paths", Gaffer::Plug::In, new StringVectorData ) ); addChild( new PathMatcherDataPlug( "__filterResults", Gaffer::Plug::In, new PathMatcherData, Plug::Default & ~Plug::Serialisable ) ); addChild( new PathMatcherDataPlug( "__pathMatcher", Gaffer::Plug::Out, new PathMatcherData ) ); + // Internal nodes to drive `filterResultsPlug()`, without leaking `setVariable` to + // the upstream scene. + + Gaffer::DeleteContextVariablesPtr deleteContextVariables = new Gaffer::DeleteContextVariables( "__DeleteContextVariables" ); + deleteContextVariables->setup( inPlug() ); + deleteContextVariables->inPlug()->setInput( inPlug() ); + deleteContextVariables->enabledPlug()->setInput( setVariablePlug() ); + deleteContextVariables->variablesPlug()->setInput( setVariablePlug() ); + addChild( deleteContextVariables ); + FilterResultsPtr filterResults = new FilterResults( "__FilterResults" ); addChild( filterResults ); - filterResults->scenePlug()->setInput( inPlug() ); + filterResults->scenePlug()->setInput( deleteContextVariables->outPlug() ); filterResults->filterPlug()->setInput( filterPlug() ); filterResultsPlug()->setInput( filterResults->outPlug() ); @@ -104,34 +150,44 @@ const Gaffer::StringPlug *Set::namePlug() const return getChild( g_firstPlugIndex + 1 ); } +Gaffer::StringPlug *Set::setVariablePlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const Gaffer::StringPlug *Set::setVariablePlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + Gaffer::StringVectorDataPlug *Set::pathsPlug() { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 3 ); } const Gaffer::StringVectorDataPlug *Set::pathsPlug() const { - return getChild( g_firstPlugIndex + 2 ); + return getChild( g_firstPlugIndex + 3 ); } Gaffer::PathMatcherDataPlug *Set::filterResultsPlug() { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } const Gaffer::PathMatcherDataPlug *Set::filterResultsPlug() const { - return getChild( g_firstPlugIndex + 3 ); + return getChild( g_firstPlugIndex + 4 ); } Gaffer::PathMatcherDataPlug *Set::pathMatcherPlug() { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } const Gaffer::PathMatcherDataPlug *Set::pathMatcherPlug() const { - return getChild( g_firstPlugIndex + 4 ); + return getChild( g_firstPlugIndex + 5 ); } void Set::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const @@ -149,6 +205,7 @@ void Set::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) if( input == namePlug() || + input == setVariablePlug() || input == inPlug()->setPlug() || input == modePlug() || input == pathMatcherPlug() @@ -227,7 +284,7 @@ IECore::ConstInternedStringVectorDataPtr Set::computeSetNames( const Gaffer::Con { ConstInternedStringVectorDataPtr inNamesData = inPlug()->setNamesPlug()->getValue(); - const std::string &names = namePlug()->getValue(); + const std::string names = namePlug()->getValue(); if( !names.size() ) { return inNamesData; @@ -238,42 +295,43 @@ IECore::ConstInternedStringVectorDataPtr Set::computeSetNames( const Gaffer::Con return inNamesData; } - vector tokenizedNames; - StringAlgo::tokenize( names, ' ', tokenizedNames ); - - // specific logic if we have only one item, to avoid the more complex logic of adding two lists together - if( tokenizedNames.size() == 1 ) { - const std::vector &inNames = inNamesData->readable(); - if( std::find( inNames.begin(), inNames.end(), tokenizedNames[0] ) != inNames.end() ) + // Fast path for the common case of a single name. + const std::vector &inNames = inNamesData->readable(); + if( names.find( ' ' ) == string::npos ) + { + if( StringAlgo::hasWildcards( names ) || find( inNames.begin(), inNames.end(), names ) != inNames.end() ) { return inNamesData; } - InternedStringVectorDataPtr resultData = inNamesData->copy(); - resultData->writable().push_back( tokenizedNames[0] ); + resultData->writable().push_back( names ); return resultData; } - // inserting the new names into the vector - // while making sure we don't have duplicates - InternedStringVectorDataPtr resultData = inNamesData->copy(); + // Slow path. Merge names ignoring duplicates and wildcards. + + vector tokenizedNames; + StringAlgo::tokenize( names, ' ', tokenizedNames ); + InternedStringVectorDataPtr resultData = inNamesData->copy(); std::vector &result = resultData->writable(); result.reserve( result.size() + tokenizedNames.size() ); - std::copy( tokenizedNames.begin(), tokenizedNames.end(), std::back_inserter( result ) ); + std::copy_if( + tokenizedNames.begin(), tokenizedNames.end(), std::back_inserter( result ), + [] ( const InternedString &s ) { + return !StringAlgo::hasWildcards( s.string() ); + } + ); + std::sort( result.begin(), result.end() ); - std::vector::iterator it; - it = std::unique( result.begin(), result.end() ); - result.resize( std::distance( result.begin(), it ) ); + result.erase( std::unique( result.begin(), result.end() ), result.end() ); return resultData; } void Set::hashSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent, IECore::MurmurHash &h ) const { - const std::string allSets = " " + namePlug()->getValue() + " "; - const std::string setNameToFind = " " + setName.string() + " "; - if( allSets.find( setNameToFind ) == std::string::npos ) + if( !StringAlgo::matchMultiple( setName.string(), namePlug()->getValue() ) ) { h = inPlug()->setPlug()->hash(); return; @@ -282,16 +340,14 @@ void Set::hashSet( const IECore::InternedString &setName, const Gaffer::Context FilteredSceneProcessor::hashSet( setName, context, parent, h ); inPlug()->setPlug()->hash( h ); - ScenePlug::GlobalScope globalScope( context ); + PathMatcherScope scope( context, setVariablePlug()->getValue(), setName ); modePlug()->hash( h ); pathMatcherPlug()->hash( h ); } IECore::ConstPathMatcherDataPtr Set::computeSet( const IECore::InternedString &setName, const Gaffer::Context *context, const ScenePlug *parent ) const { - const std::string allSets = " " + namePlug()->getValue() + " "; - const std::string setNameToFind = " " + setName.string() + " "; - if( allSets.find( setNameToFind ) == std::string::npos ) + if( !StringAlgo::matchMultiple( setName.string(), namePlug()->getValue() ) ) { return inPlug()->setPlug()->getValue(); } @@ -299,7 +355,7 @@ IECore::ConstPathMatcherDataPtr Set::computeSet( const IECore::InternedString &s Mode mode; ConstPathMatcherDataPtr pathMatcher; { - ScenePlug::GlobalScope globalScope( context ); + PathMatcherScope scope( context, setVariablePlug()->getValue(), setName ); mode = static_cast( modePlug()->getValue() ); pathMatcher = pathMatcherPlug()->getValue(); } diff --git a/src/GafferScene/SetAlgo.cpp b/src/GafferScene/SetAlgo.cpp index ded4414287c..dfd3a005e2d 100644 --- a/src/GafferScene/SetAlgo.cpp +++ b/src/GafferScene/SetAlgo.cpp @@ -239,7 +239,7 @@ struct AstEvaluator continue; } - setScope.setSetName( setName ); + setScope.setSetName( &setName ); ConstPathMatcherDataPtr setData = m_scene->setPlug()->getValue(); result.addPaths( setData->readable() ); } @@ -358,7 +358,7 @@ struct AstHasher continue; } - setScope.setSetName( setName ); + setScope.setSetName( &setName ); m_hash.append( m_scene->setPlug()->hash() ); } } diff --git a/src/GafferScene/SetFilter.cpp b/src/GafferScene/SetFilter.cpp index 224a3e5b260..9b116224607 100644 --- a/src/GafferScene/SetFilter.cpp +++ b/src/GafferScene/SetFilter.cpp @@ -145,13 +145,7 @@ void SetFilter::hashMatch( const ScenePlug *scene, const Gaffer::Context *contex /// the scene then we would be able to use that in these situations and have a broader range /// of filters. If we manage that, then we should go back to throwing an exception here if /// the context doesn't contain a path. We should then do the same in the PathFilter. - typedef IECore::TypedData ScenePathData; - const ScenePathData *pathData = context->get( ScenePlug::scenePathContextName, nullptr ); - if( pathData ) - { - const ScenePlug::ScenePath &path = pathData->readable(); - h.append( &(path[0]), path.size() ); - } + h.append( context->variableHash( ScenePlug::scenePathContextName ) ); Gaffer::Context::EditableScope expressionResultScope( context ); expressionResultScope.remove( ScenePlug::scenePathContextName ); diff --git a/src/GafferScene/SetVisualiser.cpp b/src/GafferScene/SetVisualiser.cpp index 0c392cae3cc..8ff13626465 100644 --- a/src/GafferScene/SetVisualiser.cpp +++ b/src/GafferScene/SetVisualiser.cpp @@ -62,7 +62,7 @@ namespace bool internedStringCompare( InternedString a, InternedString b ) { - return a.string() < b.string(); + return a.string() < b.string(); } typedef std::pair Override; @@ -71,7 +71,7 @@ std::vector unpackOverrides( const CompoundDataPlug *plug ) std::vector overrides; std::string name; - for( NameValuePlugIterator it( plug ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( plug ); !it.done(); ++it ) { // This will fail if the member has been disabled, or has no name if( ConstDataPtr plugData = plug->memberDataAndName( it->get(), name ) ) diff --git a/src/GafferScene/Shader.cpp b/src/GafferScene/Shader.cpp index a1877fef109..e76e89dc989 100644 --- a/src/GafferScene/Shader.cpp +++ b/src/GafferScene/Shader.cpp @@ -353,7 +353,7 @@ class Shader::NetworkBuilder if( !isLeafParameter( parameter ) || parameter->parent() ) { // Compound parameter - recurse - for( InputPlugIterator it( parameter ); !it.done(); ++it ) + for( Plug::InputIterator it( parameter ); !it.done(); ++it ) { hashParameterWalk( it->get(), h ); } @@ -361,7 +361,7 @@ class Shader::NetworkBuilder else if( const Gaffer::ArrayPlug *arrayParameter = IECore::runTimeCast( parameter ) ) { // Array parameter - for( InputPlugIterator it( arrayParameter ); !it.done(); ++it ) + for( Plug::InputIterator it( arrayParameter ); !it.done(); ++it ) { hashParameter( it->get(), h ); } @@ -378,7 +378,7 @@ class Shader::NetworkBuilder if( !isLeafParameter( parameter ) || parameter->parent() ) { // Compound parameter - recurse - for( InputPlugIterator it( parameter ); !it.done(); ++it ) + for( Plug::InputIterator it( parameter ); !it.done(); ++it ) { IECore::InternedString childParameterName; if( parameterName.string().size() ) @@ -396,7 +396,7 @@ class Shader::NetworkBuilder else if( const Gaffer::ArrayPlug *array = IECore::runTimeCast( parameter ) ) { int i = 0; - for( InputPlugIterator it( array ); !it.done(); ++it, ++i ) + for( Plug::InputIterator it( array ); !it.done(); ++it, ++i ) { IECore::InternedString childParameterName = parameterName.string() + "[" + std::to_string( i ) + "]"; addParameter( it->get(), childParameterName, shader, connections ); @@ -471,7 +471,7 @@ class Shader::NetworkBuilder { return; } - for( InputPlugIterator it( parameter ); !it.done(); ++it ) + for( Plug::InputIterator it( parameter ); !it.done(); ++it ) { const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get() ); if( effectiveParameter && isOutputParameter( effectiveParameter ) ) @@ -493,7 +493,7 @@ class Shader::NetworkBuilder { return; } - for( InputPlugIterator it( parameter ); !it.done(); ++it ) + for( Plug::InputIterator it( parameter ); !it.done(); ++it ) { const Gaffer::Plug *effectiveParameter = this->effectiveParameter( it->get() ); if( effectiveParameter && isOutputParameter( effectiveParameter ) ) @@ -727,7 +727,7 @@ void Shader::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs { if( !out->children().empty() ) { - for( RecursivePlugIterator it( out ); !it.done(); it++ ) + for( Plug::RecursiveIterator it( out ); !it.done(); it++ ) { if( (*it)->children().empty() ) { @@ -767,9 +767,9 @@ void Shader::hash( const Gaffer::ValuePlug *output, const Gaffer::Context *conte { ComputeNode::hash( output, context, h ); const Plug *outputParameter = outPlug(); - if( auto *name = context->get( g_outputParameterContextName, nullptr ) ) + if( const std::string *name = context->getIfExists< std::string >( g_outputParameterContextName ) ) { - outputParameter = outputParameter->descendant( name->readable() ); + outputParameter = outputParameter->descendant( *name ); } attributesHash( outputParameter, h ); return; @@ -793,9 +793,9 @@ void Shader::compute( Gaffer::ValuePlug *output, const Gaffer::Context *context if( output == outAttributesPlug() ) { const Plug *outputParameter = outPlug(); - if( auto *name = context->get( g_outputParameterContextName, nullptr ) ) + if( const std::string *name = context->getIfExists< std::string >( g_outputParameterContextName ) ) { - outputParameter = outputParameter->descendant( name->readable() ); + outputParameter = outputParameter->descendant( *name ); } static_cast( output )->setValue( attributes( outputParameter ) ); return; diff --git a/src/GafferScene/ShaderPlug.cpp b/src/GafferScene/ShaderPlug.cpp index 894c2f45359..f736507d9a2 100644 --- a/src/GafferScene/ShaderPlug.cpp +++ b/src/GafferScene/ShaderPlug.cpp @@ -169,7 +169,7 @@ bool ShaderPlug::acceptsInput( const Gaffer::Plug *input ) const ) { // Reject switches which have inputs from non-shader nodes. - for( PlugIterator it( switchNode->inPlugs() ); !it.done(); ++it ) + for( Plug::Iterator it( switchNode->inPlugs() ); !it.done(); ++it ) { if( (*it)->getInput() && !isShaderOutPlug( (*it)->source() ) ) { @@ -217,9 +217,11 @@ IECore::MurmurHash ShaderPlug::attributesHash() const if( auto s = runTimeCast( p->node() ) ) { Context::EditableScope scope( Context::current() ); + std::string outputParameter; if( p != s->outPlug() ) { - scope.set( Shader::g_outputParameterContextName, p->relativeName( s->outPlug() ) ); + outputParameter = p->relativeName( s->outPlug() ); + scope.set( Shader::g_outputParameterContextName, &outputParameter ); } h = s->outAttributesPlug()->hash(); } @@ -235,9 +237,11 @@ IECore::ConstCompoundObjectPtr ShaderPlug::attributes() const if( auto s = runTimeCast( p->node() ) ) { Context::EditableScope scope( Context::current() ); + std::string outputParameter; if( p != s->outPlug() ) { - scope.set( Shader::g_outputParameterContextName, p->relativeName( s->outPlug() ) ); + outputParameter = p->relativeName( s->outPlug() ); + scope.set( Shader::g_outputParameterContextName, &outputParameter ); } return s->outAttributesPlug()->getValue(); } diff --git a/src/GafferScene/Sphere.cpp b/src/GafferScene/Sphere.cpp index 0d77dbd824d..dab8388d4e4 100644 --- a/src/GafferScene/Sphere.cpp +++ b/src/GafferScene/Sphere.cpp @@ -165,6 +165,6 @@ IECore::ConstObjectPtr Sphere::computeSource( const Context *context ) const } else { - return MeshPrimitive::createSphere( radius, zMin, zMax, thetaMax, divisionsPlug()->getValue() ); + return MeshPrimitive::createSphere( radius, zMin, zMax, thetaMax, divisionsPlug()->getValue(), context->canceller() ); } } diff --git a/src/GafferScene/StandardOptions.cpp b/src/GafferScene/StandardOptions.cpp index ee4a33348d2..59ab41f2414 100644 --- a/src/GafferScene/StandardOptions.cpp +++ b/src/GafferScene/StandardOptions.cpp @@ -67,7 +67,6 @@ StandardOptions::StandardOptions( const std::string &name ) // Motion blur - options->addChild( new Gaffer::NameValuePlug( "render:cameraBlur", new IECore::BoolData( false ), false, "cameraBlur" ) ); options->addChild( new Gaffer::NameValuePlug( "render:transformBlur", new IECore::BoolData( false ), false, "transformBlur" ) ); options->addChild( new Gaffer::NameValuePlug( "render:deformationBlur", new IECore::BoolData( false ), false, "deformationBlur" ) ); options->addChild( new Gaffer::NameValuePlug( "render:shutter", new IECore::V2fData( Imath::V2f( -0.25, 0.25 ) ), false, "shutter" ) ); diff --git a/src/GafferScene/Transform.cpp b/src/GafferScene/Transform.cpp index b070b7f3f33..158adf348bb 100644 --- a/src/GafferScene/Transform.cpp +++ b/src/GafferScene/Transform.cpp @@ -206,7 +206,7 @@ Imath::M44f Transform::relativeParentTransform( const ScenePath &path, const Gaf while( ancestorPath.size() ) // Root transform is always identity so can be ignored { ancestorPath.pop_back(); - pathScope.setPath( ancestorPath ); + pathScope.setPath( &ancestorPath ); if( filterValue( pathScope.context() ) & IECore::PathMatcher::ExactMatch ) { matchingAncestorFound = true; @@ -228,7 +228,7 @@ IECore::MurmurHash Transform::relativeParentTransformHash( const ScenePath &path while( ancestorPath.size() ) // Root transform is always identity so can be ignored { ancestorPath.pop_back(); - pathScope.setPath( ancestorPath ); + pathScope.setPath( &ancestorPath ); if( filterValue( pathScope.context() ) & IECore::PathMatcher::ExactMatch ) { result.append( true ); diff --git a/src/GafferScene/TransformQuery.cpp b/src/GafferScene/TransformQuery.cpp new file mode 100644 index 00000000000..b90cf25e0e8 --- /dev/null +++ b/src/GafferScene/TransformQuery.cpp @@ -0,0 +1,399 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2021, 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/TransformQuery.h" + +#include "OpenEXR/ImathMatrixAlgo.h" +#include "OpenEXR/ImathEuler.h" + +#include +#include +#include // NOTE : M_PI + +namespace +{ + +float ensurePositiveZero( float const value ) +{ + return ( value == 0.0f ) ? std::fabs( value ) : value; +} + +void +setV3fPlugComponentValue( Gaffer::V3fPlug const& parent, Gaffer::NumericPlug< float >& child, Imath::V3f const& value ) +{ + float cv; + + if( & child == parent.getChild( 0 ) ) + { + cv = value.x; + } + else if( & child == parent.getChild( 1 ) ) + { + cv = value.y; + } + else if( & child == parent.getChild( 2 ) ) + { + cv = value.z; + } + else + { + assert( 0 ); // NOTE : Unknown child plug + cv = 0.f; + } + + child.setValue( ensurePositiveZero( cv ) ); +} + +} // namespace + +namespace GafferScene +{ + +size_t TransformQuery::g_firstPlugIndex = 0; + +GAFFER_NODE_DEFINE_TYPE( TransformQuery ); + +TransformQuery::TransformQuery( std::string const& name ) +: Gaffer::ComputeNode( name ) +{ + storeIndexOfNextChild( g_firstPlugIndex ); + addChild( new ScenePlug( "scene" ) ); + addChild( new Gaffer::StringPlug( "location" ) ); + addChild( new Gaffer::IntPlug( "space", Gaffer::Plug::In, + static_cast< int >( Space::World ), + 0, static_cast< int >( Space::Relative ) ) ); + addChild( new Gaffer::StringPlug( "relativeLocation" ) ); + addChild( new Gaffer::BoolPlug( "invert" ) ); + addChild( new Gaffer::M44fPlug( "matrix", Gaffer::Plug::Out ) ); + addChild( new Gaffer::V3fPlug( "translate", Gaffer::Plug::Out ) ); + addChild( new Gaffer::V3fPlug( "rotate", Gaffer::Plug::Out ) ); + addChild( new Gaffer::V3fPlug( "scale", Gaffer::Plug::Out ) ); +} + +TransformQuery::~TransformQuery() +{} + +ScenePlug* TransformQuery::scenePlug() +{ + return const_cast< ScenePlug* >( + static_cast< TransformQuery const* >( this )->scenePlug() ); +} + +ScenePlug const* TransformQuery::scenePlug() const +{ + return getChild< ScenePlug >( g_firstPlugIndex ); +} + +Gaffer::StringPlug* TransformQuery::locationPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< TransformQuery const* >( this )->locationPlug() ); +} + +Gaffer::StringPlug const* TransformQuery::locationPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 1 ); +} + +Gaffer::IntPlug* TransformQuery::spacePlug() +{ + return const_cast< Gaffer::IntPlug* >( + static_cast< TransformQuery const* >( this )->spacePlug() ); +} + +Gaffer::IntPlug const* TransformQuery::spacePlug() const +{ + return getChild< Gaffer::IntPlug >( g_firstPlugIndex + 2 ); +} + +Gaffer::StringPlug* TransformQuery::relativeLocationPlug() +{ + return const_cast< Gaffer::StringPlug* >( + static_cast< TransformQuery const* >( this )->relativeLocationPlug() ); +} + +Gaffer::StringPlug const* TransformQuery::relativeLocationPlug() const +{ + return getChild< Gaffer::StringPlug >( g_firstPlugIndex + 3 ); +} + +Gaffer::BoolPlug* TransformQuery::invertPlug() +{ + return const_cast< Gaffer::BoolPlug* >( + static_cast< TransformQuery const* >( this )->invertPlug() ); +} + +Gaffer::BoolPlug const* TransformQuery::invertPlug() const +{ + return getChild< Gaffer::BoolPlug >( g_firstPlugIndex + 4 ); +} + +Gaffer::M44fPlug* TransformQuery::matrixPlug() +{ + return const_cast< Gaffer::M44fPlug* >( + static_cast< TransformQuery const* >( this )->matrixPlug() ); +} + +Gaffer::M44fPlug const* TransformQuery::matrixPlug() const +{ + return getChild< Gaffer::M44fPlug >( g_firstPlugIndex + 5 ); +} + +Gaffer::V3fPlug* TransformQuery::translatePlug() +{ + return const_cast< Gaffer::V3fPlug* >( + static_cast< TransformQuery const* >( this )->translatePlug() ); +} + +Gaffer::V3fPlug const* TransformQuery::translatePlug() const +{ + return getChild< Gaffer::V3fPlug >( g_firstPlugIndex + 6 ); +} + +Gaffer::V3fPlug* TransformQuery::rotatePlug() +{ + return const_cast< Gaffer::V3fPlug* >( + static_cast< TransformQuery const* >( this )->rotatePlug() ); +} + +Gaffer::V3fPlug const* TransformQuery::rotatePlug() const +{ + return getChild< Gaffer::V3fPlug >( g_firstPlugIndex + 7 ); +} + +Gaffer::V3fPlug* TransformQuery::scalePlug() +{ + return const_cast< Gaffer::V3fPlug* >( + static_cast< TransformQuery const* >( this )->scalePlug() ); +} + +Gaffer::V3fPlug const* TransformQuery::scalePlug() const +{ + return getChild< Gaffer::V3fPlug >( g_firstPlugIndex + 8 ); +} + +void TransformQuery::affects( Gaffer::Plug const* const input, AffectedPlugsContainer& outputs ) const +{ + ComputeNode::affects( input, outputs ); + + if( input == matrixPlug() ) + { + outputs.push_back( translatePlug()->getChild( 0 ) ); + outputs.push_back( translatePlug()->getChild( 1 ) ); + outputs.push_back( translatePlug()->getChild( 2 ) ); + outputs.push_back( rotatePlug()->getChild( 0 ) ); + outputs.push_back( rotatePlug()->getChild( 1 ) ); + outputs.push_back( rotatePlug()->getChild( 2 ) ); + outputs.push_back( scalePlug()->getChild( 0 ) ); + outputs.push_back( scalePlug()->getChild( 1 ) ); + outputs.push_back( scalePlug()->getChild( 2 ) ); + } + else if( + ( input == spacePlug() ) || + ( input == invertPlug() ) || + ( input == locationPlug() ) || + ( input == relativeLocationPlug() ) || + ( input == scenePlug()->existsPlug() ) || + ( input == scenePlug()->transformPlug() ) ) + { + outputs.push_back( matrixPlug() ); + } +} + +void TransformQuery::hash( Gaffer::ValuePlug const* const output, Gaffer::Context const* const context, IECore::MurmurHash& h ) const +{ + ComputeNode::hash( output, context, h ); + + if( output == matrixPlug() ) + { + std::string const loc = locationPlug()->getValue(); + if( ! loc.empty() ) + { + ScenePlug const* const splug = scenePlug(); + assert( splug != 0 ); + + ScenePlug::ScenePath const path = ScenePlug::stringToPath( loc ); + + if( splug->exists( path ) ) + { + switch( static_cast< Space >( spacePlug()->getValue() ) ) + { + case Space::Local: + { + if( invertPlug()->getValue() ) + { + h.append( splug->transformHash( path ) ); + } + else + { + h = splug->transformHash( path ); + } + break; + } + case Space::World: + { + h.append( splug->fullTransformHash( path ) ); + h.append( invertPlug()->hash() ); + } + case Space::Relative: + { + std::string const rloc = relativeLocationPlug()->getValue(); + + if( ! rloc.empty() ) + { + ScenePlug::ScenePath const rpath = ScenePlug::stringToPath( rloc ); + + if( + ( rpath != path ) && + ( splug->exists( rpath ) ) ) + { + h.append( splug->fullTransformHash( path ) ); + h.append( splug->fullTransformHash( rpath ) ); + invertPlug()->hash( h ); + } + } + break; + } + default: + break; + } + } + } + } + else + { + Gaffer::GraphComponent const* const parent = output->parent(); + + if( + ( parent == translatePlug() ) || + ( parent == rotatePlug() ) || + ( parent == scalePlug() ) ) + { + matrixPlug()->hash( h ); + } + } +} + +void TransformQuery::compute( Gaffer::ValuePlug* const output, Gaffer::Context const* const context ) const +{ + if( output == matrixPlug() ) + { + Imath::M44f m; + + std::string const loc = locationPlug()->getValue(); + if( ! loc.empty() ) + { + ScenePlug const* const splug = scenePlug(); + assert( splug != 0 ); + + ScenePlug::ScenePath const path = ScenePlug::stringToPath( loc ); + + if( splug->exists( path ) ) + { + switch( static_cast< Space >( spacePlug()->getValue() ) ) + { + case Space::Local: + m = splug->transform( path ); + break; + case Space::World: + m = splug->fullTransform( path ); + break; + case Space::Relative: + { + std::string const rloc = relativeLocationPlug()->getValue(); + + if( ! rloc.empty() ) + { + ScenePlug::ScenePath const rpath = ScenePlug::stringToPath( rloc ); + + if( + ( rpath != path ) && + ( splug->exists( rpath ) ) ) + { + m = splug->fullTransform( path ) * splug->fullTransform( rpath ).inverse(); + } + } + break; + } + default: + break; + } + + if( invertPlug()->getValue() ) + { + m.invert(); + } + } + } + + IECore::assertedStaticCast< Gaffer::M44fPlug >( output )->setValue( m ); + } + else + { + Gaffer::GraphComponent* const parent = output->parent(); + + if( parent == translatePlug() ) + { + Imath::M44f const m = matrixPlug()->getValue(); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), m.translation() ); + } + else if( parent == rotatePlug() ) + { + Imath::Eulerf::Order order = Imath::Eulerf::XYZ; + Imath::M44f const m = Imath::sansScalingAndShear( matrixPlug()->getValue() ); + Imath::Eulerf euler( m, order ); + euler *= static_cast< float >( 180.0 / M_PI ); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), euler ); + } + else if( parent == scalePlug() ) + { + Imath::V3f scale( 0.f ); + Imath::M44f const m = matrixPlug()->getValue(); + Imath::extractScaling( m, scale ); + setV3fPlugComponentValue( + *( IECore::assertedStaticCast< Gaffer::V3fPlug >( parent ) ), + *( IECore::assertedStaticCast< Gaffer::NumericPlug< float > >( output ) ), scale ); + } + } + + ComputeNode::compute( output, context ); +} + +} // GafferScene diff --git a/src/GafferScene/TweakPlug.cpp b/src/GafferScene/TweakPlug.cpp index e516fa5c991..5783194afdb 100644 --- a/src/GafferScene/TweakPlug.cpp +++ b/src/GafferScene/TweakPlug.cpp @@ -348,7 +348,7 @@ bool TweakPlug::applyTweaks( const Plug *tweaksPlug, IECoreScene::ShaderNetwork bool appliedTweaks = false; bool removedConnections = false; - for( TweakPlugIterator tIt( tweaksPlug ); !tIt.done(); ++tIt ) + for( TweakPlug::Iterator tIt( tweaksPlug ); !tIt.done(); ++tIt ) { const TweakPlug *tweakPlug = tIt->get(); const std::string name = tweakPlug->namePlug()->getValue(); @@ -525,7 +525,7 @@ bool TweaksPlug::acceptsInput( const Plug *input ) const Gaffer::PlugPtr TweaksPlug::createCounterpart( const std::string &name, Direction direction ) const { PlugPtr result = new TweaksPlug( name, direction, getFlags() ); - for( PlugIterator it( this ); !it.done(); ++it ) + for( Plug::Iterator it( this ); !it.done(); ++it ) { result->addChild( (*it)->createCounterpart( (*it)->getName(), direction ) ); } @@ -535,7 +535,7 @@ Gaffer::PlugPtr TweaksPlug::createCounterpart( const std::string &name, Directio bool TweaksPlug::applyTweaks( IECore::CompoundData *parameters, TweakPlug::MissingMode missingMode ) const { bool applied = false; - for( TweakPlugIterator it( this ); !it.done(); ++it ) + for( TweakPlug::Iterator it( this ); !it.done(); ++it ) { if( (*it)->applyTweak( parameters, missingMode ) ) { diff --git a/src/GafferScene/UDIMQuery.cpp b/src/GafferScene/UDIMQuery.cpp index 8d55564b53c..ca7a24da498 100644 --- a/src/GafferScene/UDIMQuery.cpp +++ b/src/GafferScene/UDIMQuery.cpp @@ -329,3 +329,22 @@ void UDIMQuery::compute( Gaffer::ValuePlug *output, const Gaffer::Context *conte ComputeNode::compute( output, context ); } } + +Gaffer::ValuePlug::CachePolicy UDIMQuery::computeCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( output == outPlug() ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } + return ComputeNode::computeCachePolicy( output ); +} + +Gaffer::ValuePlug::CachePolicy UDIMQuery::hashCachePolicy( const Gaffer::ValuePlug *output ) const +{ + if( output == outPlug() ) + { + return ValuePlug::CachePolicy::TaskCollaboration; + } + return ComputeNode::hashCachePolicy( output ); +} + diff --git a/src/GafferScene/Unencapsulate.cpp b/src/GafferScene/Unencapsulate.cpp index a030f0ff1e6..f44ad6e1b6d 100644 --- a/src/GafferScene/Unencapsulate.cpp +++ b/src/GafferScene/Unencapsulate.cpp @@ -38,6 +38,8 @@ #include "GafferScene/Capsule.h" +#include "Gaffer/StringPlug.h" + #include "IECore/NullObject.h" using namespace std; @@ -63,10 +65,10 @@ class CapsuleScope : boost::noncopyable private : // Base constructor used by the two public constructors CapsuleScope( - const Gaffer::Context *context, const ScenePlug *inPlug, const ScenePlug::ScenePath &parentPath + const Gaffer::Context *context, const ScenePlug *inPlug, const ScenePlug::ScenePath &sourcePath ) { - m_object = inPlug->object( parentPath ); + m_object = inPlug->object( sourcePath ); m_capsule = IECore::runTimeCast< const Capsule >( m_object.get() ); } @@ -74,20 +76,21 @@ class CapsuleScope : boost::noncopyable CapsuleScope( const Gaffer::Context *context, const ScenePlug *inPlug, - const ScenePlug::ScenePath &parentPath, const ScenePlug::ScenePath &branchPath - ) : CapsuleScope( context, inPlug, parentPath ) + const ScenePlug::ScenePath &sourcePath, const ScenePlug::ScenePath &branchPath + ) : CapsuleScope( context, inPlug, sourcePath ) { if( m_capsule ) { m_scope.emplace( m_capsule->context() ); - m_scope->set( ScenePlug::scenePathContextName, concatScenePath( m_capsule->root(), branchPath ) ); + m_capsulePath = concatScenePath( m_capsule->root(), branchPath ); + m_scope->set( ScenePlug::scenePathContextName, &m_capsulePath ); } } CapsuleScope( const Gaffer::Context *context, const ScenePlug *inPlug, - const ScenePlug::ScenePath &parentPath, const InternedString &setName - ) : CapsuleScope( context, inPlug, parentPath ) + const ScenePlug::ScenePath &sourcePath, const InternedString *setName + ) : CapsuleScope( context, inPlug, sourcePath ) { if( m_capsule ) { @@ -131,6 +134,7 @@ class CapsuleScope : boost::noncopyable boost::optional m_scope; IECore::ConstObjectPtr m_object; const Capsule* m_capsule; + ScenePlug::ScenePath m_capsulePath; }; @@ -143,6 +147,10 @@ size_t Unencapsulate::g_firstPlugIndex = 0; Unencapsulate::Unencapsulate( const std::string &name ) : BranchCreator( name ) { + // Hide `destination` plug until we resolve issues surrounding `processesRootObject()`. + // See `BranchCreator::computeObject()`. Or perhaps we would never want to allow a + // different destination anyway? + destinationPlug()->setName( "__destination" ); storeIndexOfNextChild( g_firstPlugIndex ); } @@ -155,15 +163,15 @@ bool Unencapsulate::affectsBranchBound( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); h = cs.scene( true )->boundPlug()->hash(); } -Imath::Box3f Unencapsulate::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::Box3f Unencapsulate::computeBranchBound( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); return cs.scene( true )->boundPlug()->getValue(); } @@ -172,15 +180,15 @@ bool Unencapsulate::affectsBranchTransform( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); h = cs.scene( true )->transformPlug()->hash(); } -Imath::M44f Unencapsulate::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +Imath::M44f Unencapsulate::computeBranchTransform( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); return cs.scene( true )->transformPlug()->getValue(); } @@ -189,15 +197,15 @@ bool Unencapsulate::affectsBranchAttributes( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); h = cs.scene( true )->attributesPlug()->hash(); } -IECore::ConstCompoundObjectPtr Unencapsulate::computeBranchAttributes( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstCompoundObjectPtr Unencapsulate::computeBranchAttributes( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); return cs.scene( true )->attributesPlug()->getValue(); } @@ -211,9 +219,9 @@ bool Unencapsulate::processesRootObject() const return true; } -void Unencapsulate::hashBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); if( branchPath.size() == 0 && !cs.scene( false ) ) { h = inPlug()->objectPlug()->hash(); @@ -224,9 +232,9 @@ void Unencapsulate::hashBranchObject( const ScenePath &parentPath, const ScenePa } } -IECore::ConstObjectPtr Unencapsulate::computeBranchObject( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstObjectPtr Unencapsulate::computeBranchObject( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); if( branchPath.size() == 0 && !cs.scene( false ) ) { // Not inside capsule, just pass through input scene object @@ -240,9 +248,9 @@ bool Unencapsulate::affectsBranchChildNames( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); if( !cs.scene( false ) ) { h = outPlug()->childNamesPlug()->defaultValue()->Object::hash(); @@ -253,9 +261,9 @@ void Unencapsulate::hashBranchChildNames( const ScenePath &parentPath, const Sce } } -IECore::ConstInternedStringVectorDataPtr Unencapsulate::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Unencapsulate::computeBranchChildNames( const ScenePath &sourcePath, const ScenePath &branchPath, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, branchPath ); + CapsuleScope cs( context, inPlug(), sourcePath, branchPath ); if( !cs.scene( false ) ) { return outPlug()->childNamesPlug()->defaultValue(); @@ -271,12 +279,12 @@ bool Unencapsulate::affectsBranchSetNames( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { h = inPlug()->setNamesPlug()->hash(); } -IECore::ConstInternedStringVectorDataPtr Unencapsulate::computeBranchSetNames( const ScenePath &parentPath, const Gaffer::Context *context ) const +IECore::ConstInternedStringVectorDataPtr Unencapsulate::computeBranchSetNames( const ScenePath &sourcePath, const Gaffer::Context *context ) const { // We have a standard that any scene containing capsules must contain all the sets used in the capsules // in their list of set names, even if those sets are empty until the capsules are expanded @@ -288,23 +296,23 @@ bool Unencapsulate::affectsBranchSet( const Gaffer::Plug *input ) const return input == inPlug()->objectPlug(); } -void Unencapsulate::hashBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void Unencapsulate::hashBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - CapsuleScope cs( context, inPlug(), parentPath, setName ); + CapsuleScope cs( context, inPlug(), sourcePath, &setName ); if( !cs.scene( false ) ) { h = outPlug()->setPlug()->defaultValue()->Object::hash(); return; } - BranchCreator::hashBranchSet( parentPath, setName, context, h ); + BranchCreator::hashBranchSet( sourcePath, setName, context, h ); h.append( cs.scene( false )->setPlug()->hash() ); h.append( cs.root().data(), cs.root().size() ); } -IECore::ConstPathMatcherDataPtr Unencapsulate::computeBranchSet( const ScenePath &parentPath, const IECore::InternedString &setName, const Gaffer::Context *context ) const +IECore::ConstPathMatcherDataPtr Unencapsulate::computeBranchSet( const ScenePath &sourcePath, const IECore::InternedString &setName, const Gaffer::Context *context ) const { - CapsuleScope cs( context, inPlug(), parentPath, setName ); + CapsuleScope cs( context, inPlug(), sourcePath, &setName ); if( !cs.scene( false ) ) { return outPlug()->setPlug()->defaultValue(); diff --git a/src/GafferScene/UnionFilter.cpp b/src/GafferScene/UnionFilter.cpp index 64f728e2b88..0a7b45687a1 100644 --- a/src/GafferScene/UnionFilter.cpp +++ b/src/GafferScene/UnionFilter.cpp @@ -65,7 +65,7 @@ void UnionFilter::affects( const Gaffer::Plug *input, AffectedPlugsContainer &ou void UnionFilter::hashMatch( const ScenePlug *scene, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - for( InputIntPlugIterator it( inPlugs() ); !it.done(); ++it ) + for( IntPlug::InputIterator it( inPlugs() ); !it.done(); ++it ) { (*it)->hash( h ); } @@ -74,7 +74,7 @@ void UnionFilter::hashMatch( const ScenePlug *scene, const Gaffer::Context *cont unsigned UnionFilter::computeMatch( const ScenePlug *scene, const Gaffer::Context *context ) const { unsigned result = IECore::PathMatcher::NoMatch; - for( InputIntPlugIterator it( inPlugs() ); !it.done(); ++it ) + for( IntPlug::InputIterator it( inPlugs() ); !it.done(); ++it ) { result |= (*it)->getValue(); } diff --git a/src/GafferSceneModule/CoreBinding.cpp b/src/GafferSceneModule/CoreBinding.cpp index 010c999fd29..815aa39448c 100644 --- a/src/GafferSceneModule/CoreBinding.cpp +++ b/src/GafferSceneModule/CoreBinding.cpp @@ -42,6 +42,7 @@ #include "GafferScene/SceneElementProcessor.h" #include "GafferScene/SceneNode.h" #include "GafferScene/SceneProcessor.h" +#include "GafferScene/BoundQuery.h" #include "GafferBindings/ComputeNodeBinding.h" #include "GafferBindings/PlugBinding.h" @@ -407,4 +408,13 @@ void GafferSceneModule::bindCore() Serialisation::registerSerialiser( SceneProcessor::staticTypeId(), new SceneProcessorSerialiser ); + { + scope s = GafferBindings::DependencyNodeClass(); + + enum_( "Space" ) + .value( "Local", BoundQuery::Space::Local ) + .value( "World", BoundQuery::Space::World ) + .value( "Relative", BoundQuery::Space::Relative ) + ; + } } diff --git a/src/GafferSceneModule/FilterBinding.cpp b/src/GafferSceneModule/FilterBinding.cpp index 282c6c158b3..10f88c5aa34 100644 --- a/src/GafferSceneModule/FilterBinding.cpp +++ b/src/GafferSceneModule/FilterBinding.cpp @@ -41,6 +41,7 @@ #include "GafferScene/Filter.h" #include "GafferScene/FilterPlug.h" #include "GafferScene/FilterProcessor.h" +#include "GafferScene/FilterQuery.h" #include "GafferScene/FilterResults.h" #include "GafferScene/PathFilter.h" #include "GafferScene/ScenePlug.h" @@ -110,5 +111,6 @@ void GafferSceneModule::bindFilter() GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); GafferBindings::DependencyNodeClass(); + GafferBindings::DependencyNodeClass(); } diff --git a/src/GafferSceneModule/GafferSceneModule.cpp b/src/GafferSceneModule/GafferSceneModule.cpp index d7def876b06..f8412fea4b3 100644 --- a/src/GafferSceneModule/GafferSceneModule.cpp +++ b/src/GafferSceneModule/GafferSceneModule.cpp @@ -53,7 +53,6 @@ #include "PrimitivesBinding.h" #include "RenderBinding.h" #include "RenderControllerBinding.h" -#include "RendererAlgoBinding.h" #include "SceneAlgoBinding.h" #include "ScenePathBinding.h" #include "SetAlgoBinding.h" @@ -73,7 +72,6 @@ BOOST_PYTHON_MODULE( _GafferScene ) bindOptions(); bindAttributes(); bindSceneAlgo(); - bindRendererAlgo(); bindSetAlgo(); bindPrimitives(); bindScenePath(); diff --git a/src/GafferSceneModule/HierarchyBinding.cpp b/src/GafferSceneModule/HierarchyBinding.cpp index 1e22edf1010..7d472cb3bb8 100644 --- a/src/GafferSceneModule/HierarchyBinding.cpp +++ b/src/GafferSceneModule/HierarchyBinding.cpp @@ -42,6 +42,7 @@ #include "GafferScene/CollectScenes.h" #include "GafferScene/Duplicate.h" #include "GafferScene/Encapsulate.h" +#include "GafferScene/ExistenceQuery.h" #include "GafferScene/Group.h" #include "GafferScene/Instancer.h" #include "GafferScene/Isolate.h" @@ -162,4 +163,6 @@ void GafferSceneModule::bindHierarchy() .value( "Fixed", MotionPath::SamplingMode::Fixed ) ; } + + GafferBindings::DependencyNodeClass(); } diff --git a/src/GafferSceneModule/PrimitivesBinding.cpp b/src/GafferSceneModule/PrimitivesBinding.cpp index 50265c8fad2..f38fe29c4c0 100644 --- a/src/GafferSceneModule/PrimitivesBinding.cpp +++ b/src/GafferSceneModule/PrimitivesBinding.cpp @@ -68,7 +68,7 @@ namespace { class LightSerialiser : public GafferBindings::NodeSerialiser { - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const GafferBindings::Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, GafferBindings::Serialisation &serialisation ) const override { std::string defaultPC = GafferBindings::NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); const GafferScene::Light *light = static_cast( graphComponent ); @@ -76,7 +76,7 @@ class LightSerialiser : public GafferBindings::NodeSerialiser // \todo - Remove this once old scripts have been converted // Before we start serialization, clean up any old scripts that might have dynamic parameters on lights // ( Now we create the parameters with a loadShader after the constructor, so they don't need to be dynamic ) - for( PlugIterator it( light->parametersPlug() ); !it.done(); ++it ) + for( Plug::Iterator it( light->parametersPlug() ); !it.done(); ++it ) { (*it)->setFlags( Gaffer::Plug::Dynamic, false ); } @@ -119,7 +119,7 @@ namespace GafferSceneModule { class LightFilterSerialiser : public GafferBindings::NodeSerialiser { - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const GafferBindings::Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, GafferBindings::Serialisation &serialisation ) const override { std::string defaultPostConstructor = GafferBindings::NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); diff --git a/src/GafferSceneModule/RenderBinding.cpp b/src/GafferSceneModule/RenderBinding.cpp index 205f1984e74..68acf2c3703 100644 --- a/src/GafferSceneModule/RenderBinding.cpp +++ b/src/GafferSceneModule/RenderBinding.cpp @@ -44,6 +44,7 @@ #include "GafferScene/Private/IECoreScenePreview/Geometry.h" #include "GafferScene/Private/IECoreScenePreview/Procedural.h" #include "GafferScene/Private/IECoreScenePreview/Renderer.h" +#include "GafferScene/Private/RendererAlgo.h" #include "GafferScene/Render.h" #include "GafferDispatchBindings/TaskNodeBinding.h" @@ -272,6 +273,26 @@ list capturedObjectCapturedSampleTimes( const CapturingRenderer::CapturedObject return result; } +list capturedObjectCapturedTransforms( const CapturingRenderer::CapturedObject &o ) +{ + list result; + for( auto s : o.capturedTransforms() ) + { + result.append( s ); + } + return result; +} + +list capturedObjectCapturedTransformTimes( const CapturingRenderer::CapturedObject &o ) +{ + list result; + for( auto t : o.capturedTransformTimes() ) + { + result.append( t ); + } + return result; +} + CapturingRenderer::CapturedAttributesPtr capturedObjectCapturedAttributes( const CapturingRenderer::CapturedObject &o ) { return const_cast( o.capturedAttributes() ); @@ -296,6 +317,37 @@ object capturedObjectCapturedLinks( const CapturingRenderer::CapturedObject &o, } } +list objectSamplesWrapper( const Gaffer::ObjectPlug &objectPlug, const std::vector &sampleTimes, bool copy ) +{ + std::vector samples; + { + IECorePython::ScopedGILRelease gilRelease; + Private::RendererAlgo::objectSamples( &objectPlug, sampleTimes, samples ); + } + + list pythonSamples; + for( auto &s : samples ) + { + if( copy ) + { + pythonSamples.append( s->copy() ); + } + else + { + pythonSamples.append( boost::const_pointer_cast( s ) ); + } + } + + return pythonSamples; +} + +void outputCamerasWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const Private::RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer ) +{ + IECorePython::ScopedGILRelease gilRelease; + Private::RendererAlgo::outputCameras( &scene, &globals, renderSets, &renderer ); +} + + } // namespace void GafferSceneModule::bindRender() @@ -327,6 +379,21 @@ void GafferSceneModule::bindRender() object privateModule( borrowed( PyImport_AddModule( "GafferScene.Private" ) ) ); scope().attr( "Private" ) = privateModule; + { + object rendererAlgoModule( borrowed( PyImport_AddModule( "GafferScene.Private.RendererAlgo" ) ) ); + scope().attr( "Private" ).attr( "RendererAlgo" ) = rendererAlgoModule; + + scope rendererAlgomoduleScope( rendererAlgoModule ); + + def( "objectSamples", &objectSamplesWrapper, ( arg( "objectPlug" ), arg( "sampleTimes" ), arg( "_copy" ) = true ) ); + + class_( "RenderSets" ) + .def( init() ) + ; + + def( "outputCameras", &outputCamerasWrapper ); + } + object ieCoreScenePreviewModule( borrowed( PyImport_AddModule( "GafferScene.Private.IECoreScenePreview" ) ) ); scope().attr( "Private" ).attr( "IECoreScenePreview" ) = ieCoreScenePreviewModule; @@ -418,12 +485,13 @@ void GafferSceneModule::bindRender() IECorePython::RefCountedClass( "CapturedObject" ) .def( "capturedSamples", &capturedObjectCapturedSamples ) .def( "capturedSampleTimes", &capturedObjectCapturedSampleTimes ) + .def( "capturedTransforms", &capturedObjectCapturedTransforms ) + .def( "capturedTransformTimes", &capturedObjectCapturedTransformTimes ) .def( "capturedAttributes", &capturedObjectCapturedAttributes ) .def( "capturedLinks", &capturedObjectCapturedLinks ) .def( "numAttributeEdits", &CapturingRenderer::CapturedObject::numAttributeEdits ) .def( "numLinkEdits", &CapturingRenderer::CapturedObject::numLinkEdits ) ; - } TaskNodeClass(); diff --git a/src/GafferSceneModule/RendererAlgoBinding.cpp b/src/GafferSceneModule/RendererAlgoBinding.cpp deleted file mode 100644 index 67c9d497a1a..00000000000 --- a/src/GafferSceneModule/RendererAlgoBinding.cpp +++ /dev/null @@ -1,150 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// Copyright (c) 2016, 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 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 "boost/python.hpp" - -#include "RendererAlgoBinding.h" - -#include "GafferScene/RendererAlgo.h" -#include "GafferScene/SceneProcessor.h" - -#include "IECorePython/ScopedGILLock.h" -#include "IECorePython/ScopedGILRelease.h" - -using namespace boost::python; -using namespace GafferScene; - -namespace -{ - -tuple objectSamplesWrapper( const ScenePlug &scene, size_t segments, const Imath::V2f &shutter, bool copy ) -{ - std::vector samples; - std::vector sampleTimes; - { - IECorePython::ScopedGILRelease gilRelease; - RendererAlgo::objectSamples( &scene, segments, shutter, samples, sampleTimes ); - } - - list pythonSamples; - for( auto &s : samples ) - { - if( copy ) - { - pythonSamples.append( s->copy() ); - } - else - { - pythonSamples.append( boost::const_pointer_cast( s ) ); - } - } - - list pythonSampleTimes; - for( auto &s : sampleTimes ) - { - pythonSampleTimes.append( s ); - } - - return make_tuple( pythonSamples, pythonSampleTimes ); -} - -struct AdaptorWrapper -{ - - AdaptorWrapper( object pythonAdaptor ) - : m_pythonAdaptor( pythonAdaptor ) - { - } - - SceneProcessorPtr operator()() - { - IECorePython::ScopedGILLock gilLock; - SceneProcessorPtr result = extract( m_pythonAdaptor() ); - return result; - } - - private : - - object m_pythonAdaptor; - -}; - -void registerAdaptorWrapper( const std::string &name, object adaptor ) -{ - RendererAlgo::registerAdaptor( name, AdaptorWrapper( adaptor ) ); -} - -void outputCamerasWrapper( const ScenePlug &scene, const IECore::CompoundObject &globals, const RendererAlgo::RenderSets &renderSets, IECoreScenePreview::Renderer &renderer ) -{ - IECorePython::ScopedGILRelease gilRelease; - RendererAlgo::outputCameras( &scene, &globals, renderSets, &renderer ); -} - -void applyCameraGlobalsWrapper( IECoreScene::Camera &camera, const IECore::CompoundObject &globals, const ScenePlug &scene ) -{ - IECorePython::ScopedGILRelease gilRelease; - RendererAlgo::applyCameraGlobals( &camera, &globals, &scene ); -} - -} // namespace - -namespace GafferSceneModule -{ - -void bindRendererAlgo() -{ - - object module( borrowed( PyImport_AddModule( "GafferScene.RendererAlgo" ) ) ); - scope().attr( "RendererAlgo" ) = module; - scope moduleScope( module ); - - def( "objectSamples", &objectSamplesWrapper, ( arg( "scene" ), arg( "segments" ), arg( "shutter" ), arg( "_copy" ) = true ) ); - - def( "registerAdaptor", ®isterAdaptorWrapper ); - def( "deregisterAdaptor", &RendererAlgo::deregisterAdaptor ); - def( "createAdaptors", &RendererAlgo::createAdaptors ); - - class_( "RenderSets" ) - .def( init() ) - ; - - def( "outputCameras", &outputCamerasWrapper ); - - def( "applyCameraGlobals", &applyCameraGlobalsWrapper ); - -} - -} // namespace GafferSceneModule diff --git a/src/GafferSceneModule/SceneAlgoBinding.cpp b/src/GafferSceneModule/SceneAlgoBinding.cpp index 9a18b3ce447..715b794ff0b 100644 --- a/src/GafferSceneModule/SceneAlgoBinding.cpp +++ b/src/GafferSceneModule/SceneAlgoBinding.cpp @@ -48,6 +48,7 @@ #include "IECoreScene/Camera.h" #include "IECorePython/RefCountedBinding.h" +#include "IECorePython/ScopedGILLock.h" #include "IECorePython/ScopedGILRelease.h" #include "boost/python/suite/indexing/container_utils.hpp" @@ -61,17 +62,17 @@ using namespace GafferScene; namespace { -bool existsWrapper( const ScenePlug *scene, const ScenePlug::ScenePath &path ) +bool existsWrapper( const ScenePlug &scene, const ScenePlug::ScenePath &path ) { // gil release in case the scene traversal dips back into python: IECorePython::ScopedGILRelease r; - return SceneAlgo::exists( scene, path ); + return SceneAlgo::exists( &scene, path ); } -bool visibleWrapper( const ScenePlug *scene, const ScenePlug::ScenePath &path ) +bool visibleWrapper( const ScenePlug &scene, const ScenePlug::ScenePath &path ) { IECorePython::ScopedGILRelease r; - return SceneAlgo::visible( scene, path ); + return SceneAlgo::visible( &scene, path ); } object filteredNodesWrapper( Filter &filter ) @@ -87,53 +88,72 @@ object filteredNodesWrapper( Filter &filter ) return object( handle<>( nodesSet ) ); } -void matchingPathsWrapper1( const Filter *filter, const ScenePlug *scene, PathMatcher &paths ) +void matchingPathsWrapper1( const Filter &filter, const ScenePlug &scene, PathMatcher &paths ) { // gil release in case the scene traversal dips back into python: IECorePython::ScopedGILRelease r; - SceneAlgo::matchingPaths( filter, scene, paths ); + SceneAlgo::matchingPaths( &filter, &scene, paths ); } -void matchingPathsWrapper2( const Gaffer::IntPlug *filterPlug, const ScenePlug *scene, PathMatcher &paths ) +void matchingPathsWrapper2( const FilterPlug &filterPlug, const ScenePlug &scene, PathMatcher &paths ) { // gil release in case the scene traversal dips back into python: IECorePython::ScopedGILRelease r; - SceneAlgo::matchingPaths( filterPlug, scene, paths ); + SceneAlgo::matchingPaths( &filterPlug, &scene, paths ); } -void matchingPathsWrapper3( const PathMatcher &filter, const ScenePlug *scene, PathMatcher &paths ) +void matchingPathsWrapper3( const FilterPlug &filterPlug, const ScenePlug &scene, const ScenePlug::ScenePath &root, PathMatcher &paths ) { // gil release in case the scene traversal dips back into python: IECorePython::ScopedGILRelease r; - SceneAlgo::matchingPaths( filter, scene, paths ); + SceneAlgo::matchingPaths( &filterPlug, &scene, root, paths ); } -Imath::V2f shutterWrapper( const IECore::CompoundObject *globals, const ScenePlug *scene ) +void matchingPathsWrapper4( const PathMatcher &filter, const ScenePlug &scene, PathMatcher &paths ) +{ + // gil release in case the scene traversal dips back into python: + IECorePython::ScopedGILRelease r; + SceneAlgo::matchingPaths( filter, &scene, paths ); +} + +IECore::MurmurHash matchingPathsHashWrapper1( const GafferScene::FilterPlug &filterPlug, const ScenePlug &scene, const ScenePlug::ScenePath &root ) +{ + IECorePython::ScopedGILRelease r; + return SceneAlgo::matchingPathsHash( &filterPlug, &scene, root ); +} + +IECore::MurmurHash matchingPathsHashWrapper2( const IECore::PathMatcher &filter, const ScenePlug &scene ) { IECorePython::ScopedGILRelease r; - return SceneAlgo::shutter( globals, scene ); + return SceneAlgo::matchingPathsHash( filter, &scene ); } -bool setExistsWrapper( const ScenePlug *scene, const IECore::InternedString &setName ) +Imath::V2f shutterWrapper( const IECore::CompoundObject &globals, const ScenePlug &scene ) { IECorePython::ScopedGILRelease r; - return SceneAlgo::setExists( scene, setName ); + return SceneAlgo::shutter( &globals, &scene ); } -IECore::CompoundDataPtr setsWrapper1( const ScenePlug *scene, bool copy ) +bool setExistsWrapper( const ScenePlug &scene, const IECore::InternedString &setName ) { IECorePython::ScopedGILRelease r; - IECore::ConstCompoundDataPtr result = SceneAlgo::sets( scene ); + return SceneAlgo::setExists( &scene, setName ); +} + +IECore::CompoundDataPtr setsWrapper1( const ScenePlug &scene, bool copy ) +{ + IECorePython::ScopedGILRelease r; + IECore::ConstCompoundDataPtr result = SceneAlgo::sets( &scene ); return copy ? result->copy() : boost::const_pointer_cast( result ); } -IECore::CompoundDataPtr setsWrapper2( const ScenePlug *scene, object pythonSetNames, bool copy ) +IECore::CompoundDataPtr setsWrapper2( const ScenePlug &scene, object pythonSetNames, bool copy ) { std::vector setNames; boost::python::container_utils::extend_container( setNames, pythonSetNames ); IECorePython::ScopedGILRelease r; - IECore::ConstCompoundDataPtr result = SceneAlgo::sets( scene, setNames ); + IECore::ConstCompoundDataPtr result = SceneAlgo::sets( &scene, setNames ); return copy ? result->copy() : boost::const_pointer_cast( result ); } @@ -250,6 +270,38 @@ IECore::PathMatcher linkedLightsWrapper2( const GafferScene::ScenePlug &scene, c return SceneAlgo::linkedLights( &scene, objects ); } +struct RenderAdaptorWrapper +{ + + RenderAdaptorWrapper( object pythonAdaptor ) + : m_pythonAdaptor( pythonAdaptor ) + { + } + + SceneProcessorPtr operator()() + { + IECorePython::ScopedGILLock gilLock; + SceneProcessorPtr result = extract( m_pythonAdaptor() ); + return result; + } + + private : + + object m_pythonAdaptor; + +}; + +void registerRenderAdaptorWrapper( const std::string &name, object adaptor ) +{ + SceneAlgo::registerRenderAdaptor( name, RenderAdaptorWrapper( adaptor ) ); +} + +void applyCameraGlobalsWrapper( IECoreScene::Camera &camera, const IECore::CompoundObject &globals, const ScenePlug &scene ) +{ + IECorePython::ScopedGILRelease gilRelease; + SceneAlgo::applyCameraGlobals( &camera, &globals, &scene ); +} + } // namespace namespace GafferSceneModule @@ -268,6 +320,9 @@ void bindSceneAlgo() def( "matchingPaths", &matchingPathsWrapper1 ); def( "matchingPaths", &matchingPathsWrapper2 ); def( "matchingPaths", &matchingPathsWrapper3 ); + def( "matchingPaths", &matchingPathsWrapper4 ); + def( "matchingPathsHash", &matchingPathsHashWrapper1, ( arg( "filter" ), arg( "scene" ), arg( "root" ) = "/" ) ); + def( "matchingPathsHash", &matchingPathsHashWrapper2, ( arg( "filter" ), arg( "scene" ) ) ); def( "shutter", &shutterWrapper ); def( "setExists", &setExistsWrapper ); def( @@ -324,6 +379,16 @@ void bindSceneAlgo() def( "linkedLights", &linkedLightsWrapper1 ); def( "linkedLights", &linkedLightsWrapper2 ); + // Render adaptors + + def( "registerRenderAdaptor", ®isterRenderAdaptorWrapper ); + def( "deregisterRenderAdaptor", &SceneAlgo::deregisterRenderAdaptor ); + def( "createRenderAdaptors", &SceneAlgo::createRenderAdaptors ); + + // Camera globals + + def( "applyCameraGlobals", &applyCameraGlobalsWrapper ); + } } // namespace GafferSceneModule diff --git a/src/GafferSceneModule/ShaderBinding.cpp b/src/GafferSceneModule/ShaderBinding.cpp index c1894688410..2c0089749fc 100644 --- a/src/GafferSceneModule/ShaderBinding.cpp +++ b/src/GafferSceneModule/ShaderBinding.cpp @@ -103,7 +103,7 @@ void reloadShader( Shader &shader ) class ShaderSerialiser : public GafferBindings::NodeSerialiser { - std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, const Serialisation &serialisation ) const override + std::string postConstructor( const Gaffer::GraphComponent *graphComponent, const std::string &identifier, Serialisation &serialisation ) const override { std::string defaultPC = GafferBindings::NodeSerialiser::postConstructor( graphComponent, identifier, serialisation ); const Shader *shader = static_cast( graphComponent ); diff --git a/src/GafferSceneModule/TransformBinding.cpp b/src/GafferSceneModule/TransformBinding.cpp index f1ea8723bf0..1fbc6ab2a7a 100644 --- a/src/GafferSceneModule/TransformBinding.cpp +++ b/src/GafferSceneModule/TransformBinding.cpp @@ -44,6 +44,7 @@ #include "GafferScene/ParentConstraint.h" #include "GafferScene/PointConstraint.h" #include "GafferScene/Transform.h" +#include "GafferScene/TransformQuery.h" #include "GafferBindings/ComputeNodeBinding.h" @@ -104,4 +105,13 @@ void GafferSceneModule::bindTransform() ; } + { + scope s = GafferBindings::DependencyNodeClass(); + + enum_( "Space" ) + .value( "Local", TransformQuery::Space::Local ) + .value( "World", TransformQuery::Space::World ) + .value( "Relative", TransformQuery::Space::Relative ) + ; + } } diff --git a/src/GafferSceneModule/TweaksBinding.cpp b/src/GafferSceneModule/TweaksBinding.cpp index 6f69b72a5dc..84f6b66ae31 100644 --- a/src/GafferSceneModule/TweaksBinding.cpp +++ b/src/GafferSceneModule/TweaksBinding.cpp @@ -86,7 +86,7 @@ class TweakPlugSerialiser : public ValuePlugSerialiser return false; } - std::string constructor( const Gaffer::GraphComponent *graphComponent, const Serialisation &serialisation ) const override + std::string constructor( const Gaffer::GraphComponent *graphComponent, Serialisation &serialisation ) const override { auto tweaksPlug = static_cast( graphComponent ); diff --git a/src/GafferSceneTest/CompoundObjectSource.cpp b/src/GafferSceneTest/CompoundObjectSource.cpp index daaa0f5d0e5..9eb6ac292e0 100644 --- a/src/GafferSceneTest/CompoundObjectSource.cpp +++ b/src/GafferSceneTest/CompoundObjectSource.cpp @@ -70,7 +70,7 @@ void CompoundObjectSource::affects( const Plug *input, AffectedPlugsContainer &o SceneNode::affects( input, outputs ); if( input == inPlug() ) { - for( ValuePlugIterator it( outPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( outPlug() ); !it.done(); ++it ) { outputs.push_back( it->get() ); } diff --git a/src/GafferSceneTest/ContextSanitiser.cpp b/src/GafferSceneTest/ContextSanitiser.cpp index 156b7795a92..fe6c66322b0 100644 --- a/src/GafferSceneTest/ContextSanitiser.cpp +++ b/src/GafferSceneTest/ContextSanitiser.cpp @@ -81,14 +81,14 @@ void ContextSanitiser::processStarted( const Gaffer::Process *process ) { if( const ScenePlug *scene = process->plug()->parent() ) { - if( process->context()->get( FilterPlug::inputSceneContextName, nullptr ) ) + if( process->context()->getIfExists( FilterPlug::inputSceneContextName ) ) { warn( *process, FilterPlug::inputSceneContextName ); } if( process->plug() != scene->setPlug() ) { - if( process->context()->get( ScenePlug::setNameContextName, nullptr ) ) + if( process->context()->getIfExists( ScenePlug::setNameContextName ) ) { warn( *process, ScenePlug::setNameContextName ); } @@ -107,7 +107,7 @@ void ContextSanitiser::processStarted( const Gaffer::Process *process ) process->plug()->getName() != g_sortedChildNames ) { - if( process->context()->get( ScenePlug::scenePathContextName, nullptr ) ) + if( process->context()->getIfExists( ScenePlug::scenePathContextName ) ) { warn( *process, ScenePlug::scenePathContextName ); } @@ -118,11 +118,11 @@ void ContextSanitiser::processStarted( const Gaffer::Process *process ) { if( process->plug()->getName() == g_internalOut ) { - if( process->context()->get( ScenePlug::scenePathContextName, nullptr ) ) + if( process->context()->getIfExists( ScenePlug::scenePathContextName ) ) { warn( *process, ScenePlug::scenePathContextName ); } - if( process->context()->get( ScenePlug::setNameContextName, nullptr ) ) + if( process->context()->getIfExists( ScenePlug::setNameContextName ) ) { warn( *process, ScenePlug::setNameContextName ); } diff --git a/src/GafferSceneTest/TestLight.cpp b/src/GafferSceneTest/TestLight.cpp index 6a94600bacd..bae60da4f3e 100644 --- a/src/GafferSceneTest/TestLight.cpp +++ b/src/GafferSceneTest/TestLight.cpp @@ -59,7 +59,7 @@ TestLight::~TestLight() void TestLight::hashLight( const Gaffer::Context *context, IECore::MurmurHash &h ) const { - for( ValuePlugIterator it( parametersPlug() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( parametersPlug() ); !it.done(); ++it ) { (*it)->hash( h ); } diff --git a/src/GafferSceneTest/TraverseScene.cpp b/src/GafferSceneTest/TraverseScene.cpp index 46b0ac48cc2..11929d99402 100644 --- a/src/GafferSceneTest/TraverseScene.cpp +++ b/src/GafferSceneTest/TraverseScene.cpp @@ -116,4 +116,3 @@ boost::signals::connection GafferSceneTest::connectTraverseSceneToPreDispatchSig { return GafferDispatch::Dispatcher::preDispatchSignal().connect( boost::bind( traverseOnPreDispatch, scene ) ); } - diff --git a/src/GafferSceneUI/ContextAlgo.cpp b/src/GafferSceneUI/ContextAlgo.cpp index b0100eaadef..45f26ee4bc5 100644 --- a/src/GafferSceneUI/ContextAlgo.cpp +++ b/src/GafferSceneUI/ContextAlgo.cpp @@ -105,12 +105,7 @@ void setExpandedPaths( Context *context, const IECore::PathMatcher &paths ) IECore::PathMatcher getExpandedPaths( const Gaffer::Context *context ) { - if( const IECore::PathMatcherData *expandedPaths = context->get( g_expandedPathsName, nullptr ) ) - { - return expandedPaths->readable(); - } - - return IECore::PathMatcher(); + return context->get( g_expandedPathsName, IECore::PathMatcher() ); } bool affectsExpandedPaths( const IECore::InternedString &name ) @@ -120,14 +115,13 @@ bool affectsExpandedPaths( const IECore::InternedString &name ) void expand( Context *context, const PathMatcher &paths, bool expandAncestors ) { - IECore::PathMatcherData *expandedPaths = const_cast( context->get( g_expandedPathsName, nullptr ) ); + const IECore::PathMatcher *expandedPaths = context->getIfExists( g_expandedPathsName ); if( !expandedPaths ) { - expandedPaths = new IECore::PathMatcherData(); - context->set( g_expandedPathsName, expandedPaths ); + context->set( g_expandedPathsName, new IECore::PathMatcherData() ); + expandedPaths = context->getIfExists( g_expandedPathsName ); } - - IECore::PathMatcher &expanded = expandedPaths->writable(); + IECore::PathMatcher &expanded = *const_cast(expandedPaths); bool needUpdate = false; if( expandAncestors ) @@ -147,23 +141,22 @@ void expand( Context *context, const PathMatcher &paths, bool expandAncestors ) if( needUpdate ) { - // We modified the expanded paths in place to avoid unecessary copying, - // so the context doesn't know they've changed. So we emit the changed - // signal ourselves - context->changedSignal()( context, g_expandedPathsName ); + // We modified the expanded paths in place with const_cast to avoid unecessary copying, + // so the context doesn't know they've changed. So we must let it know + // about the change. + context->set( g_expandedPathsName, *expandedPaths ); } } IECore::PathMatcher expandDescendants( Context *context, const IECore::PathMatcher &paths, const ScenePlug *scene, int depth ) { - IECore::PathMatcherData *expandedPaths = const_cast( context->get( g_expandedPathsName, nullptr ) ); + const IECore::PathMatcher* expandedPaths = context->getIfExists( g_expandedPathsName ); if( !expandedPaths ) { - expandedPaths = new IECore::PathMatcherData(); - context->set( g_expandedPathsName, expandedPaths ); + context->set( g_expandedPathsName, new IECore::PathMatcherData() ); + expandedPaths = context->getIfExists( g_expandedPathsName ); } - - IECore::PathMatcher &expanded = expandedPaths->writable(); + IECore::PathMatcher &expanded = *const_cast(expandedPaths); bool needUpdate = false; IECore::PathMatcher leafPaths; @@ -176,10 +169,10 @@ IECore::PathMatcher expandDescendants( Context *context, const IECore::PathMatch if( needUpdate ) { - // We modified the expanded paths in place to avoid unecessary copying, - // so the context doesn't know they've changed. So we emit the changed - // signal ourselves - context->changedSignal()( context, g_expandedPathsName ); + // We modified the expanded paths in place with const_cast to avoid unecessary copying, + // so the context doesn't know they've changed. So we must let it know + // about the change. + context->set( g_expandedPathsName, *expandedPaths ); } return leafPaths; diff --git a/src/GafferSceneUI/CropWindowTool.cpp b/src/GafferSceneUI/CropWindowTool.cpp index e1c4ebefc65..5ca2a73bb42 100644 --- a/src/GafferSceneUI/CropWindowTool.cpp +++ b/src/GafferSceneUI/CropWindowTool.cpp @@ -147,7 +147,6 @@ class CropWindowTool::Rectangle : public GafferUI::Gadget Imath::Box3f bound() const override { - return Box3f(); if( m_rasterSpace ) { // We draw in raster space so don't have a sensible bound @@ -951,7 +950,7 @@ bool CropWindowTool::findCropWindowPlugFromNode( GafferScene::ScenePlug *scene, return false; } - for( NameValuePlugIterator it( options->optionsPlug() ); !it.done(); ++it ) + for( NameValuePlug::Iterator it( options->optionsPlug() ); !it.done(); ++it ) { NameValuePlug *memberPlug = it->get(); if( memberPlug->namePlug()->getValue() != "render:cropWindow" ) diff --git a/src/GafferSceneUI/RotateTool.cpp b/src/GafferSceneUI/RotateTool.cpp index e6c638744d0..0eedb2f8706 100644 --- a/src/GafferSceneUI/RotateTool.cpp +++ b/src/GafferSceneUI/RotateTool.cpp @@ -146,7 +146,7 @@ void RotateTool::updateHandles( float rasterScale ) selection().back().orientedTransform( orientation ) ); - for( RotateHandleIterator it( handles() ); !it.done(); ++it ) + for( RotateHandle::Iterator it( handles() ); !it.done(); ++it ) { bool enabled = true; for( const auto &s : selection() ) diff --git a/src/GafferSceneUI/ScaleTool.cpp b/src/GafferSceneUI/ScaleTool.cpp index 194d7e21b15..15995b4a627 100644 --- a/src/GafferSceneUI/ScaleTool.cpp +++ b/src/GafferSceneUI/ScaleTool.cpp @@ -102,7 +102,7 @@ void ScaleTool::updateHandles( float rasterScale ) this->selection().back().orientedTransform( Local ) ); - for( ScaleHandleIterator it( handles() ); !it.done(); ++it ) + for( ScaleHandle::Iterator it( handles() ); !it.done(); ++it ) { bool enabled = true; for( const auto &s : selection() ) diff --git a/src/GafferSceneUI/SceneView.cpp b/src/GafferSceneUI/SceneView.cpp index 262221a95e2..ccb22431042 100644 --- a/src/GafferSceneUI/SceneView.cpp +++ b/src/GafferSceneUI/SceneView.cpp @@ -44,7 +44,6 @@ #include "GafferScene/Grid.h" #include "GafferScene/LightToCamera.h" #include "GafferScene/PathFilter.h" -#include "GafferScene/RendererAlgo.h" #include "GafferScene/SceneAlgo.h" #include "GafferScene/SetFilter.h" #include "GafferScene/StandardOptions.h" @@ -1309,13 +1308,13 @@ class SceneView::Camera : public boost::signals::trackable cameraTransform = scenePlug()->fullTransform( cameraPath ); IECoreScene::CameraPtr camera = constCamera->copy(); - RendererAlgo::applyCameraGlobals( camera.get(), globals.get(), scenePlug() ); + SceneAlgo::applyCameraGlobals( camera.get(), globals.get(), scenePlug() ); m_lookThroughCamera = camera; } else { CameraPtr defaultCamera = new IECoreScene::Camera; - RendererAlgo::applyCameraGlobals( defaultCamera.get(), globals.get(), scenePlug() ); + SceneAlgo::applyCameraGlobals( defaultCamera.get(), globals.get(), scenePlug() ); m_lookThroughCamera = defaultCamera; } } @@ -1608,7 +1607,7 @@ SceneView::SceneView( const std::string &name ) // add in any render adaptors that might have been registered - SceneProcessorPtr adaptors = RendererAlgo::createAdaptors(); + SceneProcessorPtr adaptors = SceneAlgo::createRenderAdaptors(); preprocessor->addChild( adaptors ); adaptors->inPlug()->setInput( deleteObject->outPlug() ); @@ -1622,11 +1621,23 @@ SceneView::SceneView( const std::string &name ) preprocessor->addChild( m_drawingMode->preprocessor() ); m_drawingMode->preprocessor()->inPlug()->setInput( m_shadingMode->preprocessor()->outPlug() ); + + // remove motion blur, because the opengl renderer doesn't support it. + + StandardOptionsPtr standardOptions = new StandardOptions( "disableBlur" ); + standardOptions->optionsPlug()->getChild( "transformBlur" )->enabledPlug()->setValue( true ); + standardOptions->optionsPlug()->getChild( "transformBlur" )->valuePlug()->setValue( false ); + standardOptions->optionsPlug()->getChild( "deformationBlur" )->enabledPlug()->setValue( true ); + standardOptions->optionsPlug()->getChild( "deformationBlur" )->valuePlug()->setValue( false ); + + preprocessor->addChild( standardOptions ); + standardOptions->inPlug()->setInput( m_drawingMode->preprocessor()->outPlug() ); + // make the output for the preprocessor ScenePlugPtr preprocessorOutput = new ScenePlug( "out", Plug::Out ); preprocessor->addChild( preprocessorOutput ); - preprocessorOutput->setInput( m_drawingMode->preprocessor()->outPlug() ); + preprocessorOutput->setInput( standardOptions->outPlug() ); setPreprocessor( preprocessor ); @@ -1810,6 +1821,10 @@ void SceneView::frame( const PathMatcher &filter, const Imath::V3f &direction ) void SceneView::expandSelection( size_t depth ) { + // Note that we are scoping this context at the same time that expandDescendants is doing a cheeky in-place + // modification of the context. In general this could result in highly inconsistent hash results ... it + // is safe only because the selected paths is a "ui:" prefixed context variable, and Context is hardcoded + // to ignore variables with this prefix during hashing Context::Scope scope( getContext() ); PathMatcher selection = ContextAlgo::expandDescendants( getContext(), m_sceneGadget->getSelection(), preprocessedInPlug(), depth - 1 ); ContextAlgo::setSelectedPaths( getContext(), selection ); diff --git a/src/GafferSceneUI/ShaderTweaksUI.cpp b/src/GafferSceneUI/ShaderTweaksUI.cpp index c6b1e88ccd1..d0ca49fa9fa 100644 --- a/src/GafferSceneUI/ShaderTweaksUI.cpp +++ b/src/GafferSceneUI/ShaderTweaksUI.cpp @@ -121,7 +121,7 @@ class TweakPlugAdder : public PlugAdder { vector result; - for( TweakPlugIterator it( m_plugsParent.get() ); !it.done(); ++it ) + for( TweakPlug::Iterator it( m_plugsParent.get() ); !it.done(); ++it ) { TweakPlug *tweakPlug = it->get(); if( input ) @@ -237,4 +237,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferSceneUI/ShaderUI.cpp b/src/GafferSceneUI/ShaderUI.cpp index 5f934a9f0e5..aa8891f62c0 100644 --- a/src/GafferSceneUI/ShaderUI.cpp +++ b/src/GafferSceneUI/ShaderUI.cpp @@ -125,7 +125,7 @@ class ShaderPlugAdder : public PlugAdder { vector result; - for( PlugIterator it( m_plugsParent.get() ); !it.done(); ++it ) + for( Plug::Iterator it( m_plugsParent.get() ); !it.done(); ++it ) { Plug *plug = it->get(); if( !plug->getFlags( Plug::AcceptsInputs ) ) @@ -208,4 +208,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferSceneUI/ShaderView.cpp b/src/GafferSceneUI/ShaderView.cpp index b48e01825f2..061a51b30c1 100644 --- a/src/GafferSceneUI/ShaderView.cpp +++ b/src/GafferSceneUI/ShaderView.cpp @@ -472,4 +472,3 @@ void ShaderView::driverCreated( IECoreImage::DisplayDriver *driver, const IECore } } } - diff --git a/src/GafferSceneUI/SourceSet.cpp b/src/GafferSceneUI/SourceSet.cpp deleted file mode 100755 index 5dffc6d5aae..00000000000 --- a/src/GafferSceneUI/SourceSet.cpp +++ /dev/null @@ -1,244 +0,0 @@ -////////////////////////////////////////////////////////////////////////// -// -// Copyright (c) 2019, 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 "GafferSceneUI/SourceSet.h" - -#include "GafferSceneUI/ContextAlgo.h" - -#include "GafferScene/SceneAlgo.h" -#include "GafferScene/SceneNode.h" - -#include "Gaffer/MetadataAlgo.h" - -#include "IECore/Exception.h" - -using namespace IECore; -using namespace Gaffer; -using namespace GafferScene; -using namespace GafferSceneUI; - -namespace { - -// This is a re-implementation of the python version -// in SceneHistoryUI.py as we couldn't find a sensible -// name for this in the public API -// TODO: Re-home in MetadataAlgo we can make it make sense... -Node *ancestorWithReadOnlyChildNodes( Node *node ) -{ - Node *ancestor = nullptr; - while( node ) - { - if( MetadataAlgo::getChildNodesAreReadOnly( node ) ) - { - ancestor = node; - } - node = runTimeCast( node->parent() ); - } - return ancestor; -} - -} - - -SourceSet::SourceSet( ContextPtr context, SetPtr nodeSet ) -{ - setContext( context ); - setNodeSet( nodeSet ); -} - -SourceSet::~SourceSet() -{ -} - -void SourceSet::setContext( ContextPtr context ) -{ - if( context == m_context ) - { - return; - } - - m_context = context; - m_contextChangedConnection = context->changedSignal().connect( boost::bind( &SourceSet::contextChanged, this, ::_2 ) ); - updateSourceNode(); -} - -Context *SourceSet::getContext() const -{ - return m_context.get(); -} - -void SourceSet::setNodeSet( SetPtr nodeSet ) -{ - if( nodeSet == m_nodes ) - { - return; - } - - m_nodes = nodeSet; - m_nodeAddedConnection = nodeSet->memberAddedSignal().connect( boost::bind( &SourceSet::updateScenePlug, this ) ); - m_nodeRemovedConnection = nodeSet->memberRemovedSignal().connect( boost::bind( &SourceSet::updateScenePlug, this ) ); - updateScenePlug(); -} - -Set *SourceSet::getNodeSet() const -{ - return m_nodes.get(); -} - - -bool SourceSet::contains( const Member *object ) const -{ - return m_sourceNode && m_sourceNode.get() == object; -} - -Set::Member *SourceSet::member( size_t index ) -{ - return m_sourceNode.get(); -} - -const Set::Member *SourceSet::member( size_t index ) const -{ - return m_sourceNode.get(); -} - -size_t SourceSet::size() const -{ - return m_sourceNode ? 1 : 0; -} - -void SourceSet::contextChanged( const IECore::InternedString &name ) -{ - if( ContextAlgo::affectsLastSelectedPath( name ) ) - { - updateSourceNode(); - } -} - -void SourceSet::plugDirtied( const Gaffer::Plug *plug ) -{ - if( m_scenePlug && m_scenePlug == plug ) - { - updateSourceNode(); - } -} - -void SourceSet::updateScenePlug() -{ - ScenePlug *newScenePlug = nullptr; - if( m_nodes && m_nodes->size() > 0 ) - { - // We want the last selected node rather than the first - Node const *node = runTimeCast( m_nodes->member( m_nodes->size() - 1 ) ); - if( node ) - { - OutputScenePlugIterator it( node ); - if( !it.done() ) - { - newScenePlug = it->get(); - } - } - } - - if( newScenePlug != m_scenePlug ) - { - m_plugDirtiedConnection.disconnect(); - if( newScenePlug ) - { - m_plugDirtiedConnection = newScenePlug->node()->plugDirtiedSignal().connect( boost::bind( &SourceSet::plugDirtied, this, ::_1 ) ); - } - - m_scenePlug = newScenePlug; - } - - // We always update this (even if we don't find a scene plug) to ensure we update - // the presented node through consecutive selection of nodes without scene plugs. - updateSourceNode(); -} - -void SourceSet::updateSourceNode() -{ - Node *newSourceNode = nullptr; - - if( m_context && m_scenePlug ) - { - const ScenePlug::ScenePath path = ContextAlgo::getLastSelectedPath( m_context.get() ); - - Context::Scope scope( m_context.get() ); - try - { - if( !path.empty() && m_scenePlug->exists( path ) ) - { - if( ScenePlug *sourcePlug = SceneAlgo::source( m_scenePlug.get(), path ) ) - { - Node* const firstEditableAncestor = ancestorWithReadOnlyChildNodes( sourcePlug->node() ); - newSourceNode = firstEditableAncestor ? firstEditableAncestor : sourcePlug->node(); - } - } - else - { - newSourceNode = m_scenePlug->node(); - } - } - catch( std::exception &e ) - { - /* this will reported by Node::errorSignal() */ - } - } - else if( m_nodes && m_nodes->size() > 0 ) - { - // If we don't have a valid context or scene plug, but do have a node, - // just assume that's the source. This generally makes sense for non- - // scene nodes. - newSourceNode = runTimeCast( m_nodes->member( m_nodes->size() - 1 ) ); - } - - if( newSourceNode != m_sourceNode ) - { - if( m_sourceNode ) - { - NodePtr oldSourceNode = m_sourceNode; - m_sourceNode.reset(); - memberRemovedSignal()( this, oldSourceNode.get() ); - } - - m_sourceNode = newSourceNode; - - if( newSourceNode ) - { - memberAddedSignal()( this, newSourceNode ); - } - } -} diff --git a/src/GafferSceneUI/StandardLightVisualiser.cpp b/src/GafferSceneUI/StandardLightVisualiser.cpp index ab8c8b38b28..c4e300fb0c5 100644 --- a/src/GafferSceneUI/StandardLightVisualiser.cpp +++ b/src/GafferSceneUI/StandardLightVisualiser.cpp @@ -1191,5 +1191,3 @@ IECoreGL::ConstRenderablePtr StandardLightVisualiser::diskWireframe( float radiu return group; } - - diff --git a/src/GafferSceneUI/TranslateTool.cpp b/src/GafferSceneUI/TranslateTool.cpp index 97c34c8d6fc..68d0d9a235b 100644 --- a/src/GafferSceneUI/TranslateTool.cpp +++ b/src/GafferSceneUI/TranslateTool.cpp @@ -140,7 +140,7 @@ void TranslateTool::updateHandles( float rasterScale ) // of the target translation. For each handle, check to see // if each of the plugs it effects are settable, and if not, // disable the handle. - for( TranslateHandleIterator it( handles() ); !it.done(); ++it ) + for( TranslateHandle::Iterator it( handles() ); !it.done(); ++it ) { bool enabled = true; for( const auto &s : selection() ) diff --git a/src/GafferSceneUI/UVView.cpp b/src/GafferSceneUI/UVView.cpp index 925334ab775..e31ddad94d2 100644 --- a/src/GafferSceneUI/UVView.cpp +++ b/src/GafferSceneUI/UVView.cpp @@ -885,7 +885,7 @@ void UVView::updateTextureGadgets( const IECore::ConstCompoundObjectPtr &texture // Hide any texture gadgets we don't need this time round. - for( GadgetIterator it( textureGadgets() ); !it.done(); ++it ) + for( Gadget::Iterator it( textureGadgets() ); !it.done(); ++it ) { if( !textures->member( (*it)->getName().c_str() + gadgetNamePrefix.size() ) ) { diff --git a/src/GafferSceneUIModule/GafferSceneUIModule.cpp b/src/GafferSceneUIModule/GafferSceneUIModule.cpp index 52ca1bfa18e..37f11910e5e 100644 --- a/src/GafferSceneUIModule/GafferSceneUIModule.cpp +++ b/src/GafferSceneUIModule/GafferSceneUIModule.cpp @@ -39,7 +39,6 @@ #include "ContextAlgoBinding.h" #include "HierarchyViewBinding.h" #include "SceneGadgetBinding.h" -#include "SourceSetBinding.h" #include "ToolBinding.h" #include "ViewBinding.h" #include "VisualiserBinding.h" @@ -55,6 +54,5 @@ BOOST_PYTHON_MODULE( _GafferSceneUI ) bindHierarchyView(); bindSceneGadget(); bindContextAlgo(); - bindSourceSet(); } diff --git a/src/GafferSceneUIModule/HierarchyViewBinding.cpp b/src/GafferSceneUIModule/HierarchyViewBinding.cpp index e4f33ffea30..f9b2d6e03d9 100644 --- a/src/GafferSceneUIModule/HierarchyViewBinding.cpp +++ b/src/GafferSceneUIModule/HierarchyViewBinding.cpp @@ -97,7 +97,7 @@ class HierarchyViewFilter : public Gaffer::PathFilter if( m_scene ) { node = const_cast( m_scene->node() ); - for( ValuePlugIterator it( m_scene.get() ); !it.done(); ++it ) + for( ValuePlug::Iterator it( m_scene.get() ); !it.done(); ++it ) { sceneDirtied( it->get() ); } @@ -146,9 +146,9 @@ class HierarchyViewFilter : public Gaffer::PathFilter m_context->names( names ); for( vector::const_iterator it = names.begin(), eIt = names.end(); it != eIt; ++it ) { - const Data *newValue = m_context->get( *it ); - const Data *oldValue = oldContext->get( *it, nullptr ); - if( !oldValue || !newValue->isEqualTo( oldValue ) ) + IECore::DataPtr newValue = m_context->getAsData( *it ); + IECore::DataPtr oldValue = oldContext->getAsData( *it, nullptr ); + if( !oldValue || !newValue->isEqualTo( oldValue.get() ) ) { contextChanged( *it ); } @@ -159,7 +159,7 @@ class HierarchyViewFilter : public Gaffer::PathFilter oldContext->names( names ); for( vector::const_iterator it = names.begin(), eIt = names.end(); it != eIt; ++it ) { - if( !m_context->get( *it, nullptr ) ) + if( !m_context->getAsData( *it, nullptr ) ) { contextChanged( *it ); } diff --git a/src/GafferTest/ContextTest.cpp b/src/GafferTest/ContextTest.cpp index c1afcb0ebd9..7784effeda6 100644 --- a/src/GafferTest/ContextTest.cpp +++ b/src/GafferTest/ContextTest.cpp @@ -41,8 +41,12 @@ #include "Gaffer/Context.h" #include "IECore/Timer.h" +#include "IECore/VectorTypedData.h" +#include "IECore/PathMatcherData.h" #include "boost/lexical_cast.hpp" +#include "tbb/parallel_for.h" +#include using namespace std; using namespace boost; @@ -73,10 +77,13 @@ void GafferTest::testManyContexts() Timer t; for( int i = 0; i < 1000000; ++i ) { - ContextPtr tmp = new Context( *base, Context::Borrowed ); - tmp->set( keys[i%numKeys], i ); - GAFFERTEST_ASSERT( tmp->get( keys[i%numKeys] ) == i ); - GAFFERTEST_ASSERT( tmp->hash() != baseHash ); + // In order to efficiently manipulate a context, we need to create an EditableScope. + // ( On the other hand, using a Context directly copies new memory for the value to + // create a fully independent context, which is pretty slow ). + Context::EditableScope tmp( base.get() ); + tmp.set( keys[i%numKeys], &i ); + GAFFERTEST_ASSERT( tmp.context()->get( keys[i%numKeys] ) == i ); + GAFFERTEST_ASSERT( tmp.context()->hash() != baseHash ); } } @@ -140,67 +147,256 @@ void GafferTest::testScopingNullContext() void GafferTest::testEditableScope() { + testEditableScopeTyped( 10, 20 ); + testEditableScopeTyped( 10.0, 20.0 ); + testEditableScopeTyped( std::string( "a" ), std::string( "b" ) ); + testEditableScopeTyped( IECore::InternedString( "a" ), IECore::InternedString( "b" ) ); + testEditableScopeTyped( std::vector{ 1, 2, 3, 4 }, std::vector{ 5, 6, 7 } ); + testEditableScopeTyped( std::vector{ "a", "AA" }, std::vector{ "bbbbbbb" } ); + testEditableScopeTyped( std::vector{ "a", "AA" }, std::vector{ "bbbbbbb" } ); + + PathMatcher a; + a.addPath( "/a/y" ); + a.addPath( "/b/y" ); + a.addPath( "/c/y" ); + PathMatcher b; + b.addPath( "/a/x" ); + b.addPath( "/b/x" ); + testEditableScopeTyped( a, b ); + + // Test specific calls for dealing with time + Gaffer::ContextPtr baseContext = new Gaffer::Context(); + Gaffer::Context::EditableScope scope( baseContext.get() ); + const Gaffer::Context *currentContext = Gaffer::Context::current(); + + scope.setFrame( 5 ); + GAFFERTEST_ASSERT( currentContext->getFrame() == 5 ); + + float framesPerSecond = 8; + scope.setFramesPerSecond( &framesPerSecond ); + GAFFERTEST_ASSERT( currentContext->getFramesPerSecond() == 8 ); + + scope.setTime( 9 ); + GAFFERTEST_ASSERT( currentContext->getFrame() == 72 ); + + scope.setTime( 8.5 ); + GAFFERTEST_ASSERT( currentContext->getFrame() == 68 ); + +} + +// Create the number of contexts specified, and return counts for how many collisions there are +// in each of the four 32 bit sections of the context hash. MurmurHash performs good mixing, so +// the four sections should be independent, and as long as collisions within each section occur only +// at the expected rate, the chance of a full collision across all 4 should be infinitesimal +// ( we don't want to check for collisions in the whole 128 bit hash, since it would take years +// for one to occur randomly ) +// "mode" switches betwen 4 modes for creating contexts: +// 0 : 1 entry with a single increment int +// 1 : 40 fixed strings, plus a single incrementing int +// 2 : 20 random floats +// 3 : an even mixture of the previous 3 modes +// "seed" can be used to perform different runs to get an average number. +// The goal is that regardless of how we create the contexts, they are all unique, and should therefore +// have an identical chance of collisions if our hashing performs ideally. +std::tuple GafferTest::countContextHash32Collisions( int contexts, int mode, int seed ) +{ + std::unordered_set used[4]; + + InternedString a( "a" ); + InternedString numberNames[40]; + for( int i = 0; i < 40; i++ ) + { + numberNames[i] = InternedString( i ); + } + + unsigned int rand_seed = seed; + int collisions[4] = {0,0,0,0}; + for( int i = 0; i < contexts; i++ ) + { + int curMode = mode; + int elementSeed = seed * contexts + i; + if( curMode == 3 ) + { + curMode = i % 3; + elementSeed = seed * contexts + i / 3; + } + + Context c; + if( curMode == 0 ) + { + c.set( a, elementSeed ); + } + else if( curMode == 1 ) + { + for( int j = 0; j < 40; j++ ) + { + c.set( numberNames[j], j ); + } + c.set( a, elementSeed ); + } + else if( curMode == 2 ) + { + for( int j = 0; j < 20; j++ ) + { + c.set( numberNames[j], rand_r( &rand_seed ) ); + } + } + + if( !used[0].insert( (uint32_t)( c.hash().h1() ) ).second ) + { + collisions[0]++; + } + if( !used[1].insert( (uint32_t)( c.hash().h1() >> 32 ) ).second ) + { + collisions[1]++; + } + if( !used[2].insert( (uint32_t)( c.hash().h2() ) ).second ) + { + collisions[2]++; + } + if( !used[3].insert( (uint32_t)( c.hash().h2() >> 32 ) ).second ) + { + collisions[3]++; + } + } + + return std::make_tuple( collisions[0], collisions[1], collisions[2], collisions[3] ); +} + +void GafferTest::testContextHashPerformance( int numEntries, int entrySize, bool startInitialized ) +{ + // We usually deal with contexts that already have some stuff in them, so adding some entries + // to the context makes this test more realistic ContextPtr baseContext = new Context(); - baseContext->set( "a", 10 ); - baseContext->set( "b", 20 ); + for( int i = 0; i < numEntries; i++ ) + { + baseContext->set( InternedString( i ), std::string( entrySize, 'x') ); + } + + const InternedString varyingVarName = "varyVar"; + if( startInitialized ) + { + baseContext->set( varyingVarName, -1 ); + } + + Context::Scope baseScope( baseContext.get() ); + + const ThreadState &threadState = ThreadState::current(); + + tbb::parallel_for( tbb::blocked_range( 0, 10000000 ), [&threadState, &varyingVarName]( const tbb::blocked_range &r ) + { + for( int i = r.begin(); i != r.end(); ++i ) + { + Context::EditableScope scope( threadState ); + scope.set( varyingVarName, &i ); + + // This call is relied on by ValuePlug's HashCacheKey, so it is crucial that it be fast + scope.context()->hash(); + } + } + ); + +} + +void GafferTest::testContextCopyPerformance( int numEntries, int entrySize ) +{ + // We usually deal with contexts that already have some stuff in them, so adding some entries + // to the context makes this test more realistic + ContextPtr baseContext = new Context(); + for( int i = 0; i < numEntries; i++ ) + { + baseContext->set( InternedString( i ), std::string( entrySize, 'x') ); + } - const IntData *aData = baseContext->get( "a" ); - size_t aRefCount = aData->refCount(); + tbb::parallel_for( + tbb::blocked_range( 0, 1000000 ), + [&baseContext]( const tbb::blocked_range &r ) + { + for( int i = r.begin(); i != r.end(); ++i ) + { + ContextPtr copy = new Context( *baseContext ); + } + } + ); + +} + +void GafferTest::testCopyEditableScope() +{ + ContextPtr copy; + { + ContextPtr context = new Context(); + context->set( "a", 1 ); + context->set( "b", 2 ); + context->set( "c", 3 ); + + int ten = 10; + string cat = "cat"; + Context::EditableScope scope( context.get() ); + scope.set( "a", &ten ); + scope.setAllocated( "b", 20 ); + scope.set( "d", &ten ); + scope.setAllocated( "e", 40 ); + scope.set( "f", &cat ); + copy = new Context( *scope.context() ); + } - const IntData *bData = baseContext->get( "b" ); - size_t bRefCount = bData->refCount(); + // Both the original context and the EditableScope have + // been destructed, but a deep copy should have been taken + // to preserve all values. + + GAFFERTEST_ASSERTEQUAL( copy->get( "a" ), 10 ); + GAFFERTEST_ASSERTEQUAL( copy->get( "b" ), 20 ); + GAFFERTEST_ASSERTEQUAL( copy->get( "c" ), 3 ); + GAFFERTEST_ASSERTEQUAL( copy->get( "d" ), 10 ); + GAFFERTEST_ASSERTEQUAL( copy->get( "e" ), 40 ); + GAFFERTEST_ASSERTEQUAL( copy->get( "f" ), "cat" ); + + // A second copy should be fairly cheap, just referencing + // the same data. + + ContextPtr copy2 = new Context( *copy ); + GAFFERTEST_ASSERTEQUAL( ©->get( "a" ), ©2->get( "a" ) ); + GAFFERTEST_ASSERTEQUAL( ©->get( "b" ), ©2->get( "b" ) ); + GAFFERTEST_ASSERTEQUAL( ©->get( "c" ), ©2->get( "c" ) ); + GAFFERTEST_ASSERTEQUAL( ©->get( "d" ), ©2->get( "d" ) ); + GAFFERTEST_ASSERTEQUAL( ©->get( "e" ), ©2->get( "e" ) ); + GAFFERTEST_ASSERTEQUAL( ©->get( "f" ), ©2->get( "f" ) ); + + // And the second copy should still be valid if the first + // one is destroyed. + + copy = nullptr; + GAFFERTEST_ASSERTEQUAL( copy2->get( "a" ), 10 ); + GAFFERTEST_ASSERTEQUAL( copy2->get( "b" ), 20 ); + GAFFERTEST_ASSERTEQUAL( copy2->get( "c" ), 3 ); + GAFFERTEST_ASSERTEQUAL( copy2->get( "d" ), 10 ); + GAFFERTEST_ASSERTEQUAL( copy2->get( "e" ), 40 ); + GAFFERTEST_ASSERTEQUAL( copy2->get( "f" ), "cat" ); +} + +void GafferTest::testContextHashValidation() +{ + ContextPtr context = new Context(); + Context::EditableScope scope( context.get() ); + // If we modify a value behind the back of + // the EditableScope, we want that to be detected + // in the next call to `get()`. + + int value = 0; + scope.set( "value", &value ); + value = 1; // Naughty! + + std::string error = ""; + try + { + scope.context()->get( "value" ); + } + catch( const std::exception &e ) { - // Scope an editable copy of the context - Context::EditableScope scope( baseContext.get() ); - - const Context *currentContext = Context::current(); - GAFFERTEST_ASSERT( currentContext != baseContext ); - - // The editable copy should be identical to the original, - // and the original should be unchanged. - GAFFERTEST_ASSERT( baseContext->get( "a" ) == 10 ); - GAFFERTEST_ASSERT( baseContext->get( "b" ) == 20 ); - GAFFERTEST_ASSERT( currentContext->get( "a" ) == 10 ); - GAFFERTEST_ASSERT( currentContext->get( "b" ) == 20 ); - GAFFERTEST_ASSERT( currentContext->hash() == baseContext->hash() ); - - // The copy should even be referencing the exact same data - // as the original. - GAFFERTEST_ASSERT( baseContext->get( "a" ) == aData ); - GAFFERTEST_ASSERT( baseContext->get( "b" ) == bData ); - GAFFERTEST_ASSERT( currentContext->get( "a" ) == aData ); - GAFFERTEST_ASSERT( currentContext->get( "b" ) == bData ); - - // But it shouldn't have affected the reference counts, because - // we rely on the base context to maintain the lifetime for us - // as an optimisation. - GAFFERTEST_ASSERT( aData->refCount() == aRefCount ); - GAFFERTEST_ASSERT( bData->refCount() == bRefCount ); - - // Editing the copy shouldn't affect the original - scope.set( "c", 30 ); - GAFFERTEST_ASSERT( baseContext->get( "c", -1 ) == -1 ); - GAFFERTEST_ASSERT( currentContext->get( "c" ) == 30 ); - - // Even if we're editing a variable that exists in - // the original. - scope.set( "a", 40 ); - GAFFERTEST_ASSERT( baseContext->get( "a" ) == 10 ); - GAFFERTEST_ASSERT( currentContext->get( "a" ) == 40 ); - - // And we should be able to remove a variable from the - // copy without affecting the original too. - scope.remove( "b" ); - GAFFERTEST_ASSERT( baseContext->get( "b" ) == 20 ); - GAFFERTEST_ASSERT( currentContext->get( "b", -1 ) == -1 ); - - // And none of the edits should have affected the original - // data at all. - GAFFERTEST_ASSERT( baseContext->get( "a" ) == aData ); - GAFFERTEST_ASSERT( baseContext->get( "b" ) == bData ); - GAFFERTEST_ASSERT( aData->refCount() == aRefCount ); - GAFFERTEST_ASSERT( bData->refCount() == bRefCount ); + error = e.what(); } + GAFFERTEST_ASSERTEQUAL( error, "Context variable \"value\" has an invalid hash" ); } diff --git a/src/GafferSceneModule/RendererAlgoBinding.h b/src/GafferTest/RandomTest.cpp similarity index 72% rename from src/GafferSceneModule/RendererAlgoBinding.h rename to src/GafferTest/RandomTest.cpp index ba38cefaf35..8c5f9c43530 100644 --- a/src/GafferSceneModule/RendererAlgoBinding.h +++ b/src/GafferTest/RandomTest.cpp @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////// // -// Copyright (c) 2016, Image Engine Design Inc. All rights reserved. +// Copyright (c) 2021, 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 @@ -34,14 +34,31 @@ // ////////////////////////////////////////////////////////////////////////// -#ifndef GAFFERSCENEMODULE_RENDERERALGOBINDING_H -#define GAFFERSCENEMODULE_RENDERERALGOBINDING_H +#include "GafferTest/RandomTest.h" -namespace GafferSceneModule -{ +#include "GafferTest/Assert.h" + +#include "Gaffer/Context.h" +#include "Gaffer/Random.h" +#include "Gaffer/StringPlug.h" -void bindRendererAlgo(); +using namespace std; +using namespace boost; +using namespace IECore; +using namespace Gaffer; + +void GafferTest::testRandomPerf() +{ + ContextPtr base = new Context(); + InternedString varName = "varName"; -} // namespace GafferSceneModule + Gaffer::RandomPtr random = new Gaffer::Random(); + random->contextEntryPlug()->setValue( varName.string() ); -#endif // GAFFERSCENEMODULE_RENDERERALGOBINDING_H + Context::EditableScope scope( base.get() ); + for( int i = 0; i < 100000; ++i ) + { + scope.set( varName, &i ); + random->outColorPlug()->getValue(); + } +} diff --git a/src/GafferTestModule/GafferTestModule.cpp b/src/GafferTestModule/GafferTestModule.cpp index 489fd272fe6..f40ca07664d 100644 --- a/src/GafferTestModule/GafferTestModule.cpp +++ b/src/GafferTestModule/GafferTestModule.cpp @@ -43,6 +43,7 @@ #include "GafferTest/FilteredRecursiveChildIteratorTest.h" #include "GafferTest/MetadataTest.h" #include "GafferTest/MultiplyNode.h" +#include "GafferTest/RandomTest.h" #include "GafferTest/RecursiveChildIteratorTest.h" #include "LRUCacheTest.h" @@ -62,6 +63,13 @@ static void testMetadataThreadingWrapper() testMetadataThreading(); } +static boost::python::tuple countContextHash32CollisionsWrapper( int entries, int mode, int seed ) +{ + IECorePython::ScopedGILRelease gilRelease; + auto result = countContextHash32Collisions( entries, mode, seed ); + return boost::python::make_tuple( std::get<0>(result), std::get<1>(result), std::get<2>(result), std::get<3>(result) ); +} + BOOST_PYTHON_MODULE( _GafferTest ) { @@ -83,8 +91,14 @@ BOOST_PYTHON_MODULE( _GafferTest ) def( "testManyEnvironmentSubstitutions", &testManyEnvironmentSubstitutions ); def( "testScopingNullContext", &testScopingNullContext ); def( "testEditableScope", &testEditableScope ); + def( "countContextHash32Collisions", &countContextHash32CollisionsWrapper ); + def( "testContextHashPerformance", &testContextHashPerformance ); + def( "testContextCopyPerformance", &testContextCopyPerformance ); + def( "testCopyEditableScope", &testCopyEditableScope ); + def( "testContextHashValidation", &testContextHashValidation ); def( "testComputeNodeThreading", &testComputeNodeThreading ); def( "testDownstreamIterator", &testDownstreamIterator ); + def( "testRandomPerf", &testRandomPerf ); bindTaskMutexTest(); bindLRUCacheTest(); diff --git a/src/GafferTestModule/TaskMutexTest.cpp b/src/GafferTestModule/TaskMutexTest.cpp index f349c59520e..0ff6fe9284b 100644 --- a/src/GafferTestModule/TaskMutexTest.cpp +++ b/src/GafferTestModule/TaskMutexTest.cpp @@ -40,11 +40,8 @@ #include "GafferTest/Assert.h" -#include "Gaffer/Private/IECorePreview/ParallelAlgo.h" #include "Gaffer/Private/IECorePreview/TaskMutex.h" -#include "boost/make_unique.hpp" - #include "tbb/enumerable_thread_specific.h" #include "tbb/parallel_for.h" @@ -134,7 +131,7 @@ void testTaskMutexWithinIsolate() auto getMutexWithinIsolate = [&mutex]() { - ParallelAlgo::isolate( + tbb::this_task_arena::isolate( [&mutex]() { TaskMutex::ScopedLock lock( mutex ); GAFFERTEST_ASSERT( lock.lockType() == TaskMutex::ScopedLock::LockType::Write ) @@ -144,7 +141,7 @@ void testTaskMutexWithinIsolate() }; - ParallelAlgo::isolate( + tbb::this_task_arena::isolate( [&]() { tbb::parallel_for( tbb::blocked_range( 0, 1000000 ), @@ -207,7 +204,7 @@ void testTaskMutexJoiningOuterTasks() std::vector independentTasks; for( size_t i = 0; i < tbb::tbb_thread::hardware_concurrency() * 1000; ++i ) { - independentTasks.push_back( boost::make_unique() ); + independentTasks.push_back( std::make_unique() ); } tbb::parallel_for( diff --git a/src/GafferTestModule/ValuePlugTest.cpp b/src/GafferTestModule/ValuePlugTest.cpp index 50f75720388..7551fd58a2e 100644 --- a/src/GafferTestModule/ValuePlugTest.cpp +++ b/src/GafferTestModule/ValuePlugTest.cpp @@ -38,29 +38,55 @@ #include "ValuePlugTest.h" -#include "GafferTest/MultiplyNode.h" - +#include "Gaffer/Context.h" +#include "Gaffer/NumericPlug.h" #include "Gaffer/ValuePlug.h" #include "tbb/parallel_for.h" +#include "IECorePython/ScopedGILRelease.h" + + using namespace boost::python; using namespace Gaffer; -using namespace GafferTest; namespace { -void testValuePlugContentionForOneItem() +// Call getValue() on the given plug many times in parallel. +// +// Evaluating the same value over and over again is obviously not useful, +// but it can help turn up performance issues that can happen when a +// downstream graph ends up repeatedly evaluating something which turn out +// not to vary. +void parallelGetValue( const IntPlug *plug, int iterations ) { - MultiplyNodePtr node = new MultiplyNode; + IECorePython::ScopedGILRelease gilRelease; + tbb::parallel_for( + tbb::blocked_range( 0, iterations ), + [&plug]( const tbb::blocked_range &r ) { + for( int i = r.begin(); i < r.end(); ++i ) + { + plug->getValue(); + } + } + ); +} +// Variant of the above which stores the iteration in a context variable, allowing +// the parallel evaluates to vary +void parallelGetValueWithVar( const IntPlug *plug, int iterations, const IECore::InternedString iterationVar ) +{ + IECorePython::ScopedGILRelease gilRelease; + const ThreadState &threadState = ThreadState::current(); tbb::parallel_for( - tbb::blocked_range( 0, 10000000 ), - [&node]( const tbb::blocked_range &r ) { + tbb::blocked_range( 0, iterations ), + [&plug, &iterationVar, &threadState]( const tbb::blocked_range &r ) { + Context::EditableScope scope( threadState ); for( int i = r.begin(); i < r.end(); ++i ) { - node->productPlug()->getValue(); + scope.set( iterationVar, &i ); + plug->getValue(); } } ); @@ -70,5 +96,6 @@ void testValuePlugContentionForOneItem() void GafferTestModule::bindValuePlugTest() { - def( "testValuePlugContentionForOneItem", &testValuePlugContentionForOneItem ); + def( "parallelGetValue", ¶llelGetValue ); + def( "parallelGetValue", ¶llelGetValueWithVar ); } diff --git a/src/GafferUI/AnimationGadget.cpp b/src/GafferUI/AnimationGadget.cpp index 2fbb3b25727..66dc49d4ecb 100644 --- a/src/GafferUI/AnimationGadget.cpp +++ b/src/GafferUI/AnimationGadget.cpp @@ -155,7 +155,7 @@ void computeGrid( const ViewportGadget *viewportGadget, float fps, AxisDefinitio pxPerUnit.x *= 5; // If there's not enough space for this zoom level, try using every 10th frame. - while( pxPerUnit.x < labelMinSize.x && pxPerUnit.x != 0 ) + while( pxPerUnit.x < labelMinSize.x && pxPerUnit.x != 0 ) { xStride *= 10; pxPerUnit.x *= 10; @@ -639,7 +639,7 @@ void AnimationGadget::frame() const Animation::CurvePlug *curvePlug = IECore::runTimeCast( &runtimeTyped ); for( const auto &key : *curvePlug ) - { + { b.extendBy( V3f( key.getTime(), key.getValue(), 0 ) ); } } @@ -652,7 +652,7 @@ void AnimationGadget::frame() const Animation::CurvePlug *curvePlug = IECore::runTimeCast( &runtimeTyped ); for( const auto &key : *curvePlug ) - { + { b.extendBy( V3f( key.getTime(), key.getValue(), 0 ) ); } } diff --git a/src/GafferUI/AnnotationsGadget.cpp b/src/GafferUI/AnnotationsGadget.cpp index 54cb3078546..a32a45177cc 100644 --- a/src/GafferUI/AnnotationsGadget.cpp +++ b/src/GafferUI/AnnotationsGadget.cpp @@ -105,9 +105,37 @@ IECoreGL::Texture *numericBookmarkTexture() return numericBookmarkTexture.get(); } +string wrap( const std::string &text, size_t maxLineLength ) +{ + string result; + + using Tokenizer = boost::tokenizer>; + boost::char_separator separator( "", " \n" ); + Tokenizer tokenizer( text, separator ); + + size_t lineLength = 0; + for( const auto &s : tokenizer ) + { + if( s == "\n" ) + { + result += s; + lineLength = 0; + } + else if( lineLength == 0 || lineLength + s.size() < maxLineLength ) + { + result += s; + lineLength += s.size(); + } + else + { + result += "\n" + s; + lineLength = s.size(); + } + } + + return result; +} float g_offset = 0.5; -float g_borderWidth = 0.5; -float g_spacing = 0.25; } // namespace @@ -117,8 +145,10 @@ float g_spacing = 0.25; GAFFER_GRAPHCOMPONENT_DEFINE_TYPE( AnnotationsGadget ); +const std::string AnnotationsGadget::untemplatedAnnotations = "__untemplated__"; + AnnotationsGadget::AnnotationsGadget() - : Gadget( "AnnotationsGadget" ) + : Gadget( "AnnotationsGadget" ), m_dirty( true ), m_visibleAnnotations( "*" ) { Metadata::nodeValueChangedSignal().connect( boost::bind( &AnnotationsGadget::nodeMetadataChanged, this, ::_1, ::_2, ::_3 ) @@ -129,6 +159,27 @@ AnnotationsGadget::~AnnotationsGadget() { } +void AnnotationsGadget::setVisibleAnnotations( const IECore::StringAlgo::MatchPattern &patterns ) +{ + if( patterns == m_visibleAnnotations ) + { + return; + } + + m_visibleAnnotations = patterns; + for( auto &a : m_annotations ) + { + a.second.dirty = true; + } + m_dirty = true; + dirty( DirtyType::Render ); +} + +const IECore::StringAlgo::MatchPattern &AnnotationsGadget::getVisibleAnnotations() const +{ + return m_visibleAnnotations; +} + bool AnnotationsGadget::acceptsParent( const GraphComponent *potentialParent ) const { return runTimeCast( potentialParent ); @@ -157,50 +208,12 @@ void AnnotationsGadget::doRenderLayer( Layer layer, const Style *style ) const return; } - vector registeredValues; + update(); + for( auto &ga : m_annotations ) { - const Node *node = ga.first->node(); Annotations &annotations = ga.second; - - if( annotations.dirty ) - { - annotations.renderable = false; - - annotations.bookmarked = Gaffer::MetadataAlgo::getBookmarked( node ); - annotations.renderable |= annotations.bookmarked; - - if( int bookmark = MetadataAlgo::numericBookmark( node ) ) - { - annotations.numericBookmark = std::to_string( bookmark ); - annotations.renderable = true; - } - else - { - annotations.numericBookmark = InternedString(); - } - - annotations.standardAnnotations.clear(); - registeredValues.clear(); - Metadata::registeredValues( node, registeredValues ); - for( const auto &key : registeredValues ) - { - if( boost::starts_with( key.string(), "annotation:" ) && boost::ends_with( key.string(), ":text" ) ) - { - if( auto text = Metadata::value( node, key ) ) - { - const string prefix = key.string().substr( 0, key.string().size() - 4 ); - annotations.standardAnnotations.push_back( - { text, Metadata::value( node, prefix + "color" ) } - ); - annotations.renderable = true; - } - } - } - - annotations.dirty = false; - } - + assert( !annotations.dirty ); if( !annotations.renderable ) { continue; @@ -228,41 +241,10 @@ void AnnotationsGadget::doRenderLayer( Layer layer, const Style *style ) const glPopMatrix(); } - if( annotations.standardAnnotations.size() ) + V2f origin( b.max.x + g_offset, b.max.y ); + for( const auto &a : annotations.standardAnnotations ) { - glPushMatrix(); - IECoreGL::glTranslate( V2f( b.max.x + g_offset + g_borderWidth, b.max.y - g_borderWidth ) ); - - const Color4f midGrey( 0.65, 0.65, 0.65, 1.0 ); - const Color3f darkGrey( 0.05 ); - float previousHeight = 0; - for( const auto &a : annotations.standardAnnotations ) - { - Box3f textBounds = style->textBound( Style::BodyText, a.text->readable() ); - - float yOffset; - if( &a == &annotations.standardAnnotations.front() ) - { - yOffset = -style->characterBound( Style::BodyText ).max.y; - } - else - { - yOffset = -previousHeight -g_spacing; - } - - IECoreGL::glTranslate( V2f( 0, yOffset ) ); - /// \todo We're using `renderNodeFrame()` because it's the only way we can specify a colour, - /// but really we want `renderFrame()` to provide that option. Or we could consider having - /// explicit annotation rendering methods in the Style class. - style->renderNodeFrame( - Box2f( V2f( 0, textBounds.min.y ), V2f( textBounds.max.x, textBounds.max.y ) ), - g_borderWidth, Style::NormalState, - a.color ? &(a.color->readable()) : &darkGrey - ); - style->renderText( Style::BodyText, a.text->readable(), Style::NormalState, &midGrey ); - previousHeight = textBounds.size().y + g_borderWidth * 2; - } - glPopMatrix(); + origin = style->renderAnnotation( origin, a.text(), Style::NormalState, a.colorData ? &a.color() : nullptr ); } } } @@ -282,6 +264,7 @@ void AnnotationsGadget::graphGadgetChildAdded( GraphComponent *child ) if( NodeGadget *nodeGadget = runTimeCast( child ) ) { m_annotations[nodeGadget] = Annotations(); + m_dirty = true; } } @@ -290,6 +273,7 @@ void AnnotationsGadget::graphGadgetChildRemoved( const GraphComponent *child ) if( const NodeGadget *nodeGadget = runTimeCast( child ) ) { m_annotations.erase( nodeGadget ); + m_dirty = true; } } @@ -305,7 +289,7 @@ void AnnotationsGadget::nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore:: if( !MetadataAlgo::bookmarkedAffectedByChange( key ) && !MetadataAlgo::numericBookmarkAffectedByChange( key ) && - !boost::starts_with( key.c_str(), "annotation:" ) + !MetadataAlgo::annotationsAffectedByChange( key ) ) { return; @@ -316,6 +300,76 @@ void AnnotationsGadget::nodeMetadataChanged( IECore::TypeId nodeTypeId, IECore:: auto it = m_annotations.find( gadget ); assert( it != m_annotations.end() ); it->second.dirty = true; + m_dirty = true; dirty( DirtyType::Render ); } } + +void AnnotationsGadget::update() const +{ + if( !m_dirty ) + { + return; + } + + vector templates; + MetadataAlgo::annotationTemplates( templates ); + std::sort( templates.begin(), templates.end() ); + const bool untemplatedVisible = StringAlgo::matchMultiple( untemplatedAnnotations, m_visibleAnnotations ); + + vector names; + for( auto &ga : m_annotations ) + { + const Node *node = ga.first->node(); + Annotations &annotations = ga.second; + if( !annotations.dirty ) + { + continue; + } + + annotations.renderable = false; + + annotations.bookmarked = Gaffer::MetadataAlgo::getBookmarked( node ); + annotations.renderable |= annotations.bookmarked; + + if( int bookmark = MetadataAlgo::numericBookmark( node ) ) + { + annotations.numericBookmark = std::to_string( bookmark ); + annotations.renderable = true; + } + else + { + annotations.numericBookmark = InternedString(); + } + + annotations.standardAnnotations.clear(); + names.clear(); + MetadataAlgo::annotations( node, names ); + for( const auto &name : names ) + { + if( !StringAlgo::matchMultiple( name, m_visibleAnnotations ) ) + { + const bool templated = binary_search( templates.begin(), templates.end(), name ) || name == "user"; + if( templated || !untemplatedVisible ) + { + continue; + } + } + + annotations.standardAnnotations.push_back( + MetadataAlgo::getAnnotation( node, name, /* inheritTemplate = */ true ) + ); + // Word wrap. It might be preferable to do this during + // rendering, but we have no way of querying the extent of + // `Style::renderWrappedText()`. + annotations.standardAnnotations.back().textData = new StringData( + wrap( annotations.standardAnnotations.back().text(), 60 ) + ); + } + annotations.renderable |= (bool)annotations.standardAnnotations.size(); + + annotations.dirty = false; + } + + m_dirty = false; +} diff --git a/src/GafferUI/ArrayPlugUI.cpp b/src/GafferUI/ArrayPlugUI.cpp index a5cef0d6931..2e37c73a89f 100644 --- a/src/GafferUI/ArrayPlugUI.cpp +++ b/src/GafferUI/ArrayPlugUI.cpp @@ -144,4 +144,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferUI/AuxiliaryConnectionsGadget.cpp b/src/GafferUI/AuxiliaryConnectionsGadget.cpp index e631db8a690..3416612ccce 100644 --- a/src/GafferUI/AuxiliaryConnectionsGadget.cpp +++ b/src/GafferUI/AuxiliaryConnectionsGadget.cpp @@ -86,7 +86,7 @@ void visitAuxiliaryConnections( const GraphGadget *graphGadget, const NodeGadget /// private plugs for its inputs, and we can ignore all private plugs /// unconditionally. const bool ignorePrivatePlugs = !runTimeCast( dstNode ); - for( Gaffer::RecursivePlugIterator it( dstNode ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveIterator it( dstNode ); !it.done(); ++it ) { const Gaffer::Plug *dstPlug = it->get(); if( ignorePrivatePlugs && boost::starts_with( dstPlug->getName().c_str(), "__" ) ) diff --git a/src/GafferUI/BackdropNodeGadget.cpp b/src/GafferUI/BackdropNodeGadget.cpp index 66837fe4e2f..dbc53b204bc 100644 --- a/src/GafferUI/BackdropNodeGadget.cpp +++ b/src/GafferUI/BackdropNodeGadget.cpp @@ -228,7 +228,7 @@ void BackdropNodeGadget::framed( std::vector &nodes ) const const Box3f bound3 = transformedBound( graphGadget ); const Box2f bound2( V2f( bound3.min.x, bound3.min.y ), V2f( bound3.max.x, bound3.max.y ) ); - for( NodeIterator it( nodeParent ); !it.done(); ++it ) + for( Node::Iterator it( nodeParent ); !it.done(); ++it ) { if( node() == it->get() ) { diff --git a/src/GafferUI/BoxUI.cpp b/src/GafferUI/BoxUI.cpp index 13340743851..cf90a7ba916 100644 --- a/src/GafferUI/BoxUI.cpp +++ b/src/GafferUI/BoxUI.cpp @@ -138,4 +138,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferUI/CompoundNumericNodule.cpp b/src/GafferUI/CompoundNumericNodule.cpp index ea45457e48e..9b72d7bb370 100644 --- a/src/GafferUI/CompoundNumericNodule.cpp +++ b/src/GafferUI/CompoundNumericNodule.cpp @@ -205,7 +205,7 @@ bool CompoundNumericNodule::canCreateConnection( const Gaffer::Plug *endpoint ) } // Things like float <-> Color3f.[rgb] - for( PlugIterator it( plug() ); !it.done(); ++it ) + for( Plug::Iterator it( plug() ); !it.done(); ++it ) { if( canConnect( endpoint, it->get() ) ) { @@ -236,7 +236,7 @@ void CompoundNumericNodule::createConnection( Gaffer::Plug *endpoint ) vector plugs; string allName; - for( PlugIterator it( plug() ); !it.done(); ++it ) + for( Plug::Iterator it( plug() ); !it.done(); ++it ) { if( canConnect( endpoint, it->get() ) ) { @@ -335,7 +335,7 @@ void CompoundNumericNodule::updateChildNoduleVisibility() addChild( layout ); if( NodeGadget *nodeGadget = ancestor() ) { - for( PlugIterator it( plug() ); !it.done(); ++it ) + for( Plug::Iterator it( plug() ); !it.done(); ++it ) { if( Nodule *nodule = layout->nodule( it->get() ) ) { @@ -352,7 +352,7 @@ void CompoundNumericNodule::updateChildNoduleVisibility() removeChild( layout ); if( NodeGadget *nodeGadget = ancestor() ) { - for( PlugIterator it( plug() ); !it.done(); ++it ) + for( Plug::Iterator it( plug() ); !it.done(); ++it ) { if( Nodule *nodule = layout->nodule( it->get() ) ) { diff --git a/src/GafferUI/EditScopeUI.cpp b/src/GafferUI/EditScopeUI.cpp index 82e8c925878..acad84ca4b1 100644 --- a/src/GafferUI/EditScopeUI.cpp +++ b/src/GafferUI/EditScopeUI.cpp @@ -138,4 +138,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferUI/Gadget.cpp b/src/GafferUI/Gadget.cpp index 7d515f7e64e..ad45a128e8d 100644 --- a/src/GafferUI/Gadget.cpp +++ b/src/GafferUI/Gadget.cpp @@ -198,7 +198,7 @@ void Gadget::setVisible( bool visible ) void Gadget::emitDescendantVisibilityChanged() { - for( GadgetIterator it( this ); !it.done(); ++it ) + for( Gadget::Iterator it( this ); !it.done(); ++it ) { if( !(*it)->getVisible() ) { diff --git a/src/GafferUI/GraphGadget.cpp b/src/GafferUI/GraphGadget.cpp index 1b7f0ec83b4..8e4f57cdca9 100644 --- a/src/GafferUI/GraphGadget.cpp +++ b/src/GafferUI/GraphGadget.cpp @@ -426,7 +426,7 @@ size_t GraphGadget::connectionGadgets( const Gaffer::Plug *plug, std::vector &connections, const Gaffer::Set *excludedNodes ) { - for( Gaffer::RecursivePlugIterator it( node ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveIterator it( node ); !it.done(); ++it ) { this->connectionGadgets( it->get(), connections, excludedNodes ); } @@ -436,7 +436,7 @@ size_t GraphGadget::connectionGadgets( const Gaffer::Node *node, std::vector &connections, const Gaffer::Set *excludedNodes ) const { - for( Gaffer::RecursivePlugIterator it( node ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveIterator it( node ); !it.done(); ++it ) { this->connectionGadgets( it->get(), connections, excludedNodes ); } @@ -527,7 +527,7 @@ void GraphGadget::connectedNodeGadgetsWalk( NodeGadget *gadget, std::setnode() ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveIterator it( gadget->node() ); !it.done(); ++it ) { Gaffer::Plug *plug = it->get(); if( ( direction != Gaffer::Plug::Invalid ) && ( plug->direction() != direction ) ) @@ -967,7 +967,7 @@ void GraphGadget::plugSet( Gaffer::Plug *plug ) void GraphGadget::noduleAdded( Nodule *nodule ) { addConnectionGadgets( nodule ); - for( RecursiveNoduleIterator it( nodule ); !it.done(); ++it ) + for( Nodule::RecursiveIterator it( nodule ); !it.done(); ++it ) { addConnectionGadgets( it->get() ); } @@ -976,7 +976,7 @@ void GraphGadget::noduleAdded( Nodule *nodule ) void GraphGadget::noduleRemoved( Nodule *nodule ) { removeConnectionGadgets( nodule ); - for( RecursiveNoduleIterator it( nodule ); !it.done(); ++it ) + for( Nodule::RecursiveIterator it( nodule ); !it.done(); ++it ) { removeConnectionGadgets( it->get() ); } @@ -1244,8 +1244,10 @@ bool GraphGadget::dragMove( GadgetPtr gadget, const DragDropEvent &event ) vector::const_iterator pIt = upper_bound( snapPoints.begin(), snapPoints.end(), pOffset - V2f( snapThresh ), CompareV2fX() ); for( ; pIt != pEnd; pIt++ ) { - if( fabs( pOffset[1] - (*pIt)[1] ) < snapThresh && - fabs( pOffset[0] - (*pIt)[0] ) < snapThresh ) + if( + fabs( pOffset[1] - (*pIt)[1] ) < snapThresh && + fabs( pOffset[0] - (*pIt)[0] ) < snapThresh + ) { pos = *pIt + m_dragStartPosition; break; @@ -1309,7 +1311,7 @@ void GraphGadget::updateDragReconnectCandidate( const DragDropEvent &event ) // and if so, stash what we need into our m_dragReconnect member // variables for use in dragEnd. - for( Gaffer::RecursiveOutputPlugIterator it( node ); !it.done(); ++it ) + for( Gaffer::Plug::RecursiveOutputIterator it( node ); !it.done(); ++it ) { // See if the output has a corresponding input, and that // the resulting in/out plug pair can be inserted into the @@ -1617,7 +1619,7 @@ void GraphGadget::updateGraph() } // now make sure we have gadgets for all the nodes we're meant to display - for( Gaffer::NodeIterator it( m_root.get() ); !it.done(); ++it ) + for( Gaffer::Node::Iterator it( m_root.get() ); !it.done(); ++it ) { if( !m_filter || m_filter->contains( it->get() ) ) { @@ -1709,7 +1711,7 @@ void GraphGadget::updateNodeGadgetTransform( NodeGadget *nodeGadget ) if( Gaffer::V2fPlug *p = nodePositionPlug( node, /* createIfMissing = */ false ) ) { const V2f t = p->getValue(); - m.translate( V3f( t[0], t[1], 0 ) ); + m.translate( V3f( t[0], t[1], 0 ) ); } nodeGadget->setTransform( m ); @@ -1717,7 +1719,7 @@ void GraphGadget::updateNodeGadgetTransform( NodeGadget *nodeGadget ) void GraphGadget::addConnectionGadgets( NodeGadget *nodeGadget ) { - for( RecursiveNoduleIterator it( nodeGadget ); !it.done(); ++it ) + for( Nodule::RecursiveIterator it( nodeGadget ); !it.done(); ++it ) { addConnectionGadgets( it->get() ); } @@ -1782,7 +1784,7 @@ void GraphGadget::addConnectionGadget( Nodule *dstNodule ) void GraphGadget::removeConnectionGadgets( const NodeGadget *nodeGadget ) { - for( RecursiveNoduleIterator it( nodeGadget ); !it.done(); ++it ) + for( Nodule::RecursiveIterator it( nodeGadget ); !it.done(); ++it ) { removeConnectionGadgets( it->get() ); } diff --git a/src/GafferUI/NameSwitchUI.cpp b/src/GafferUI/NameSwitchUI.cpp index b60f3bc6ccb..671d7b05fd3 100644 --- a/src/GafferUI/NameSwitchUI.cpp +++ b/src/GafferUI/NameSwitchUI.cpp @@ -149,4 +149,3 @@ struct Registration Registration g_registration; } // namespace - diff --git a/src/GafferUI/NoduleLayout.cpp b/src/GafferUI/NoduleLayout.cpp index 088ede219c2..b2c91bb2102 100644 --- a/src/GafferUI/NoduleLayout.cpp +++ b/src/GafferUI/NoduleLayout.cpp @@ -561,7 +561,7 @@ std::vector NoduleLayout::layoutOrder() // Add any plugs which should be visible - for( PlugIterator plugIt( m_parent.get() ); !plugIt.done(); ++plugIt ) + for( Plug::Iterator plugIt( m_parent.get() ); !plugIt.done(); ++plugIt ) { Plug *plug = plugIt->get(); if( boost::starts_with( plug->getName().string(), "__" ) ) diff --git a/src/GafferUI/Pointer.cpp b/src/GafferUI/Pointer.cpp index 0519e6106ae..ffb969cca11 100644 --- a/src/GafferUI/Pointer.cpp +++ b/src/GafferUI/Pointer.cpp @@ -110,9 +110,10 @@ void Pointer::setCurrent( ConstPointerPtr pointer ) { return; } - if( pointer && g_current && - pointer->image()->isEqualTo( g_current->image() ) && - pointer->hotspot() == g_current->hotspot() + if( + pointer && g_current && + pointer->image()->isEqualTo( g_current->image() ) && + pointer->hotspot() == g_current->hotspot() ) { return; diff --git a/src/GafferUI/StandardGraphLayout.cpp b/src/GafferUI/StandardGraphLayout.cpp index c589df947f8..28dadbbd664 100644 --- a/src/GafferUI/StandardGraphLayout.cpp +++ b/src/GafferUI/StandardGraphLayout.cpp @@ -205,7 +205,7 @@ class LayoutEngine // Build a map from node to vertex so we can use it to lookup nodes // when inserting edges. - for( NodeIterator it( graphGadget->getRoot() ); !it.done(); ++it ) + for( Node::Iterator it( graphGadget->getRoot() ); !it.done(); ++it ) { Node *node = it->get(); const NodeGadget *nodeGadget = graphGadget->nodeGadget( node ); @@ -232,7 +232,7 @@ class LayoutEngine for( NodesToVertices::const_iterator it = m_nodesToVertices.begin(), eIt = m_nodesToVertices.end(); it != eIt; ++it ) { - for( RecursiveInputPlugIterator pIt( it->first ); !pIt.done(); ++pIt ) + for( Plug::RecursiveInputIterator pIt( it->first ); !pIt.done(); ++pIt ) { ConnectionGadget *connection = graphGadget->connectionGadget( pIt->get() ); if( !connection || connection->getMinimised() ) @@ -527,7 +527,7 @@ class LayoutEngine for( NodesToVertices::const_iterator nodeIt = m_nodesToVertices.begin(), eIt = m_nodesToVertices.end(); nodeIt != eIt; ++nodeIt ) { - for( RecursiveInputPlugIterator plugIt( nodeIt->first ); !plugIt.done(); ++plugIt ) + for( Plug::RecursiveInputIterator plugIt( nodeIt->first ); !plugIt.done(); ++plugIt ) { const Plug *dstPlug = plugIt->get(); const Plug *srcPlug = dstPlug->getInput(); @@ -1097,7 +1097,7 @@ class LayoutEngine } bool leftEdgeBlocked = false; - for( RecursiveNoduleIterator it( dstNodeGadget ); !it.done(); ++it ) + for( Nodule::RecursiveIterator it( dstNodeGadget ); !it.done(); ++it ) { V3f noduleTangent = dstNodeGadget->connectionTangent( it->get() ); @@ -1175,7 +1175,7 @@ bool StandardGraphLayout::connectNodes( GraphGadget *graph, Gaffer::Set *nodes, } bool hasInputs = false; - for( RecursiveInputPlugIterator it( node ); !it.done(); ++it ) + for( Plug::RecursiveInputIterator it( node ); !it.done(); ++it ) { if( (*it)->getInput() && nodeGadget->nodule( it->get() ) ) { @@ -1437,7 +1437,7 @@ bool StandardGraphLayout::connectNodeInternal( GraphGadget *graph, Gaffer::Node size_t StandardGraphLayout::outputPlugs( NodeGadget *nodeGadget, std::vector &plugs ) const { - for( RecursiveOutputPlugIterator it( nodeGadget->node() ); !it.done(); it++ ) + for( Plug::RecursiveOutputIterator it( nodeGadget->node() ); !it.done(); it++ ) { if( auto nodule = nodeGadget->nodule( it->get() ) ) { @@ -1471,7 +1471,7 @@ size_t StandardGraphLayout::outputPlugs( GraphGadget *graph, Gaffer::Set *nodes, size_t StandardGraphLayout::unconnectedInputPlugs( NodeGadget *nodeGadget, std::vector &plugs ) const { plugs.clear(); - for( RecursiveInputPlugIterator it( nodeGadget->node() ); !it.done(); it++ ) + for( Plug::RecursiveInputIterator it( nodeGadget->node() ); !it.done(); it++ ) { if( (*it)->getInput() == nullptr and nodeGadget->nodule( it->get() ) ) { @@ -1492,7 +1492,7 @@ Gaffer::Plug *StandardGraphLayout::correspondingOutput( const Gaffer::Plug *inpu return nullptr; } - for( RecursiveOutputPlugIterator it( dependencyNode ); !it.done(); ++it ) + for( Plug::RecursiveOutputIterator it( dependencyNode ); !it.done(); ++it ) { if( dependencyNode->correspondingInput( it->get() ) == input ) { diff --git a/src/GafferUI/StandardNodeGadget.cpp b/src/GafferUI/StandardNodeGadget.cpp index 509dc9c3a05..a969d42ad67 100644 --- a/src/GafferUI/StandardNodeGadget.cpp +++ b/src/GafferUI/StandardNodeGadget.cpp @@ -621,7 +621,7 @@ void StandardNodeGadget::enter( Gadget *gadget ) { if( m_labelsVisibleOnHover ) { - for( RecursiveStandardNoduleIterator it( gadget ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( gadget ); !it.done(); ++it ) { (*it)->setLabelVisible( true ); } @@ -632,7 +632,7 @@ void StandardNodeGadget::leave( Gadget *gadget ) { if( m_labelsVisibleOnHover ) { - for( RecursiveStandardNoduleIterator it( gadget ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( gadget ); !it.done(); ++it ) { (*it)->setLabelVisible( false ); } @@ -646,7 +646,7 @@ bool StandardNodeGadget::dragEnter( GadgetPtr gadget, const DragDropEvent &event { // Display the labels for all the compatible nodules so the // user can see their options. - for( RecursiveStandardNoduleIterator it( this ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( this ); !it.done(); ++it ) { (*it)->setLabelVisible( canConnect( event, it->get() ) ); } @@ -690,7 +690,7 @@ bool StandardNodeGadget::dragLeave( GadgetPtr gadget, const DragDropEvent &event if( m_dragDestination != event.destinationGadget ) { m_dragDestination->setHighlighted( false ); - for( RecursiveStandardNoduleIterator it( this ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( this ); !it.done(); ++it ) { (*it)->setLabelVisible( false ); } @@ -710,7 +710,7 @@ bool StandardNodeGadget::drop( GadgetPtr gadget, const DragDropEvent &event ) connect( event, m_dragDestination ); m_dragDestination->setHighlighted( false ); - for( RecursiveStandardNoduleIterator it( this ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( this ); !it.done(); ++it ) { (*it)->setLabelVisible( false ); } @@ -729,7 +729,7 @@ ConnectionCreator *StandardNodeGadget::closestDragDestination( const DragDropEve ConnectionCreator *result = nullptr; float maxDist = Imath::limits::max(); - for( RecursiveConnectionCreatorIterator it( this ); !it.done(); it++ ) + for( ConnectionCreator::RecursiveIterator it( this ); !it.done(); it++ ) { if( !(*it)->getVisible() ) { diff --git a/src/GafferUI/StandardNodule.cpp b/src/GafferUI/StandardNodule.cpp index cf223dded4e..685a4f7c6eb 100644 --- a/src/GafferUI/StandardNodule.cpp +++ b/src/GafferUI/StandardNodule.cpp @@ -465,7 +465,7 @@ void StandardNodule::setCompatibleLabelsVisible( const DragDropEvent &event, boo return; } - for( RecursiveStandardNoduleIterator it( nodeGadget ); !it.done(); ++it ) + for( StandardNodule::RecursiveIterator it( nodeGadget ); !it.done(); ++it ) { if( creator->canCreateConnection( it->get()->plug() ) ) { diff --git a/src/GafferUI/StandardStyle.cpp b/src/GafferUI/StandardStyle.cpp index 833b1267c95..0f4a29af975 100644 --- a/src/GafferUI/StandardStyle.cpp +++ b/src/GafferUI/StandardStyle.cpp @@ -506,6 +506,11 @@ V3f auxiliaryConnectionArrowPosition( const Box2f &dstNodeFrame, const V3f &p, c return p + v * t; } +float luminance( const Color3f &c ) +{ + return c.dot( V3f( 0.2126, 0.7152, 0.0722 ) ); +} + } // namespace ////////////////////////////////////////////////////////////////////////// @@ -680,35 +685,7 @@ void StandardStyle::renderFrame( const Imath::Box2f &frame, float borderWidth, S void StandardStyle::renderNodeFrame( const Imath::Box2f &contents, float borderWidth, State state, const Imath::Color3f *userColor ) const { - - Box2f b = contents; - V2f bw( borderWidth ); - b.min -= bw; - b.max += bw; - - V2f cornerSizes = bw / b.size(); - glUniform1i( g_isCurveParameter, 0 ); - glUniform1i( g_borderParameter, 1 ); - glUniform2f( g_borderRadiusParameter, cornerSizes.x, cornerSizes.y ); - glUniform1f( g_borderWidthParameter, 0.15f / borderWidth ); - glUniform1i( g_edgeAntiAliasingParameter, 0 ); - glUniform1i( g_textureTypeParameter, 0 ); - - glColor( colorForState( RaisedColor, state, userColor ) ); - - glBegin( GL_QUADS ); - - glTexCoord2f( 0, 0 ); - glVertex2f( b.min.x, b.min.y ); - glTexCoord2f( 0, 1 ); - glVertex2f( b.min.x, b.max.y ); - glTexCoord2f( 1, 1 ); - glVertex2f( b.max.x, b.max.y ); - glTexCoord2f( 1, 0 ); - glVertex2f( b.max.x, b.min.y ); - - glEnd(); - + renderFrameInternal( contents, borderWidth, 0.15f / borderWidth, colorForState( RaisedColor, state, userColor ) ); } void StandardStyle::renderNodule( float radius, State state, const Imath::Color3f *userColor ) const @@ -843,6 +820,39 @@ Imath::V3f StandardStyle::closestPointOnConnection( const Imath::V3f &p, const I } +Imath::V2f StandardStyle::renderAnnotation( const Imath::V2f &origin, const std::string &text, State state, const Imath::Color3f *userColor ) const +{ + const float padding = 0.5; + const float borderWidth = 0.1; + const float spacing = 0.25; + const Color3f defaultColor( 0.05 ); + const Box3f characterBound = this->characterBound( BodyText ); + + glPushMatrix(); + + IECoreGL::glTranslate( origin + V2f( padding, -padding - characterBound.max.y ) ); + + const Color4f darkGrey( 0.1, 0.1, 0.1, 1.0 ); + const Color4f midGrey( 0.65, 0.65, 0.65, 1.0 ); + + Box3f textBounds = textBound( BodyText, text ); + + renderFrameInternal( + Box2f( V2f( 0, textBounds.min.y ), V2f( textBounds.max.x, characterBound.max.y ) ), + padding, borderWidth, colorForState( RaisedColor, state, userColor ) + ); + + const Color3f &color = userColor ? *userColor : defaultColor; + renderText( + Style::BodyText, text, Style::NormalState, + luminance( color ) > 0.4 ? &darkGrey : &midGrey + ); + + glPopMatrix(); + + return origin - V2f( 0, characterBound.max.y - textBounds.min.y + padding * 2 + spacing ); +} + void StandardStyle::renderSolidRectangle( const Imath::Box2f &box ) const { glUniform1i( g_isCurveParameter, 0 ); @@ -1202,6 +1212,37 @@ unsigned int StandardStyle::connectionDisplayList() return g_list; } +void StandardStyle::renderFrameInternal( const Imath::Box2f &contents, float padding, float borderWidth, const Imath::Color3f &userColor ) const +{ + Box2f b = contents; + V2f p( padding ); + b.min -= p; + b.max += p; + + V2f cornerSizes = p / b.size(); + glUniform1i( g_isCurveParameter, 0 ); + glUniform1i( g_borderParameter, 1 ); + glUniform2f( g_borderRadiusParameter, cornerSizes.x, cornerSizes.y ); + glUniform1f( g_borderWidthParameter, borderWidth ); + glUniform1i( g_edgeAntiAliasingParameter, 0 ); + glUniform1i( g_textureTypeParameter, 0 ); + + glColor( userColor ); + + glBegin( GL_QUADS ); + + glTexCoord2f( 0, 0 ); + glVertex2f( b.min.x, b.min.y ); + glTexCoord2f( 0, 1 ); + glVertex2f( b.min.x, b.max.y ); + glTexCoord2f( 1, 1 ); + glVertex2f( b.max.x, b.max.y ); + glTexCoord2f( 1, 0 ); + glVertex2f( b.max.x, b.min.y ); + + glEnd(); +} + Imath::Color3f StandardStyle::colorForState( Color c, State s, const Imath::Color3f *userColor ) const { Color3f result = userColor ? *userColor : m_colors[c]; diff --git a/src/GafferUI/ViewportGadget.cpp b/src/GafferUI/ViewportGadget.cpp index 363426a5626..c236c496b54 100644 --- a/src/GafferUI/ViewportGadget.cpp +++ b/src/GafferUI/ViewportGadget.cpp @@ -637,14 +637,14 @@ class ViewportGadget::CameraController : public boost::noncopyable GAFFER_GRAPHCOMPONENT_DEFINE_TYPE( ViewportGadget ); ViewportGadget::ViewportGadget( GadgetPtr primaryChild ) - : Gadget(), - m_cameraController( new CameraController() ), - m_cameraInMotion( false ), - m_cameraEditable( true ), - m_preciseMotionAllowed( true ), - m_preciseMotionEnabled( false ), - m_dragTracking( DragTracking::NoDragTracking ), - m_variableAspectZoom( false ) + : Gadget(), + m_cameraController( new CameraController() ), + m_cameraInMotion( false ), + m_cameraEditable( true ), + m_preciseMotionAllowed( true ), + m_preciseMotionEnabled( false ), + m_dragTracking( DragTracking::NoDragTracking ), + m_variableAspectZoom( false ) { // Viewport visibility is managed by GadgetWidgets, diff --git a/src/GafferUIModule/AnimationGadgetBinding.cpp b/src/GafferUIModule/AnimationGadgetBinding.cpp index f3fa1e00426..47eefbb253f 100644 --- a/src/GafferUIModule/AnimationGadgetBinding.cpp +++ b/src/GafferUIModule/AnimationGadgetBinding.cpp @@ -84,4 +84,3 @@ void GafferUIModule::bindAnimationGadget() ; } - diff --git a/src/GafferUIModule/GraphGadgetBinding.cpp b/src/GafferUIModule/GraphGadgetBinding.cpp index a21c305f794..c272e50b7d9 100644 --- a/src/GafferUIModule/GraphGadgetBinding.cpp +++ b/src/GafferUIModule/GraphGadgetBinding.cpp @@ -259,6 +259,9 @@ void GafferUIModule::bindGraphGadget() ; GadgetClass() + .def_readonly( "untemplatedAnnotations", &AnnotationsGadget::untemplatedAnnotations ) + .def( "setVisibleAnnotations", &AnnotationsGadget::setVisibleAnnotations ) + .def( "getVisibleAnnotations", &AnnotationsGadget::getVisibleAnnotations, return_value_policy() ) ; IECorePython::RunTimeTypedClass() diff --git a/src/GafferVDB/LevelSetOffset.cpp b/src/GafferVDB/LevelSetOffset.cpp index 30e43b9acce..b7f08f5b887 100644 --- a/src/GafferVDB/LevelSetOffset.cpp +++ b/src/GafferVDB/LevelSetOffset.cpp @@ -36,6 +36,8 @@ #include "GafferVDB/LevelSetOffset.h" +#include "GafferVDB/Interrupter.h" + #include "IECoreVDB/VDBObject.h" #include "Gaffer/StringPlug.h" @@ -55,7 +57,7 @@ GAFFER_NODE_DEFINE_TYPE( LevelSetOffset ); size_t LevelSetOffset::g_firstPlugIndex = 0; LevelSetOffset::LevelSetOffset( const std::string &name ) - : SceneElementProcessor( name, IECore::PathMatcher::NoMatch ) + : Deformer( name ) { storeIndexOfNextChild( g_firstPlugIndex ); @@ -87,33 +89,26 @@ const Gaffer::FloatPlug *LevelSetOffset::offsetPlug() const return getChild( g_firstPlugIndex + 1 ); } -void LevelSetOffset::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const -{ - SceneElementProcessor::affects( input, outputs ); - - if( input == gridPlug() || input == offsetPlug() ) - { - outputs.push_back( outPlug()->objectPlug() ); - outputs.push_back( outPlug()->boundPlug() ); - } -} - -bool LevelSetOffset::processesObject() const +bool LevelSetOffset::affectsProcessedObject( const Gaffer::Plug *input ) const { - return true; + return + Deformer::affectsProcessedObject( input ) || + input == gridPlug() || + input == offsetPlug() + ; } void LevelSetOffset::hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - SceneElementProcessor::hashProcessedObject( path, context, h ); + Deformer::hashProcessedObject( path, context, h ); gridPlug()->hash( h ); offsetPlug()->hash( h ); } -IECore::ConstObjectPtr LevelSetOffset::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::ConstObjectPtr inputObject ) const +IECore::ConstObjectPtr LevelSetOffset::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const { - const VDBObject *vdbObject = runTimeCast(inputObject.get()); + const VDBObject *vdbObject = runTimeCast( inputObject ); if( !vdbObject ) { return inputObject; @@ -129,19 +124,20 @@ IECore::ConstObjectPtr LevelSetOffset::computeProcessedObject( const ScenePath & } openvdb::GridBase::Ptr newGrid; + Interrupter interrupter( context->canceller() ); if ( openvdb::FloatGrid::ConstPtr floatGrid = openvdb::GridBase::constGrid( gridBase ) ) { openvdb::FloatGrid::Ptr newFloatGrid = openvdb::GridBase::grid ( floatGrid->deepCopyGrid() ); newGrid = newFloatGrid; - openvdb::tools::LevelSetFilter filter( *newFloatGrid ); + openvdb::tools::LevelSetFilter filter( *newFloatGrid, &interrupter ); filter.offset( offsetPlug()->getValue() ); } else if ( openvdb::DoubleGrid::ConstPtr doubleGrid = openvdb::GridBase::constGrid( newGrid ) ) { openvdb::DoubleGrid::Ptr newDoubleGrid = openvdb::GridBase::grid( doubleGrid->deepCopyGrid() ); newGrid = newDoubleGrid; - openvdb::tools::LevelSetFilter filter( *newDoubleGrid ); + openvdb::tools::LevelSetFilter filter( *newDoubleGrid, &interrupter ); filter.offset( offsetPlug()->getValue() ); } else @@ -156,23 +152,26 @@ IECore::ConstObjectPtr LevelSetOffset::computeProcessedObject( const ScenePath & return newVDBObject; } - -bool LevelSetOffset::processesBound() const +bool LevelSetOffset::affectsProcessedObjectBound( const Gaffer::Plug *input ) const { - return true; + return + input == inPlug()->boundPlug() || + input == offsetPlug() + ; } -void LevelSetOffset::hashProcessedBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const +void LevelSetOffset::hashProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - SceneElementProcessor::hashProcessedBound( path, context, h ); - - gridPlug()->hash( h ); + inPlug()->boundPlug()->hash( h ); offsetPlug()->hash( h ); } -Imath::Box3f LevelSetOffset::computeProcessedBound( const ScenePath &path, const Gaffer::Context *context, const Imath::Box3f &inputBound ) const +Imath::Box3f LevelSetOffset::computeProcessedObjectBound( const ScenePath &path, const Gaffer::Context *context ) const { - Imath::Box3f newBound = inputBound; + /// \todo `in.bound` includes the child bounds. Ideally we + /// would have a separate `in.objectBound` with just the bounds + /// of the input object. + Imath::Box3f newBound = inPlug()->boundPlug()->getValue(); float offset = -offsetPlug()->getValue(); newBound.min -= Imath::V3f(offset, offset, offset); diff --git a/src/GafferVDB/LevelSetToMesh.cpp b/src/GafferVDB/LevelSetToMesh.cpp index 12397f60f97..5c802d52b88 100644 --- a/src/GafferVDB/LevelSetToMesh.cpp +++ b/src/GafferVDB/LevelSetToMesh.cpp @@ -36,18 +36,15 @@ #include "GafferVDB/LevelSetToMesh.h" -#include "IECoreVDB/VDBObject.h" - #include "Gaffer/StringPlug.h" +#include "IECoreVDB/VDBObject.h" + #include "IECoreScene/MeshPrimitive.h" #include "openvdb/openvdb.h" #include "openvdb/tools/VolumeToMesh.h" -#include "boost/mpl/for_each.hpp" -#include "boost/mpl/list.hpp" - using namespace std; using namespace Imath; using namespace IECore; @@ -175,7 +172,7 @@ GAFFER_NODE_DEFINE_TYPE( LevelSetToMesh ); size_t LevelSetToMesh::g_firstPlugIndex = 0; LevelSetToMesh::LevelSetToMesh( const std::string &name ) - : SceneElementProcessor( name, IECore::PathMatcher::NoMatch ) + : Deformer( name ) { storeIndexOfNextChild( g_firstPlugIndex ); @@ -183,6 +180,11 @@ LevelSetToMesh::LevelSetToMesh( const std::string &name ) addChild( new FloatPlug( "isoValue", Plug::In, 0.0f ) ); addChild( new FloatPlug( "adaptivity", Plug::In, 0.0f, 0.0f, 1.0f ) ); + // The output mesh will always be bounded by the input level set, and only + // in rare cases will it be shrunk enough to warrant the cost of computing + // exact bounds. So we default `adjustBounds` to `false`. + adjustBoundsPlug()->setValue( false ); + adjustBoundsPlug()->resetDefault(); } LevelSetToMesh::~LevelSetToMesh() @@ -199,7 +201,6 @@ const Gaffer::StringPlug *LevelSetToMesh::gridPlug() const return getChild( g_firstPlugIndex ); } - Gaffer::FloatPlug *LevelSetToMesh::isoValuePlug() { return getChild( g_firstPlugIndex + 1); @@ -220,42 +221,28 @@ const Gaffer::FloatPlug *LevelSetToMesh::adaptivityPlug() const return getChild( g_firstPlugIndex + 2 ); } -void LevelSetToMesh::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const +bool LevelSetToMesh::affectsProcessedObject( const Gaffer::Plug *input ) const { - SceneElementProcessor::affects( input, outputs ); - - if( + return + Deformer::affectsProcessedObject( input ) || input == isoValuePlug() || input == adaptivityPlug() || input == gridPlug() - ) - { - outputs.push_back( outPlug()->objectPlug() ); - } - - if( input == isoValuePlug() ) - { - outputs.push_back( outPlug()->boundPlug() ); - } -} - -bool LevelSetToMesh::processesObject() const -{ - return true; + ; } void LevelSetToMesh::hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - SceneElementProcessor::hashProcessedObject( path, context, h ); + Deformer::hashProcessedObject( path, context, h ); gridPlug()->hash( h ); isoValuePlug()->hash( h ); adaptivityPlug()->hash( h ); } -IECore::ConstObjectPtr LevelSetToMesh::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::ConstObjectPtr inputObject ) const +IECore::ConstObjectPtr LevelSetToMesh::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const { - const VDBObject *vdbObject = runTimeCast( inputObject.get() ); + const VDBObject *vdbObject = runTimeCast( inputObject ); if( !vdbObject ) { return inputObject; @@ -270,20 +257,3 @@ IECore::ConstObjectPtr LevelSetToMesh::computeProcessedObject( const ScenePath & return volumeToMesh( grid, isoValuePlug()->getValue(), adaptivityPlug()->getValue() ); } - -bool LevelSetToMesh::processesBound() const -{ - return true; -} - -void LevelSetToMesh::hashProcessedBound( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const -{ - SceneElementProcessor::hashProcessedBound( path, context, h ); - isoValuePlug()->hash( h ); -} - -Imath::Box3f LevelSetToMesh::computeProcessedBound( const ScenePath &path, const Gaffer::Context *context, const Imath::Box3f &inputBound ) const -{ - const V3f offset( isoValuePlug()->getValue() ); - return Box3f( inputBound.min - offset, inputBound.max + offset ); -} diff --git a/src/GafferVDB/MeshToLevelSet.cpp b/src/GafferVDB/MeshToLevelSet.cpp index e9af276c072..2486cb0876a 100644 --- a/src/GafferVDB/MeshToLevelSet.cpp +++ b/src/GafferVDB/MeshToLevelSet.cpp @@ -36,6 +36,8 @@ #include "GafferVDB/MeshToLevelSet.h" +#include "GafferVDB/Interrupter.h" + #include "IECoreVDB/VDBObject.h" #include "Gaffer/StringPlug.h" @@ -128,7 +130,7 @@ GAFFER_NODE_DEFINE_TYPE( MeshToLevelSet ); size_t MeshToLevelSet::g_firstPlugIndex = 0; MeshToLevelSet::MeshToLevelSet( const std::string &name ) - : SceneElementProcessor( name, IECore::PathMatcher::NoMatch ) + : ObjectProcessor( name ) { storeIndexOfNextChild( g_firstPlugIndex ); @@ -182,29 +184,20 @@ const FloatPlug *MeshToLevelSet::interiorBandwidthPlug() const return getChild( g_firstPlugIndex + 3 ); } -void MeshToLevelSet::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const +bool MeshToLevelSet::affectsProcessedObject( const Gaffer::Plug *input ) const { - SceneElementProcessor::affects( input, outputs ); - - if( + return + ObjectProcessor::affectsProcessedObject( input ) || input == gridPlug() || input == voxelSizePlug() || input == exteriorBandwidthPlug() || input == interiorBandwidthPlug() - ) - { - outputs.push_back( outPlug()->objectPlug() ); - } -} - -bool MeshToLevelSet::processesObject() const -{ - return true; + ; } void MeshToLevelSet::hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - SceneElementProcessor::hashProcessedObject( path, context, h ); + ObjectProcessor::hashProcessedObject( path, context, h ); gridPlug()->hash( h ); voxelSizePlug()->hash( h ); @@ -212,9 +205,9 @@ void MeshToLevelSet::hashProcessedObject( const ScenePath &path, const Gaffer::C interiorBandwidthPlug()->hash ( h ); } -IECore::ConstObjectPtr MeshToLevelSet::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::ConstObjectPtr inputObject ) const +IECore::ConstObjectPtr MeshToLevelSet::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const { - const MeshPrimitive *mesh = runTimeCast( inputObject.get() ); + const MeshPrimitive *mesh = runTimeCast( inputObject ); if( !mesh ) { return inputObject; @@ -225,14 +218,15 @@ IECore::ConstObjectPtr MeshToLevelSet::computeProcessedObject( const ScenePath & const float interiorBandwidth = interiorBandwidthPlug()->getValue(); openvdb::math::Transform::Ptr transform = openvdb::math::Transform::createLinearTransform( voxelSize ); + Interrupter interrupter( context->canceller() ); openvdb::FloatGrid::Ptr grid = openvdb::tools::meshToVolume( + interrupter, CortexMeshAdapter( mesh, transform.get() ), *transform, exteriorBandwidth, //in voxel units interiorBandwidth, //in voxel units - 0 //conversionFlags, - //primitiveIndexGrid.get() + 0 //conversionFlags ); grid->setName( gridPlug()->getValue() ); diff --git a/src/GafferVDB/PointsGridToPoints.cpp b/src/GafferVDB/PointsGridToPoints.cpp index bdccc1fc6d0..0cfc6ecdedd 100644 --- a/src/GafferVDB/PointsGridToPoints.cpp +++ b/src/GafferVDB/PointsGridToPoints.cpp @@ -451,7 +451,8 @@ GAFFER_NODE_DEFINE_TYPE( PointsGridToPoints ); size_t PointsGridToPoints::g_firstPlugIndex = 0; -PointsGridToPoints::PointsGridToPoints( const std::string &name ) : SceneElementProcessor( name ) +PointsGridToPoints::PointsGridToPoints( const std::string &name ) + : ObjectProcessor( name ) { storeIndexOfNextChild( g_firstPlugIndex ); @@ -496,33 +497,26 @@ const Gaffer::BoolPlug *PointsGridToPoints::invertNamesPlug() const return getChild( g_firstPlugIndex + 2 ); } -void PointsGridToPoints::affects( const Gaffer::Plug *input, AffectedPlugsContainer &outputs ) const +bool PointsGridToPoints::affectsProcessedObject( const Gaffer::Plug *input ) const { - SceneElementProcessor::affects( input, outputs ); - - if( input == gridPlug() || input == namesPlug() || input == invertNamesPlug() ) - { - outputs.push_back( outPlug()->objectPlug() ); - } -} - -bool PointsGridToPoints::processesObject() const -{ - return true; + return + ObjectProcessor::affectsProcessedObject( input ) || + input == gridPlug() || input == namesPlug() || input == invertNamesPlug() + ; } void PointsGridToPoints::hashProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::MurmurHash &h ) const { - SceneElementProcessor::hashProcessedObject( path, context, h ); + ObjectProcessor::hashProcessedObject( path, context, h ); gridPlug()->hash( h ); namesPlug()->hash( h ); invertNamesPlug()->hash ( h ); } -IECore::ConstObjectPtr PointsGridToPoints::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, IECore::ConstObjectPtr inputObject ) const +IECore::ConstObjectPtr PointsGridToPoints::computeProcessedObject( const ScenePath &path, const Gaffer::Context *context, const IECore::Object *inputObject ) const { - const VDBObject *vdbObject = runTimeCast( inputObject.get() ); + const VDBObject *vdbObject = runTimeCast( inputObject ); if( !vdbObject ) { return inputObject; @@ -555,6 +549,3 @@ IECore::ConstObjectPtr PointsGridToPoints::computeProcessedObject( const ScenePa return points; } - - - diff --git a/src/GafferVDB/SphereLevelSet.cpp b/src/GafferVDB/SphereLevelSet.cpp index ddb99bb1b89..1e14a1d6923 100644 --- a/src/GafferVDB/SphereLevelSet.cpp +++ b/src/GafferVDB/SphereLevelSet.cpp @@ -124,12 +124,13 @@ void SphereLevelSet::affects( const Plug *input, AffectedPlugsContainer &outputs { ObjectSource::affects( input, outputs ); - if( input == gridPlug() || + if( + input == gridPlug() || input == radiusPlug() || input->parent() == centerPlug() || input == voxelSizePlug() || input == halfWidthPlug() - ) + ) { outputs.push_back( sourcePlug() ); } @@ -169,4 +170,3 @@ IECore::ConstObjectPtr SphereLevelSet::computeSource( const Context *context ) c return newVDBObject; } - diff --git a/startup/GafferScene/branchCreatorCompatibility.py b/startup/GafferScene/branchCreatorCompatibility.py new file mode 100644 index 00000000000..743a256ddb2 --- /dev/null +++ b/startup/GafferScene/branchCreatorCompatibility.py @@ -0,0 +1,61 @@ +########################################################################## +# +# Copyright (c) 2021, 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 IECore + +import Gaffer +import GafferScene + +_filterResults = GafferScene.FilterResults() +_filteredPaths = Gaffer.PathMatcherDataPlug( "__filteredPaths", defaultValue = IECore.PathMatcherData() ) + +def __branchCreatorGetItem( originalGetItem ) : + + def getItem( self, key ) : + + # In Gaffer 0.54, a connection between these internal implementation details was accidentally + # serialised into scripts. The implementation has since been replaced, but we need to provide + # proxies to keep the bogus serialisations loading quietly. + if key == "__filteredPaths" : + return _filteredPaths + elif key == "__filterResults" : + return _filterResults + + return originalGetItem( self, key ) + + return getItem + +GafferScene.BranchCreator.__getitem__ = __branchCreatorGetItem( GafferScene.BranchCreator.__getitem__ ) diff --git a/startup/GafferScene/rendererAlgoCompatibility.py b/startup/GafferScene/rendererAlgoCompatibility.py index 01b720e4fd4..c937d8d34ab 100644 --- a/startup/GafferScene/rendererAlgoCompatibility.py +++ b/startup/GafferScene/rendererAlgoCompatibility.py @@ -35,7 +35,20 @@ ########################################################################## import GafferScene +import types -# Backwards compatibility for old bindings which were not namespaced correctly. -for n in ( "registerAdaptor", "deregisterAdaptor", "createAdaptors" ) : - setattr( GafferScene, n, getattr( GafferScene.RendererAlgo, n ) ) +# RendererAlgo is now private, and does not directly expose anything to Python. +# Create a dummy module to contain our compability shims +GafferScene.RendererAlgo = types.ModuleType( "RendererAlgo" ) + +# Backwards compatibility for old bindings which were not namespaced correctly, +# or in RendererAlgo instead of SceneAlgo +for newName, oldName in ( + ( "registerRenderAdaptor", "registerAdaptor" ), + ( "deregisterRenderAdaptor", "deregisterAdaptor" ), + ( "createRenderAdaptors", "createAdaptors" ) + ) : + setattr( GafferScene.RendererAlgo, oldName, getattr( GafferScene.SceneAlgo, newName ) ) + setattr( GafferScene, oldName, getattr( GafferScene.SceneAlgo, newName ) ) + +setattr( GafferScene.RendererAlgo, "applyCameraGlobals", GafferScene.SceneAlgo.applyCameraGlobals ) diff --git a/startup/GafferUI/sliderCompatibility.py b/startup/GafferUI/sliderCompatibility.py new file mode 100644 index 00000000000..2f9d98f1576 --- /dev/null +++ b/startup/GafferUI/sliderCompatibility.py @@ -0,0 +1,41 @@ +########################################################################## +# +# Copyright (c) 2021, 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 GafferUI + +# Provides minimal backwards compatibility for old NumericSlider class, +# which has been absorbed into the Slider class. +GafferUI.NumericSlider = GafferUI.Slider \ No newline at end of file diff --git a/startup/gui/editor.py b/startup/gui/editor.py index 49c785ab17f..9f32c817e4b 100644 --- a/startup/gui/editor.py +++ b/startup/gui/editor.py @@ -45,28 +45,6 @@ def __editorCreated( editor ) : GafferUI.Editor.instanceCreatedSignal().connect( __editorCreated, scoped = False ) - -#### Editor follows Scene Selection - -# This makes the supplied editor follow the source node of the scene selection of another editor. -# When followSelectionSource is True, the editor will follow the source node for the scene -# selection (as observed by the targetEditor's nodeSet), instead of the targets nodeSet itself. -# For example, a NodeEditor linked to a Viewer with followSelectionSource would show the source -# node for any selected lights/objects. - -def __driveFromSceneSelectionSourceChangeCallback( editor, targetEditor ) : - - sourceSet = targetEditor.getNodeSet() - context = targetEditor.getContext() - return GafferSceneUI.SourceSet( context, sourceSet ) - -DriverModeSceneSelectionSource = "SceneSelectionSource" -GafferUI.NodeSetEditor.registerNodeSetDriverMode( - DriverModeSceneSelectionSource, __driveFromSceneSelectionSourceChangeCallback, - "Following the scene selection's source node from {editor}." -) - - ### Pinning Menu Items # Helper for registering unique menu items considering current state @@ -86,13 +64,13 @@ def __addFollowMenuItem( menuDefinition, editor, targetEditor, subMenuTitle, mod highlightTarget = weakref.ref( targetEditor.parent().parent() if targetEditor._qtWidget().isHidden() else targetEditor.parent() ) isCurrent = existingMode == mode if existingDriver is targetEditor else False - menuDefinition.insertBefore( "/%s/%s" % ( subMenuTitle, title ), { + menuDefinition.append( "/%s/%s" % ( subMenuTitle, title ), { "command" : lambda _ : weakEditor().setNodeSetDriver( weakTarget(), mode ), "active" : not editor.drivesNodeSet( targetEditor ), "checkBox" : isCurrent, "enter" : lambda : highlightTarget().setHighlighted( True ), "leave" : lambda : highlightTarget().setHighlighted( False ) - }, "/Pin Divider" ) + } ) # Simple follows, eg: Hierarchy -> Viewer def __registerEditorNodeSetDriverItems( editor, menuDefinition ) : @@ -124,33 +102,10 @@ def __registerEditorNodeSetDriverItems( editor, menuDefinition ) : itemNameCounts ) -# Selection follows, ie: NodeEditor -> Viewer -def __registerNodeSetFollowsSceneSelectionItems( editor, menuDefinition ) : - - if not isinstance( editor, GafferUI.NodeEditor ) : - return - - # We support Viewers and Hierarchy Views as they are the only - # standard editors that manipulate the selection in the context. - parentCompoundEditor = editor.ancestor( GafferUI.CompoundEditor ) - targets = parentCompoundEditor.editors( GafferUI.Viewer ) - targets.extend( parentCompoundEditor.editors( GafferSceneUI.HierarchyView ) ) - - itemNameCounts = {} - for target in targets : - if target is not editor : - __addFollowMenuItem( menuDefinition, - editor, target, - "Scene Selection", - DriverModeSceneSelectionSource, - itemNameCounts - ) - ## Registration def __registerNodeSetMenuItems( editor, menuDefinition ) : - __registerNodeSetFollowsSceneSelectionItems( editor, menuDefinition ) __registerEditorNodeSetDriverItems( editor, menuDefinition ) GafferUI.GraphBookmarksUI.appendNodeSetMenuDefinitions( editor, menuDefinition ) diff --git a/startup/gui/graphEditor.py b/startup/gui/graphEditor.py index 563f25c68a5..eb3685d64ba 100644 --- a/startup/gui/graphEditor.py +++ b/startup/gui/graphEditor.py @@ -100,6 +100,7 @@ def __nodeContextMenu( graphEditor, node, menuDefinition ) : GafferDispatchUI.DispatcherUI.appendNodeContextMenuDefinitions( graphEditor, node, menuDefinition ) GafferUI.GraphEditor.appendContentsMenuDefinitions( graphEditor, node, menuDefinition ) GafferUI.UIEditor.appendNodeContextMenuDefinitions( graphEditor, node, menuDefinition ) + GafferUI.AnnotationsUI.appendNodeContextMenuDefinitions( graphEditor, node, menuDefinition ) GafferSceneUI.FilteredSceneProcessorUI.appendNodeContextMenuDefinitions( graphEditor, node, menuDefinition ) GafferUI.GraphBookmarksUI.appendNodeContextMenuDefinitions( graphEditor, node, menuDefinition ) diff --git a/startup/gui/menus.py b/startup/gui/menus.py index 0bf6b0a4849..e992ea8a5c9 100644 --- a/startup/gui/menus.py +++ b/startup/gui/menus.py @@ -343,7 +343,10 @@ def __lightCreator( nodeName, shaderName, shape ) : nodeMenu.append( "/Scene/OpenGL/Attributes", GafferScene.OpenGLAttributes, searchText = "OpenGLAttributes" ) nodeMenu.definition().append( "/Scene/OpenGL/Shader", { "subMenu" : GafferSceneUI.OpenGLShaderUI.shaderSubMenu } ) nodeMenu.append( "/Scene/OpenGL/Render", GafferScene.OpenGLRender, searchText = "OpenGLRender" ) - +nodeMenu.append( "/Scene/Utility/Filter Query", GafferScene.FilterQuery, searchText = "FilterQuery" ) +nodeMenu.append( "/Scene/Utility/Transform Query", GafferScene.TransformQuery, searchText = "TransformQuery" ) +nodeMenu.append( "/Scene/Utility/Bound Query", GafferScene.BoundQuery, searchText = "BoundQuery" ) +nodeMenu.append( "/Scene/Utility/Existence Query", GafferScene.ExistenceQuery, searchText = "ExistenceQuery" ) # Image nodes diff --git a/startup/gui/performanceMonitor.py b/startup/gui/performanceMonitor.py new file mode 100644 index 00000000000..758089312bb --- /dev/null +++ b/startup/gui/performanceMonitor.py @@ -0,0 +1,227 @@ +########################################################################## +# +# Copyright (c) 2021, 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 Cinesite VFX Ltd. 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 IECore + +import Gaffer +import GafferUI + +menu = GafferUI.ScriptWindow.menuDefinition( application ) + +def __performanceMonitor( menu, createIfMissing = False ) : + + # We store a monitor per script, so that we don't pollute + # other scripts with metrics collected as a side effect + # of monitoring this one. + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + monitor = getattr( script, "__performanceMonitor", None ) + if monitor is not None : + return monitor + + if createIfMissing : + monitor = Gaffer.PerformanceMonitor() + monitor.__running = False + script.__performanceMonitor = monitor + return monitor + else : + return None + +def __startPerformanceMonitor( menu ) : + + monitor = __performanceMonitor( menu, createIfMissing = True ) + assert( not monitor.__running ) + + monitor.__enter__() + monitor.__running = True + +def __stopPerformanceMonitor( menu ) : + + monitor = __performanceMonitor( menu ) + assert( monitor is not None and monitor.__running ) + monitor.__exit__( None, None, None ) + monitor.__running = False + + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + Gaffer.MonitorAlgo.annotate( script, monitor, persistent = False ) + +def __clearPerformanceMonitor( menu ) : + + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + del script.__performanceMonitor + Gaffer.MonitorAlgo.removePerformanceAnnotations( script ) + +def __contextMonitor( menu, createIfMissing = False ) : + + # We store a monitor per script, so that we don't pollute + # other scripts with metrics collected as a side effect + # of monitoring this one. + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + monitor = getattr( script, "__contextMonitor", None ) + if monitor is not None : + return monitor + + if createIfMissing : + monitor = Gaffer.ContextMonitor( script.selection()[-1] ) + monitor.__running = False + script.__contextMonitor = monitor + return monitor + else : + return None + +def __startContextMonitor( menu ) : + + monitor = __contextMonitor( menu, createIfMissing = True ) + assert( not monitor.__running ) + + monitor.__enter__() + monitor.__running = True + +def __stopContextMonitor( menu ) : + + monitor = __contextMonitor( menu ) + assert( monitor is not None and monitor.__running ) + monitor.__exit__( None, None, None ) + monitor.__running = False + + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + Gaffer.MonitorAlgo.annotate( script, monitor, persistent = False ) + +def __clearContextMonitor( menu ) : + + script = menu.ancestor( GafferUI.ScriptWindow ).scriptNode() + del script.__contextMonitor + Gaffer.MonitorAlgo.removeContextAnnotations( script ) + +def __profilingSubMenu( menu ) : + + result = IECore.MenuDefinition() + + # PerformanceMonitor + + performanceMonitor = __performanceMonitor( menu ) + + result.append( + "/Performance Monitor/" + ( "Start" if performanceMonitor is None else "Resume" ), + { + "command" : __startPerformanceMonitor, + "active" : performanceMonitor is None or not performanceMonitor.__running + } + ) + + result.append( + "/Performance Monitor/Stop and Annotate", + { + "command" : __stopPerformanceMonitor, + "active" : performanceMonitor is not None and performanceMonitor.__running + } + ) + result.append( + "/Performance Monitor/Divider", + { + "divider" : True, + } + ) + result.append( + "/Performance Monitor/Clear", + { + "command" : __clearPerformanceMonitor, + "active" : performanceMonitor is not None and not performanceMonitor.__running + } + ) + + # ContextMonitor + + contextMonitor = __contextMonitor( menu ) + selection = menu.ancestor( GafferUI.ScriptWindow ).scriptNode().selection() + + result.append( + "/Context Monitor/" + ( "Start for Selected Node" if contextMonitor is None else "Resume" ), + { + "command" : __startContextMonitor, + "active" : ( + ( contextMonitor is None and selection.size() == 1 ) or + ( contextMonitor is not None and not contextMonitor.__running ) + ) + } + ) + + result.append( + "/Context Monitor/Stop and Annotate", + { + "command" : __stopContextMonitor, + "active" : contextMonitor is not None and contextMonitor.__running + } + ) + result.append( + "/Context Monitor/Divider", + { + "divider" : True, + } + ) + result.append( + "/Context Monitor/Clear", + { + "command" : __clearContextMonitor, + "active" : contextMonitor is not None and not contextMonitor.__running + } + ) + + return result + +menu.append( "/Tools/Profiling", { "subMenu" : __profilingSubMenu } ) + +def __graphEditorCreated( graphEditor ) : + + ## \todo It's a bit naughty accessing an internal gadget like this. + # What we really want is for Editors to have plugs (like Views do), and for + # the visible annotations to be specified on a promoted plug. Then + # we could just set a `userDefault` for that plug. + annotationsGadget = graphEditor.graphGadget()["__annotations"] + + annotations = Gaffer.MetadataAlgo.annotationTemplates() + [ "user", annotationsGadget.untemplatedAnnotations ] + visiblePattern = annotationsGadget.getVisibleAnnotations() + visibleAnnotations = { a for a in annotations if IECore.StringAlgo.matchMultiple( a, visiblePattern ) } + visibleAnnotations -= { + "performanceMonitor:hashDuration", + "performanceMonitor:computeDuration", + "performanceMonitor:perHashDuration", + "performanceMonitor:perComputeDuration", + "performanceMonitor:hashesPerCompute", + } + + annotationsGadget.setVisibleAnnotations( " ".join( visibleAnnotations ) ) + +GafferUI.GraphEditor.instanceCreatedSignal().connect( __graphEditorCreated, scoped = False )