sd/CppunitTest_sd_tiledrendering.mk                                            
     |    1 
 sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Group.odp           
     |binary
 sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Groups.odp          
     |binary
 
sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_MultiLevel_Group.odp 
    |binary
 
sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Shape_Inside_A_Group.odp
 |binary
 sd/qa/unit/tiledrendering/tiledrendering.cxx                                   
     |  118 +++++++++-
 sd/source/ui/inc/SlideshowLayerRenderer.hxx                                    
     |   19 +
 sd/source/ui/tools/SlideshowLayerRenderer.cxx                                  
     |   83 +++++--
 sd/source/ui/unoidl/unomodel.cxx                                               
     |  105 ++++++++
 9 files changed, 302 insertions(+), 24 deletions(-)

New commits:
commit 5c96c56704d591ffaf2f8eb36f446c056d152d67
Author:     Marco Cecchetti <marco.cecche...@collabora.com>
AuthorDate: Thu Nov 28 13:23:11 2024 +0100
Commit:     Tomaž Vajngerl <qui...@gmail.com>
CommitDate: Mon Dec 2 07:07:49 2024 +0100

    lok: slideshow: support effects applied to a group of shapes
    
    What has been achieved:
    - when a group is animated a layer with all shapes belonging to the group is
    created and marked as an animated layer
    - any effect applied to a shape belonging to a group (animated or not) is
    discarded
    - any effect based on color animations applied to a group of shapes is 
discarded
    
    For the last 2 points, we mimic the same behavior that occurs on 
LibreOffice.
    
    Unit tests for several scenarios as been provided.
    
    Change-Id: Ie094ac2a6a85f08e0e873062b0a780fe322c83bd
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177479
    Reviewed-by: Tomaž Vajngerl <qui...@gmail.com>
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>

diff --git a/sd/CppunitTest_sd_tiledrendering.mk 
b/sd/CppunitTest_sd_tiledrendering.mk
index 1fcfbf881292..809fc4d020a4 100644
--- a/sd/CppunitTest_sd_tiledrendering.mk
+++ b/sd/CppunitTest_sd_tiledrendering.mk
@@ -47,6 +47,7 @@ $(eval $(call gb_CppunitTest_set_include,sd_tiledrendering,\
     -I$(SRCDIR)/sd/inc \
     -I$(SRCDIR)/sd/source/ui/inc \
     -I$(SRCDIR)/sd/qa/unit \
+    -I$(WORKDIR)/UnpackedTarball/frozen/include \
     $$(INCLUDE) \
 ))
 
diff --git 
a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Group.odp 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Group.odp
new file mode 100644
index 000000000000..8be4426d207b
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Group.odp differ
diff --git 
a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Groups.odp 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Groups.odp
new file mode 100644
index 000000000000..73191f3901c2
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Groups.odp differ
diff --git 
a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_MultiLevel_Group.odp
 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_MultiLevel_Group.odp
new file mode 100644
index 000000000000..7d3555f57988
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_MultiLevel_Group.odp
 differ
diff --git 
a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Shape_Inside_A_Group.odp
 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Shape_Inside_A_Group.odp
new file mode 100644
index 000000000000..3265dd11e013
Binary files /dev/null and 
b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_Animated_Shape_Inside_A_Group.odp
 differ
diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx 
b/sd/qa/unit/tiledrendering/tiledrendering.cxx
index f0c4601137b3..0683209e687f 100644
--- a/sd/qa/unit/tiledrendering/tiledrendering.cxx
+++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx
@@ -3364,7 +3364,7 @@ public:
         }
     }
 
-    void checkPageLayer(int nIndex, const std::string& rGroup)
+    void checkPageLayer(int nIndex, const std::string& rGroup, bool 
bIsAnimated = false)
     {
         const std::string sMsg = rGroup + " Layer Index: " + 
std::to_string(nIndex);
 
@@ -3384,9 +3384,34 @@ public:
         CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, rGroup,
                                      
aTree.get_child("group").get_value<std::string>());
         CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, nIndex, 
