def testCanDeleteFaces(self): rectangleScene = self.makeRectangleFromTwoSquaresScene() deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput(rectangleScene["out"]) pathFilter = GafferScene.PathFilter("PathFilter") pathFilter["paths"].setValue(IECore.StringVectorData(['/object'])) deleteFaces["filter"].setInput(pathFilter["out"]) faceDeletedObject = deleteFaces["out"].object("/object") self.assertEqual(faceDeletedObject.verticesPerFace, IECore.IntVectorData([4])) self.assertEqual(faceDeletedObject.vertexIds, IECore.IntVectorData([0, 1, 3, 2])) self.assertEqual(faceDeletedObject.numFaces(), 1) self.assertEqual( faceDeletedObject["P"].data, IECore.V3fVectorData([ IECore.V3f(0, 0, 0), IECore.V3f(1, 0, 0), IECore.V3f(0, 1, 0), IECore.V3f(1, 1, 0) ])) # verify the primvars are correct self.assertEqual(faceDeletedObject["uniform"].data, IECore.IntVectorData([10])) self.assertEqual(faceDeletedObject["vertex"].data, IECore.IntVectorData([100, 101, 103, 104])) self.assertEqual(faceDeletedObject["faceVarying"].data, IECore.IntVectorData([20, 21, 22, 23]))
def testBoundsOfChildObjects(self): rectangle = self.makeRectangleFromTwoSquaresScene() sphere = GafferScene.Sphere() sphere["radius"].setValue(10) # Totally encloses the rectangle parent = GafferScene.Parent() parent["in"].setInput(rectangle["out"]) parent["parent"].setValue("/object") parent["children"][0].setInput(sphere["out"]) self.assertSceneValid(parent["out"]) pathFilter = GafferScene.PathFilter("PathFilter") pathFilter["paths"].setValue(IECore.StringVectorData(["/object"])) deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput(parent["out"]) deleteFaces["filter"].setInput(pathFilter["out"]) # The sphere should not have been modified self.assertEqual(deleteFaces["out"].object("/object/sphere"), parent["out"].object("/object/sphere")) # And the bounding boxes should still enclose all the objects, # including the sphere. self.assertSceneValid(deleteFaces["out"])
def testNoUnnecessaryDirtyPropagationCrossTalk(self): # plane # | # primitiveVariables # / \ # | deleteFaces # | | # | | # switch plane = GafferScene.Plane() primitiveVariables = GafferScene.PrimitiveVariables() primitiveVariables["in"].setInput(plane["out"]) pv = Gaffer.NameValuePlug("test", IECore.IntData(0)) primitiveVariables["primitiveVariables"].addChild(pv) # DeleteFaces has a dependency between the object and the # bound, so dirtying the input object also dirties the # output bound. There is cross-talk between the plugs. deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput(primitiveVariables["out"]) switch = Gaffer.Switch() switch.setup(primitiveVariables["out"]) switch["in"][0].setInput(primitiveVariables["out"]) switch["in"][1].setInput(deleteFaces["out"]) # When the Switch index is constant 0, we know that DeleteFaces # is not the active branch. So we don't expect dirtying the input # object to dirty the output bound. cs = GafferTest.CapturingSlot(switch.plugDirtiedSignal()) pv["value"].setValue(1) self.assertIn(switch["out"]["object"], {x[0] for x in cs}) self.assertNotIn(switch["out"]["bound"], {x[0] for x in cs}) # When the Switch index is constant 1, we know that DeleteFaces # is the active branch, so we do expect crosstalk. switch["index"].setValue(1) del cs[:] pv["value"].setValue(2) self.assertIn(switch["out"]["object"], {x[0] for x in cs}) self.assertIn(switch["out"]["bound"], {x[0] for x in cs}) # And when the Switch index is computed (indeterminate during # dirty propagation) we also expect crosstalk. add = GafferTest.AddNode() switch["index"].setInput(add["sum"]) del cs[:] pv["value"].setValue(3) self.assertIn(switch["out"]["object"], {x[0] for x in cs}) self.assertIn(switch["out"]["bound"], {x[0] for x in cs})
def testDeletingFacesUpdatesBounds( self ) : rectangleScene = self.makeRectangleFromTwoSquaresScene() expectedOriginalBound = rectangleScene["out"].bound( "/object" ) self.assertEqual(expectedOriginalBound, imath.Box3f( imath.V3f( 0, 0, 0 ), imath.V3f( 2, 1, 0 ) ) ) deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput( rectangleScene["out"] ) pathFilter = GafferScene.PathFilter( "PathFilter" ) pathFilter["paths"].setValue( IECore.StringVectorData( [ '/object' ] ) ) deleteFaces["filter"].setInput( pathFilter["out"] ) actualFaceDeletedBounds = deleteFaces["out"].bound( "/object" ) expectedBoundingBox = imath.Box3f( imath.V3f( 0, 0, 0 ), imath.V3f( 1, 1, 0 ) ) self.assertEqual( actualFaceDeletedBounds, expectedBoundingBox )
def testIgnoreMissing(self): rectangle = self.makeRectangleFromTwoSquaresScene() deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput(rectangle["out"]) pathFilter = GafferScene.PathFilter("PathFilter") pathFilter["paths"].setValue(IECore.StringVectorData(['/object'])) deleteFaces["filter"].setInput(pathFilter["out"]) self.assertNotEqual(deleteFaces["in"].object("/object"), deleteFaces["out"].object("/object")) deleteFaces["faces"].setValue("doesNotExist") self.assertRaises(RuntimeError, deleteFaces["out"].object, "/object") deleteFaces["ignoreMissingVariable"].setValue(True) self.assertEqual(deleteFaces["in"].object("/object"), deleteFaces["out"].object("/object"))
def testMerging(self): allFilter = GafferScene.PathFilter() allFilter["paths"].setValue(IECore.StringVectorData(['/...'])) plane = GafferScene.Plane() plane["divisions"].setValue(imath.V2i(20, 20)) # Assign a basic gradient shader uvGradientCode = GafferOSL.OSLCode() uvGradientCode["out"].addChild( Gaffer.Color3fPlug("out", direction=Gaffer.Plug.Direction.Out)) uvGradientCode["code"].setValue('out = color( u, v, 0.5 );') shaderAssignment = GafferScene.ShaderAssignment() shaderAssignment["in"].setInput(plane["out"]) shaderAssignment["filter"].setInput(allFilter["out"]) shaderAssignment["shader"].setInput(uvGradientCode["out"]["out"]) # Set up a random id from 0 - 3 on each face randomCode = GafferOSL.OSLCode() randomCode["out"].addChild( Gaffer.IntPlug("randomId", direction=Gaffer.Plug.Direction.Out)) randomCode["code"].setValue( 'randomId = int(cellnoise( P * 100 ) * 4);') outInt = GafferOSL.OSLShader() outInt.loadShader("ObjectProcessing/OutInt") outInt["parameters"]["name"].setValue('randomId') outInt["parameters"]["value"].setInput(randomCode["out"]["randomId"]) outObject = GafferOSL.OSLShader() outObject.loadShader("ObjectProcessing/OutObject") outObject["parameters"]["in0"].setInput( outInt["out"]["primitiveVariable"]) oSLObject = GafferOSL.OSLObject() oSLObject["in"].setInput(shaderAssignment["out"]) oSLObject["filter"].setInput(allFilter["out"]) oSLObject["shader"].setInput(outObject["out"]) oSLObject["interpolation"].setValue(2) # Create 4 meshes by picking each of the 4 ids deleteContextVariables = Gaffer.DeleteContextVariables() deleteContextVariables.setup(GafferScene.ScenePlug()) deleteContextVariables["variables"].setValue('collect:rootName') deleteContextVariables["in"].setInput(oSLObject["out"]) pickCode = GafferOSL.OSLCode() pickCode["parameters"].addChild(Gaffer.IntPlug("targetId")) pickCode["out"].addChild( Gaffer.IntPlug("cull", direction=Gaffer.Plug.Direction.Out)) pickCode["code"].setValue( 'int randomId; getattribute( "randomId", randomId ); cull = randomId != targetId;' ) expression = Gaffer.Expression() pickCode.addChild(expression) expression.setExpression( 'parent.parameters.targetId = stoi( context( "collect:rootName", "0" ) );', "OSL") outInt1 = GafferOSL.OSLShader() outInt1.loadShader("ObjectProcessing/OutInt") outInt1["parameters"]["name"].setValue('deleteFaces') outInt1["parameters"]["value"].setInput(pickCode["out"]["cull"]) outObject1 = GafferOSL.OSLShader() outObject1.loadShader("ObjectProcessing/OutObject") outObject1["parameters"]["in0"].setInput( outInt1["out"]["primitiveVariable"]) oSLObject1 = GafferOSL.OSLObject() oSLObject1["in"].setInput(deleteContextVariables["out"]) oSLObject1["filter"].setInput(allFilter["out"]) oSLObject1["shader"].setInput(outObject1["out"]) oSLObject1["interpolation"].setValue(2) deleteFaces = GafferScene.DeleteFaces() deleteFaces["in"].setInput(oSLObject1["out"]) deleteFaces["filter"].setInput(allFilter["out"]) collectScenes = GafferScene.CollectScenes() collectScenes["in"].setInput(deleteFaces["out"]) collectScenes["rootNames"].setValue( IECore.StringVectorData(['0', '1', '2', '3'])) collectScenes["sourceRoot"].setValue('/plane') # First variant: bake everything, covering the whole 1001 UDIM customAttributes1 = GafferScene.CustomAttributes() customAttributes1["attributes"].addMember( 'bake:fileName', IECore.StringData( '${bakeDirectory}/complete/<AOV>/<AOV>.<UDIM>.exr')) customAttributes1["in"].setInput(collectScenes["out"]) # Second vaiant: bake just 2 of the 4 meshes, leaving lots of holes that will need filling pruneFilter = GafferScene.PathFilter() pruneFilter["paths"].setValue(IECore.StringVectorData(['/2', '/3'])) prune = GafferScene.Prune() prune["in"].setInput(collectScenes["out"]) prune["filter"].setInput(pruneFilter["out"]) customAttributes2 = GafferScene.CustomAttributes() customAttributes2["attributes"].addMember( 'bake:fileName', IECore.StringData( '${bakeDirectory}/incomplete/<AOV>/<AOV>.<UDIM>.exr')) customAttributes2["in"].setInput(prune["out"]) # Third variant: bake everything, but with one mesh at a higher resolution customAttributes3 = GafferScene.CustomAttributes() customAttributes3["attributes"].addMember( 'bake:fileName', IECore.StringData( '${bakeDirectory}/mismatch/<AOV>/<AOV>.<UDIM>.exr')) customAttributes3["in"].setInput(collectScenes["out"]) pathFilter2 = GafferScene.PathFilter() pathFilter2["paths"].setValue(IECore.StringVectorData(['/2'])) customAttributes = GafferScene.CustomAttributes() customAttributes["attributes"].addMember('bake:resolution', IECore.IntData(200)) customAttributes["filter"].setInput(pathFilter2["out"]) customAttributes["in"].setInput(customAttributes3["out"]) # Merge the 3 variants mergeGroup = GafferScene.Group() mergeGroup["in"][-1].setInput(customAttributes["out"]) mergeGroup["in"][-1].setInput(customAttributes1["out"]) mergeGroup["in"][-1].setInput(customAttributes2["out"]) arnoldTextureBake = GafferArnold.ArnoldTextureBake() arnoldTextureBake["in"].setInput(mergeGroup["out"]) arnoldTextureBake["filter"].setInput(allFilter["out"]) arnoldTextureBake["bakeDirectory"].setValue(self.temporaryDirectory() + '/bakeMerge/') arnoldTextureBake["defaultResolution"].setValue(128) # We want to check the intermediate results arnoldTextureBake["cleanupIntermediateFiles"].setValue(False) # Dispatch the bake script = Gaffer.ScriptNode() script.addChild(arnoldTextureBake) dispatcher = GafferDispatch.LocalDispatcher() dispatcher["jobsDirectory"].setValue(self.temporaryDirectory()) dispatcher.dispatch([arnoldTextureBake]) # Check results imageReader = GafferImage.ImageReader() outLayer = GafferOSL.OSLShader() outLayer.loadShader("ImageProcessing/OutLayer") outLayer["parameters"]["layerColor"].setInput( uvGradientCode["out"]["out"]) outImage = GafferOSL.OSLShader() outImage.loadShader("ImageProcessing/OutImage") outImage["parameters"]["in0"].setInput(outLayer["out"]["layer"]) oSLImage = GafferOSL.OSLImage() oSLImage["in"].setInput(imageReader["out"]) oSLImage["shader"].setInput(outImage["out"]) merge3 = GafferImage.Merge() merge3["in"]["in0"].setInput(oSLImage["out"]) merge3["in"]["in1"].setInput(imageReader["out"]) merge3["operation"].setValue(10) edgeDetect = self.SimpleEdgeDetect() edgeDetect["in"].setInput(imageReader["out"]) edgeStats = GafferImage.ImageStats() edgeStats["in"].setInput(edgeDetect["out"]) refDiffStats = GafferImage.ImageStats() refDiffStats["in"].setInput(merge3["out"]) oneLayerReader = GafferImage.ImageReader() grade = GafferImage.Grade() grade["in"].setInput(oneLayerReader["out"]) grade["channels"].setValue('[A]') grade["blackPoint"].setValue(imath.Color4f(0, 0, 0, 0.999899983)) copyChannels = GafferImage.CopyChannels() copyChannels["in"]["in0"].setInput(merge3["out"]) copyChannels["in"]["in1"].setInput(grade["out"]) copyChannels["channels"].setValue('[A]') premultiply = GafferImage.Premultiply() premultiply["in"].setInput(copyChannels["out"]) refDiffCoveredStats = GafferImage.ImageStats() refDiffCoveredStats["in"].setInput(premultiply["out"]) # We are testing 3 different cases: # complete : Should be an exact match. # incomplete : Expect some mild variance of slopes and some error, because we have to # reconstruct a lot of missing data. # mismatch : We should get a larger image, sized to the highest override on any mesh. # Match won't be as perfect, because we're combining source images at # different resolutions for name, expectedSize, maxEdge, maxRefDiff, maxMaskedDiff in [ ("complete", 128, 0.01, 0.000001, 0.000001), ("incomplete", 128, 0.05, 0.15, 0.000001), ("mismatch", 200, 0.01, 0.01, 0.01) ]: imageReader["fileName"].setValue(self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.tx") oneLayerReader["fileName"].setValue(self.temporaryDirectory() + "/bakeMerge/" + name + "/beauty/beauty.1001.exr") self.assertEqual(imageReader["out"]["format"].getValue().width(), expectedSize) self.assertEqual(imageReader["out"]["format"].getValue().height(), expectedSize) edgeStats["area"].setValue( imath.Box2i(imath.V2i(1), imath.V2i(expectedSize - 1))) refDiffStats["area"].setValue( imath.Box2i(imath.V2i(1), imath.V2i(expectedSize - 1))) refDiffCoveredStats["area"].setValue( imath.Box2i(imath.V2i(0), imath.V2i(expectedSize))) # Blue channel is constant, so everything should line up perfectly self.assertEqual(0, edgeStats["max"].getValue()[2]) self.assertEqual(0, refDiffStats["max"].getValue()[2]) self.assertEqual(0, refDiffCoveredStats["max"].getValue()[2]) for i in range(2): # Make sure we've got actual data, by checking that we have some error ( we're not expecting # to perfectly reconstruct the gradient when the input is incomplete ) self.assertGreater(edgeStats["max"].getValue()[i], 0.005) if name == "incomplete": self.assertGreater(edgeStats["max"].getValue()[i], 0.03) self.assertGreater(refDiffStats["max"].getValue()[i], 0.06) self.assertLess(edgeStats["max"].getValue()[i], maxEdge) self.assertLess(refDiffStats["max"].getValue()[i], maxRefDiff) self.assertLess(refDiffCoveredStats["max"].getValue()[i], maxMaskedDiff)