aTree.get_child("index").get_value<int>());
-        CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, std::string("bitmap"),
-                                     
aTree.get_child("type").get_value<std::string>());
-        CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aTree, "content"));
+
+        if (!bIsAnimated)
+        {
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, std::string("bitmap"),
+                                         
aTree.get_child("type").get_value<std::string>());
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aTree, 
"content"));
+        }
+        else
+        {
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, std::string("animated"),
+                                         
aTree.get_child("type").get_value<std::string>());
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aTree, 
"content"));
+
+            auto aContentChild = aTree.get_child("content");
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aContentChild, 
"hash"));
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aContentChild, 
"initVisible"));
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, std::string("bitmap"),
+                                         
aContentChild.get_child("type").get_value<std::string>());
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(sMsg, true, has_child(aContentChild, 
"content"));
+
+            auto aContentChildChild = aContentChild.get_child("content");
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(
+                sMsg, std::string("%IMAGETYPE%"),
+                aContentChildChild.get_child("type").get_value<std::string>());
+            CPPUNIT_ASSERT_EQUAL_MESSAGE(
+                sMsg, std::string("%IMAGECHECKSUM%"),
+                
aContentChildChild.get_child("checksum").get_value<std::string>());
+        }
     }
 
     void checkFinalEmptyLayer()
@@ -3804,6 +3829,91 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Skip_Ba
     pXImpressDocument->postSlideshowCleanup();
 }
 
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animated_Shape_Inside_A_Group)
+{
+    // 1 not animated groups made up by 2 shapes
+    // one of the 2 shapes is animated
+
+    SdXImpressDocument* pXImpressDocument
+        = createDoc("SlideRenderingTest_Animated_Shape_Inside_A_Group.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    SlideRendererChecker aSlideRendererChecker(pXImpressDocument, 0, 2000, 
2000, false, false);
+    aSlideRendererChecker.checkSlideSize(2000, 1125);
+
+    // not animated group, no layer should be created for the animated shape
+    aSlideRendererChecker.checkPageLayer(0, "DrawPage", /*bIsAnimated=*/ 
false);
+
+    aSlideRendererChecker.checkFinalEmptyLayer();
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animated_Group)
+{
+    // 1 animated groups made up by 2 not animated shapes
+    // a single not animated shape
+
+    SdXImpressDocument* pXImpressDocument = 
createDoc("SlideRenderingTest_Animated_Group.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    SlideRendererChecker aSlideRendererChecker(pXImpressDocument, 0, 2000, 
2000, false, false);
+    aSlideRendererChecker.checkSlideSize(2000, 1125);
+
+    // animated group
+    aSlideRendererChecker.checkPageLayer(0, "DrawPage", /*bIsAnimated=*/ true);
+
+    // not animated shape
+    aSlideRendererChecker.checkPageLayer(1, "DrawPage", /*bIsAnimated=*/ 
false);
+
+    aSlideRendererChecker.checkFinalEmptyLayer();
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animated_Groups)
+{
+    // 2 animated groups made up by 2 shapes each
+
+    SdXImpressDocument* pXImpressDocument = 
createDoc("SlideRenderingTest_Animated_Groups.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    SlideRendererChecker aSlideRendererChecker(pXImpressDocument, 0, 2000, 
2000, false, false);
+    aSlideRendererChecker.checkSlideSize(2000, 1125);
+
+    // 1st group
+    aSlideRendererChecker.checkPageLayer(0, "DrawPage", /*bIsAnimated=*/ true);
+
+    // 2nd group
+    aSlideRendererChecker.checkPageLayer(1, "DrawPage", /*bIsAnimated=*/ true);
+
+    aSlideRendererChecker.checkFinalEmptyLayer();
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
+CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animated_MultiLevel_Group)
+{
+    // 3 1st level groups made up by 2 shapes each
+    // the 1st group is not animated but one of its shape is
+    // the 2nd group is animated, none of its shapes is
+    // the 3rd group is animated with a color based effect
+    // 1st and 2nd group are grouped together and the 2nd level group is 
animated
+
+    SdXImpressDocument* pXImpressDocument = 
createDoc("SlideRenderingTest_Animated_MultiLevel_Group.odp");
+    
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+    SlideRendererChecker aSlideRendererChecker(pXImpressDocument, 0, 2000, 
2000, false, false);
+    aSlideRendererChecker.checkSlideSize(2000, 1125);
+
+    // a single layer should be created for the highest level group,
+    // embedded animated groups or animated shapes should be ignored
+    aSlideRendererChecker.checkPageLayer(0, "DrawPage", /*bIsAnimated=*/ true);
+
+    // a group with applied an effect based on color animations should not be 
animated
+    aSlideRendererChecker.checkPageLayer(1, "DrawPage", /*bIsAnimated=*/ 
false);
+
+    aSlideRendererChecker.checkFinalEmptyLayer();
+
+    pXImpressDocument->postSlideshowCleanup();
+}
+
 CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, 
testSlideshowLayeredRendering_Animations)
 {
     // Check rendering of animated objects - each in own layer
diff --git a/sd/source/ui/inc/SlideshowLayerRenderer.hxx 
b/sd/source/ui/inc/SlideshowLayerRenderer.hxx
index 93f4f5e36513..f6c3c6534564 100644
--- a/sd/source/ui/inc/SlideshowLayerRenderer.hxx
+++ b/sd/source/ui/inc/SlideshowLayerRenderer.hxx
@@ -24,6 +24,10 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include <frozen/bits/defines.h>
+#include <frozen/bits/elsa_std.h>
+#include <frozen/unordered_set.h>
+
 class SdrPage;
 class SdrModel;
 class SdrObject;
@@ -41,6 +45,21 @@ class ViewObjectContactRedirector;
 
 namespace sd
 {
+constexpr auto constNonValidEffectsForGroupSet = 
frozen::make_unordered_set<std::string_view>({
+    "ooo-emphasis-fill-color",
+    "ooo-emphasis-font-color",
+    "ooo-emphasis-line-color",
+    "ooo-emphasis-color-blend",
+    "ooo-emphasis-complementary-color",
+    "ooo-emphasis-complementary-color-2",
+    "ooo-emphasis-contrasting-color",
+    "ooo-emphasis-darken",
+    "ooo-emphasis-desaturate",
+    "ooo-emphasis-flash-bulb",
+    "ooo-emphasis-lighten",
+    "ooo-emphasis-grow-with-color",
+});
+
 class RenderContext;
 
 enum class RenderStage
diff --git a/sd/source/ui/tools/SlideshowLayerRenderer.cxx 
b/sd/source/ui/tools/SlideshowLayerRenderer.cxx
index 66bd549f02bb..51c1209803eb 100644
--- a/sd/source/ui/tools/SlideshowLayerRenderer.cxx
+++ b/sd/source/ui/tools/SlideshowLayerRenderer.cxx
@@ -151,6 +151,8 @@ OUString getMasterTextFieldType(SdrObject* pObject)
     return aType;
 }
 
+bool isGroup(SdrObject* pObject) { return pObject->getChildrenOfSdrObject() != 
nullptr; }
+
 /// Sets visible for all kinds of polypolys in the container
 void changePolyPolys(drawinglayer::primitive2d::Primitive2DContainer& 
rContainer,
                      bool bRenderObject)
@@ -299,6 +301,18 @@ private:
                || (mrRenderState.mbDateTimeEnabled && svType == u"DateTime");
     }
 
+    SdrObject* getAnimatedAncestor(SdrObject* pObject) const
+    {
+        SdrObject* pAncestor = pObject;
+        while ((pAncestor = pAncestor->getParentSdrObjectFromSdrObject()))
+        {
+            auto aIterator = 
mrRenderState.maAnimationRenderInfoList.find(pAncestor);
+            if (aIterator != mrRenderState.maAnimationRenderInfoList.end())
+                return pAncestor;
+        }
+        return pAncestor;
+    }
+
 public:
     AnalyzeRenderingRedirector(RenderState& rRenderState, bool 
bRenderMasterPage)
         : mrRenderState(rRenderState)
@@ -457,9 +471,26 @@ public:
                 closeRenderPass();
             }
         }
-        // No specal handling is needed, just add the object to the current 
rendering pass
+        // check if object is part of an animated group
+        else if (SdrObject* pAncestor = getAnimatedAncestor(pObject))
+        {
+            // a new animated group is started ?
+            if (mpCurrentRenderPass->mpObject && mpCurrentRenderPass->mpObject 
!= pAncestor)
+                closeRenderPass();
+
+            // Add the animated object
+            mpCurrentRenderPass->maObjectsAndParagraphs.emplace(pObject, 
std::deque<sal_Int32>());
+            mpCurrentRenderPass->meStage = eCurrentStage;
+            mpCurrentRenderPass->mbAnimation = true;
+            mpCurrentRenderPass->mpObject = pAncestor;
+        }
+        // No special handling is needed, just add the object to the current 
rendering pass
         else
         {
+            // an animated group is complete ?
+            if (mpCurrentRenderPass->mpObject)
+                closeRenderPass();
+
             mpCurrentRenderPass->maObjectsAndParagraphs.emplace(pObject, 
std::deque<sal_Int32>());
             mpCurrentRenderPass->meStage = eCurrentStage;
         }
@@ -565,6 +596,20 @@ void 
SlideshowLayerRenderer::resolveEffect(CustomAnimationEffectPtr const& rEffe
     if (!pObject)
         return;
 
+    // afaics, when a shape is part of a group any applied effect is ignored,
+    // so no layer should be created
+    if (pObject->getParentSdrObjectFromSdrObject())
+        return;
+
+    // some kind of effect, like the ones based on color animations,
+    // is ignored when applied to a group
+    if (isGroup(pObject))
+    {
+        if 
(constNonValidEffectsForGroupSet.find(rEffect->getPresetId().toUtf8())
+            != constNonValidEffectsForGroupSet.end())
+            return;
+    }
+
     AnimationRenderInfo aAnimationInfo;
     auto aIterator = maRenderState.maAnimationRenderInfoList.find(pObject);
     if (aIterator != maRenderState.maAnimationRenderInfoList.end())
@@ -779,27 +824,33 @@ void writeAnimated(::tools::JsonWriter& aJsonWriter, 
AnimationLayerInfo const& r
         writeContentNode(aJsonWriter);
         writeBoundingBox(aJsonWriter, pObject);
 
-        if (nParagraph < 0)
+        // a group of object has no such property
+        if (!isGroup(pObject))
         {
-            drawing::FillStyle aFillStyle
-                = pObject->GetProperties().GetItem(XATTR_FILLSTYLE).GetValue();
-            if (aFillStyle == drawing::FillStyle::FillStyle_SOLID)
+            if (nParagraph < 0)
             {
-                auto aFillColor = 
pObject->GetProperties().GetItem(XATTR_FILLCOLOR).GetColorValue();
-                aJsonWriter.put("fillColor", "#" + 
aFillColor.AsRGBHEXString());
+                drawing::FillStyle aFillStyle
+                    = 
pObject->GetProperties().GetItem(XATTR_FILLSTYLE).GetValue();
+                if (aFillStyle == drawing::FillStyle::FillStyle_SOLID)
+                {
+                    auto aFillColor
+                        = 
pObject->GetProperties().GetItem(XATTR_FILLCOLOR).GetColorValue();
+                    aJsonWriter.put("fillColor", "#" + 
aFillColor.AsRGBHEXString());
+                }
+                drawing::LineStyle aLineStyle
+                    = 
pObject->GetProperties().GetItem(XATTR_LINESTYLE).GetValue();
+                if (aLineStyle == drawing::LineStyle::LineStyle_SOLID)
+                {
+                    auto aLineColor
+                        = 
pObject->GetProperties().GetItem(XATTR_LINECOLOR).GetColorValue();
+                    aJsonWriter.put("lineColor", "#" + 
aLineColor.AsRGBHEXString());
+                }
             }
-            drawing::LineStyle aLineStyle
-                = pObject->GetProperties().GetItem(XATTR_LINESTYLE).GetValue();
-            if (aLineStyle == drawing::LineStyle::LineStyle_SOLID)
+            else
             {
-                auto aLineColor = 
pObject->GetProperties().GetItem(XATTR_LINECOLOR).GetColorValue();
-                aJsonWriter.put("lineColor", "#" + 
aLineColor.AsRGBHEXString());
+                writeFontColor(aJsonWriter, pObject, nParagraph);
             }
         }
-        else
-        {
-            writeFontColor(aJsonWriter, pObject, nParagraph);
-        }
     }
 }
 
diff --git a/sd/source/ui/unoidl/unomodel.cxx b/sd/source/ui/unoidl/unomodel.cxx
index 9fccb11d82d5..07f9415a000a 100644
--- a/sd/source/ui/unoidl/unomodel.cxx
+++ b/sd/source/ui/unoidl/unomodel.cxx
@@ -722,6 +722,99 @@ bool isValidNode(const Reference<XAnimationNode>& xNode)
     return false;
 }
 
+SdrObject* getObjectForShape(uno::Reference<drawing::XShape> const& xShape)
+{
+    if (!xShape.is())
+        return nullptr;
+    SvxShape* pShape = comphelper::getFromUnoTunnel<SvxShape>(xShape);
+    if (pShape)
+        return pShape->GetSdrObject();
+    return nullptr;
+}
+
+SdrObject* getTargetObject(const uno::Any& aTargetAny)
+{
+    SdrObject* pObject = nullptr;
+    uno::Reference<drawing::XShape> xShape;
+
+    if ((aTargetAny >>= xShape) && xShape.is())
+    {
+        pObject = getObjectForShape(xShape);
+    }
+    else // if target is not a shape - could be paragraph target containing a 
shape
+    {
+        presentation::ParagraphTarget aParagraphTarget;
+        if ((aTargetAny >>= aParagraphTarget) && aParagraphTarget.Shape.is())
+        {
+            pObject = getObjectForShape(aParagraphTarget.Shape);
+        }
+    }
+
+    return pObject;
+}
+
+bool isNodeTargetInShapeGroup(const Reference<XAnimationNode>& xNode)
+{
+    Reference<XAnimate> xAnimate(xNode, UNO_QUERY);
+    if (xAnimate.is())
+    {
+        SdrObject* pObject = getTargetObject(xAnimate->getTarget());
+        if (pObject)
+            return pObject->getParentSdrObjectFromSdrObject() != nullptr;
+    }
+    return false;
+}
+
+bool isNodeTargetAGroup(const Reference<XAnimationNode>& xNode)
+{
+    Reference<XAnimate> xAnimate(xNode, UNO_QUERY);
+    if (xAnimate.is())
+    {
+        SdrObject* pObject = getTargetObject(xAnimate->getTarget());
+        if (pObject)
+            return pObject->getChildrenOfSdrObject() != nullptr;
+    }
+    return false;
+}
+
+bool isEffectValidForTarget(const Reference<XAnimationNode>& xNode)
+{
+    const Sequence<NamedValue> aUserData(xNode->getUserData());
+    for (const auto& rValue : aUserData)
+    {
+        if (!IsXMLToken(rValue.Name, XML_PRESET_ID))
+            continue;
+
+        OUString aPresetId;
+        if (rValue.Value >>= aPresetId)
+        {
+            if (constNonValidEffectsForGroupSet.find(aPresetId.toUtf8())
+                != constNonValidEffectsForGroupSet.end())
+            {
+                // it's in the list, so we need to check if the effect target 
is a group or not
+                Reference<XTimeContainer> xContainer(xNode, UNO_QUERY);
+                if (xContainer.is())
+                {
+                    Reference<XEnumerationAccess> 
xEnumerationAccess(xContainer, UNO_QUERY);
+                    Reference<XEnumeration> xEnumeration = 
xEnumerationAccess->createEnumeration();
+
+                    // target is the same for all children, check the first one
+                    if (xEnumeration.is() && xEnumeration->hasMoreElements())
+                    {
+                        Reference<XAnimationNode> 
xChildNode(xEnumeration->nextElement(),
+                                                             UNO_QUERY);
+                        if (isNodeTargetAGroup(xChildNode))
+                            return false;
+                    }
+                }
+            }
+        }
+        // preset id found and checked, we can exit
+        break;
+    }
+    return true;
+}
+
 void AnimationsExporter::exportAnimations()
 {
     if (!mxDrawPage.is() || !mxPageProps.is() || !mxRootNode.is() || 
!hasEffects())
@@ -733,12 +826,16 @@ void AnimationsExporter::exportAnimations()
         exportNodeImpl(mxRootNode);
     }
 }
+
 void AnimationsExporter::exportNode(const Reference<XAnimationNode>& xNode)
 {
-     if (!isValidNode(xNode))
-         return;
-     ::tools::ScopedJsonWriterStruct aStruct = mrWriter.startStruct();
-     exportNodeImpl(xNode);
+    // afaics, when a shape is part of a group any applied effect is ignored
+    // moreover, some kind of effect, like the ones based on color animations,
+    // is ignored when applied to a group
+    if (!isValidNode(xNode) || isNodeTargetInShapeGroup(xNode) || 
!isEffectValidForTarget(xNode))
+        return;
+    ::tools::ScopedJsonWriterStruct aStruct = mrWriter.startStruct();
+    exportNodeImpl(xNode);
 }
 
 void AnimationsExporter::exportNodeImpl(const Reference<XAnimationNode>& xNode)

Reply via email to