oox/CppunitTest_oox_testscene3d.mk                       |   53 +
 oox/Library_oox.mk                                       |    1 
 oox/Module_oox.mk                                        |    1 
 oox/inc/drawingml/customshapeproperties.hxx              |    2 
 oox/inc/drawingml/scene3dhelper.hxx                      |   96 +++
 oox/qa/unit/data/Scene3d_isometricRightUp.pptx           |binary
 oox/qa/unit/data/Scene3d_legacyObliqueBottomRight.pptx   |binary
 oox/qa/unit/data/Scene3d_legacyPerspectiveTopRight.pptx  |binary
 oox/qa/unit/data/Scene3d_obliqueTopRight.pptx            |binary
 oox/qa/unit/data/Scene3d_orthographicFront.pptx          |binary
 oox/qa/unit/data/Scene3d_perspectiveContrastingLeft.pptx |binary
 oox/qa/unit/testscene3d.cxx                              |  320 ++++++++++
 oox/source/drawingml/customshapeproperties.cxx           |   10 
 oox/source/drawingml/scene3dhelper.cxx                   |  442 +++++++++++++++
 oox/source/drawingml/shape.cxx                           |   74 ++
 oox/source/token/properties.txt                          |   23 
 16 files changed, 1008 insertions(+), 14 deletions(-)

New commits:
commit 6e5529d7382558a38e547074e2f674e85e11e66e
Author:     Regina Henschel <rb.hensc...@t-online.de>
AuthorDate: Tue Jan 23 20:18:48 2024 +0100
Commit:     Miklos Vajna <vmik...@collabora.com>
CommitDate: Mon Feb 19 13:57:14 2024 +0100

    tdf#70039 convert 3D effects to extrusion
    
    ODF allows a 3D mode for custom shapes. That can be used to render
    some of the 3D effects possible in MS Office.
    
    MS Office has not published, how they calculate the 3D-scenes. Thus
    most principles and values are found by experiments. My assumptions
    are contained as comments.
    
    This current solution does not work well for perspectiveFront camera
    with rotation on only y-axis or on only x-axis. If someone has an idea,
    what is wrong in my solution or what MS Office might specially do,
    please tell me.
    
    The tests do not cover whether the rendering in LO is the same as in
    MS Office. I have no idea how to write such tests. To test manually:
    In Powerpoint: Copy the shape and set the copy to wireframe. Cut the
    copy and insert it as svg image. Move the image so that the lines cover
    the original shape. Save it. In LibreOffice: Open the file and set the
    shape to wireframe. Now you can easily compare the rendering of
    PowerPoint and LibreOffice.
    
    Extrusion can be used for images, that have a 3D-scene applied like in
    tdf#45495. That would work with this patch, but the related places are
    commented out because of tdf#159515.
    
    This patch does not cover lighting and material and it does not
    contain export.
    
    3D-text is not contained in the patch. There are principle problems
    with 3D-text. Thus a solution requieres a lot, including additions to
    the ODF standard.
    
    The comments in tdf#70039 contain more about aspects, where MS Office
    and ODF are in principle incompatible in regard to 3D-effects.
    
    Change-Id: I8a5da536ade2a4b67630af221ea47e0288450188
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/162594
    Reviewed-by: Sarper Akdemir <sarper.akdemir.ext...@allotropia.de>
    Tested-by: Jenkins
    Reviewed-by: Miklos Vajna <vmik...@collabora.com>

diff --git a/oox/CppunitTest_oox_testscene3d.mk 
b/oox/CppunitTest_oox_testscene3d.mk
new file mode 100644
index 000000000000..23b08eee6fed
--- /dev/null
+++ b/oox/CppunitTest_oox_testscene3d.mk
@@ -0,0 +1,53 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#*************************************************************************
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+#*************************************************************************
+
+$(eval $(call gb_CppunitTest_CppunitTest,oox_testscene3d))
+
+$(eval $(call gb_CppunitTest_use_externals,oox_testscene3d,\
+       boost_headers \
+       libxml2 \
+))
+
+$(eval $(call gb_CppunitTest_add_exception_objects,oox_testscene3d, \
+    oox/qa/unit/testscene3d \
+))
+
+$(eval $(call gb_CppunitTest_use_libraries,oox_testscene3d, \
+    comphelper \
+    cppu \
+    cppuhelper \
+    oox \
+    sal \
+    subsequenttest \
+    test \
+    unotest \
+    utl \
+    tl \
+))
+
+$(eval $(call gb_CppunitTest_use_sdk_api,oox_testscene3d))
+
+$(eval $(call gb_CppunitTest_use_ure,oox_testscene3d))
+$(eval $(call gb_CppunitTest_use_vcl,oox_testscene3d))
+
+$(eval $(call gb_CppunitTest_use_rdb,oox_testscene3d,services))
+
+$(eval $(call gb_CppunitTest_use_custom_headers,oox_testscene3d,\
+       officecfg/registry \
+))
+
+$(eval $(call gb_CppunitTest_use_configuration,oox_testscene3d))
+
+$(eval $(call gb_CppunitTest_add_arguments,oox_testscene3d, \
+    
-env:arg-env=$(gb_Helper_LIBRARY_PATH_VAR)"$$$${$(gb_Helper_LIBRARY_PATH_VAR)+=$$$$$(gb_Helper_LIBRARY_PATH_VAR)}"
 \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/oox/Library_oox.mk b/oox/Library_oox.mk
index 343781331720..7330fcc8995d 100644
--- a/oox/Library_oox.mk
+++ b/oox/Library_oox.mk
@@ -181,6 +181,7 @@ $(eval $(call gb_Library_add_exception_objects,oox,\
     oox/source/drawingml/objectdefaultcontext \
     oox/source/drawingml/presetgeometrynames \
     oox/source/drawingml/scene3dcontext \
+    oox/source/drawingml/scene3dhelper \
     oox/source/drawingml/shapecontext \
     oox/source/drawingml/shape \
     oox/source/drawingml/shape3dproperties \
diff --git a/oox/Module_oox.mk b/oox/Module_oox.mk
index 34efb288d401..f1d88c2dee21 100644
--- a/oox/Module_oox.mk
+++ b/oox/Module_oox.mk
@@ -35,6 +35,7 @@ $(eval $(call gb_Module_add_check_targets,oox,\
        CppunitTest_oox_shape \
        CppunitTest_oox_export \
     CppunitTest_oox_mcgr \
+    CppunitTest_oox_testscene3d \
     CppunitTest_oox_wpc_drawing_canvas \
 ))
 endif
diff --git a/oox/inc/drawingml/customshapeproperties.hxx 
b/oox/inc/drawingml/customshapeproperties.hxx
index c699ffddcce7..4b9ee40503cd 100644
--- a/oox/inc/drawingml/customshapeproperties.hxx
+++ b/oox/inc/drawingml/customshapeproperties.hxx
@@ -124,6 +124,7 @@ public:
 
     sal_Int32 getArcNum() { return mnArcNum++; }
     sal_Int32 countArcTo() { return mnArcNum; }
+    PropertyMap& getExtrusionPropertyMap() { return maExtrusionPropertyMap; }
 
     /**
        Returns whether or not the current CustomShapeProperties
@@ -156,6 +157,7 @@ private:
     static void initializePresetDataMap();
 
     sal_Int32 mnArcNum;
+    PropertyMap maExtrusionPropertyMap;
 };
 
 }
diff --git a/oox/inc/drawingml/scene3dhelper.hxx 
b/oox/inc/drawingml/scene3dhelper.hxx
new file mode 100755
index 000000000000..4752cea0cdfc
--- /dev/null
+++ b/oox/inc/drawingml/scene3dhelper.hxx
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#pragma once
+
+#include <drawingml/shape3dproperties.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <oox/helper/propertymap.hxx>
+
+namespace oox
+{
+class Scene3DHelper
+{
+public:
+    /** Creates extrusion properties in rPropertyMap from the 3D information 
in p3DProperties and
+        gives values which have to be set as shape properties.
+        @param [in] p3DProperties a pointer to Shape3DProperties
+        @param [in] rnMSOShapeRotation a MSO shape rotation angle in 1/60000 
deg
+        @param [in, out] rPropertyMap a map that is filled with properties 
directly usable in
+            CustomShapeProperties.pushToPropSet() for property Extrusion.
+        @param [out] rRotZ the angle for shape rotation around z-axis in rad, 
with orientation
+            same as API shape property RotateAngle. It has the shape rotation 
from rnMSOShapeRotation
+            integrated.
+        @param [out] rExtrusionColor a complex color from which the color of 
the extruded faces can
+            be created. The extrusion color is not handled as extrusion 
property but as secondary
+            color in the style of the shape. If no 3D exist or the camera is 
invalid, the parameter
+            value is unchanged.
+        @return Returns true if extrusion properties are generated. Returns 
false if there is no 3D
+            or if rendering without 3D is currently better. rPropertyMap is 
unchanged in such case.
+    */
+    bool setExtrusionProperties(const oox::drawingml::Shape3DPropertiesPtr 
p3DProperties,
+                                const sal_Int32& rnMSOShapeRotation, 
oox::PropertyMap& rPropertyMap,
+                                double& rRotZ, oox::drawingml::Color& 
rExtrusionColor);
+
+private:
+    /** Calculates angles suitable for API from OOXML scene3d angles.
+        @param [in] nLat, nLon, nRev in unit 1/60000 deg with same orientation 
as the attributes lat,
+        lon and rev of the <rot> child element of the <scene3d> element in 
OOXML markup.
+        @param [out] fX, fY, fZ values in unit radians with correct 
orientation for API properties
+            EnhancedCustomShapeExtrusion::RotateAngle and 
RotationDescriptor::RotateAngle*/
+    static void getAPIAnglesFromOOXAngle(const sal_Int32 nLat, const sal_Int32 
nLon,
+                                         const sal_Int32 nRev, double& fX, 
double& fY, double& fZ);
+
+    /** Calculates angles suitable for API from Shape3DProperties.
+        @details It considers the preset camera in case the optional, direct 
rotation angles have no
+            value. It integrates the given rnMSOShapeRotation into fZ.
+        @param [in] p3DProperties a pointer to Shape3DProperties
+        @param [in] rnMSOShapeRotation rotation in 1/60000 deg as given in 
'rot' attribute of 'xfrm'
+            element in OOXML.
+        @param [out] fX, fY, fZ values in unit radians with correct 
orientation for API properties
+            EnhancedCustomShapeExtrusion::RotateAngle and 
RotationDescriptor::RotateAngle*/
+    void getAPIAnglesFrom3DProperties(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                      const sal_Int32& rnMSOShapeRotation, 
double& fX, double& fY,
+                                      double& fZ);
+
+    /** Adds the rotation angles fX and fY as property RotateAngle to the map.
+        @param [in, out] rPropertyMap a map to add the RotateAngle property
+        @param [in] fX, fY rotation angle in unit rad with correct orientation 
for the property.*/
+    static void addRotateAngleToMap(oox::PropertyMap& rPropertyMap, const 
double fX,
+                                    const double fY);
+
+    /** Adds the Depth property to the map.
+        @details The second component is relative, whereas the z-position in 
OOXML is absolute. Uses
+        360EMU depth in case of zero Depth as otherwise no relative position 
is possible.
+        @param [in] p3DProperties a pointer to Shape3DProperties
+        @param [in, out] rPropertyMap a map to add the Depth property*/
+    static void addExtrusionDepthToMap(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                       oox::PropertyMap& rPropertyMap);
+
+    /** Adds the projection mode itself and the associated camera parameters 
to the map.
+        @details Both modes add ProjectionMode and Origin properties. Adds 
Skew property in case of
+        mode PARALLEL and ViewPoint property in case of mode PERSPECTIVE. The 
Skew angles includes the
+        shape rotation because MSOffice rotates after creating the projection 
and ODF before.
+        @param [in] p3DProperties a pointer to Shape3DProperties
+        @param [in, out] rPropertyMap a map to add ProjectMode, Origin and 
Skew or ViewPoint
+            properties.
+        @param [in] bIsParallel true for mode PARALLEL, false for PRERPECTIVE
+        @param [in] rnMSOShapeRotation shape rotation in 1/60000 degree*/
+    void addProjectionGeometryToMap(const oox::drawingml::Shape3DPropertiesPtr 
p3DProperties,
+                                    oox::PropertyMap& rPropertyMap, const bool 
bIsParallel,
+                                    const sal_Int32 rnMSOShapeRotation);
+
+    // Index into array aPrstCameraValuesArray.
+    sal_Int16 mnPrstCameraIndex = -1; // '-1' means invalid or not yet searched
+}; // end class Scene3DHelper
+
+} // end namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
\ No newline at end of file
diff --git a/oox/qa/unit/data/Scene3d_isometricRightUp.pptx 
b/oox/qa/unit/data/Scene3d_isometricRightUp.pptx
new file mode 100644
index 000000000000..e060adc935b6
Binary files /dev/null and b/oox/qa/unit/data/Scene3d_isometricRightUp.pptx 
differ
diff --git a/oox/qa/unit/data/Scene3d_legacyObliqueBottomRight.pptx 
b/oox/qa/unit/data/Scene3d_legacyObliqueBottomRight.pptx
new file mode 100644
index 000000000000..2d447cd59b17
Binary files /dev/null and 
b/oox/qa/unit/data/Scene3d_legacyObliqueBottomRight.pptx differ
diff --git a/oox/qa/unit/data/Scene3d_legacyPerspectiveTopRight.pptx 
b/oox/qa/unit/data/Scene3d_legacyPerspectiveTopRight.pptx
new file mode 100644
index 000000000000..6493c5d0ac84
Binary files /dev/null and 
b/oox/qa/unit/data/Scene3d_legacyPerspectiveTopRight.pptx differ
diff --git a/oox/qa/unit/data/Scene3d_obliqueTopRight.pptx 
b/oox/qa/unit/data/Scene3d_obliqueTopRight.pptx
new file mode 100644
index 000000000000..409d97cb5bfe
Binary files /dev/null and b/oox/qa/unit/data/Scene3d_obliqueTopRight.pptx 
differ
diff --git a/oox/qa/unit/data/Scene3d_orthographicFront.pptx 
b/oox/qa/unit/data/Scene3d_orthographicFront.pptx
new file mode 100644
index 000000000000..68f5a8b167a8
Binary files /dev/null and b/oox/qa/unit/data/Scene3d_orthographicFront.pptx 
differ
diff --git a/oox/qa/unit/data/Scene3d_perspectiveContrastingLeft.pptx 
b/oox/qa/unit/data/Scene3d_perspectiveContrastingLeft.pptx
new file mode 100644
index 000000000000..a4863cc608c0
Binary files /dev/null and 
b/oox/qa/unit/data/Scene3d_perspectiveContrastingLeft.pptx differ
diff --git a/oox/qa/unit/testscene3d.cxx b/oox/qa/unit/testscene3d.cxx
new file mode 100755
index 000000000000..27987089a339
--- /dev/null
+++ b/oox/qa/unit/testscene3d.cxx
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <test/unoapixml_test.hxx>
+
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/drawing/ProjectionMode.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <comphelper/sequenceashashmap.hxx>
+#include <editeng/unoprnms.hxx>
+
+using namespace css;
+
+/// Covers tests for scene3d import and export, available since LO 24.8., see 
tdf#70039
+class TestScene3d : public UnoApiXmlTest
+{
+public:
+    TestScene3d()
+        : UnoApiXmlTest("/oox/qa/unit/data/")
+    {
+    }
+
+protected:
+    // get shape with nShapeIndex from page nPageIndex
+    uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex, sal_uInt8 
nPageIndex);
+};
+
+uno::Reference<drawing::XShape> TestScene3d::getShape(sal_uInt8 nShapeIndex, 
sal_uInt8 nPageIndex)
+{
+    uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
+                                                                   
uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", 
xDrawPagesSupplier.is());
+    uno::Reference<drawing::XDrawPages> 
xDrawPages(xDrawPagesSupplier->getDrawPages());
+    uno::Reference<drawing::XDrawPage> 
xDrawPage(xDrawPages->getByIndex(nPageIndex),
+                                                 uno::UNO_QUERY_THROW);
+    CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
+    uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), 
uno::UNO_QUERY);
+    CPPUNIT_ASSERT_MESSAGE("Could not get xShape", xShape.is());
+    return xShape;
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_isometricRightUp)
+{
+    // Given a document with a scene3d element on a shape. Without the fix, 
the shape was imported as
+    // flat 2D shape without extrusion. This test covers some basic properties.
+    loadFromFile(u"Scene3d_isometricRightUp.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(1, 0)); // shape 1 on page 0
+
+    // Prepare property maps
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
+    comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+
+    // Make sure extrusion is on.
+    bool bIsExtruded(false);
+    aExtrusionPropMap.getValue(u"Extrusion"_ustr) >>= bIsExtruded;
+    CPPUNIT_ASSERT_MESSAGE("error: Extrusion not enabled", bIsExtruded);
+
+    // Make sure the extrusion properties correspond to the camera preset type.
+    // projection mode should be PARALLEL
+    drawing::ProjectionMode eProjectionMode = 
drawing::ProjectionMode_PERSPECTIVE;
+    aExtrusionPropMap.getValue(u"ProjectionMode"_ustr) >>= eProjectionMode;
+    CPPUNIT_ASSERT_EQUAL(drawing::ProjectionMode_PARALLEL, eProjectionMode);
+
+    // For Skew, Origin, RotateAngle and Depth
+    drawing::EnhancedCustomShapeParameterPair aParaPair;
+
+    // Should be front projection, which means Skew amount and angle are zero.
+    aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // .. and Origin x and y are zero.
+    aExtrusionPropMap.getValue(u"Origin"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // Rotation around x-axis should be -35deg, around y-axis should be -45deg.
+    aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-35.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-45.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // Depth should be 36pt = 1270Hmm without shift
+    aExtrusionPropMap.getValue(u"Depth"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1270.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // This shape does not use an extrusion color.
+    bool bIsExtrusionColorEnabled(true);
+    aExtrusionPropMap.getValue(u"Color"_ustr) >>= bIsExtrusionColorEnabled;
+    CPPUNIT_ASSERT_MESSAGE("error: Extrusion color enabled", 
!bIsExtrusionColorEnabled);
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_legacyObliqueBottomRight)
+{
+    // The legacy preset camera types correspond to the geometry available in 
the UI of LibreOffice.
+    // They are not available in the UI of MS Office, but if given, user can 
change some properties.
+    // The test includes some of them.
+
+    loadFromFile(u"Scene3d_legacyObliqueBottomRight.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(1, 0)); // shape 1 on page 0
+
+    // Prepare property maps
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
+    comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+
+    // For Skew, Origin, RotateAngle and Depth
+    drawing::EnhancedCustomShapeParameterPair aParaPair;
+
+    // Should be a slanted projection with shifted origin
+    aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(50.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(135.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+    aExtrusionPropMap.getValue(u"Origin"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // The shape has a rotation. Rotations on x-axis and y-axis belong to the 
extrusion rotation,
+    // rotation on z-axis belongs to the shape rotation.
+    aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-3.45117839701884, 
aParaPair.First.Value.get<double>(), 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(9.3912858020435, 
aParaPair.Second.Value.get<double>(), 1E-12);
+    sal_Int32 nZRotate; // unit 1/100 degree
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(2028), nZRotate);
+
+    // Depth should be 36pt = 1270Hmm. The second value corresponds to the 
'Distance from ground'
+    // setting in the UI of MS Office, only that it is relative to the depth 
in ODF.
+    aExtrusionPropMap.getValue(u"Depth"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(1270.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // This shape uses an extrusion color. The extrusion color itself does not 
belong to the
+    // extrusion but to the shape fill properties. The color is not a complex 
color but still RGB.
+    bool bIsExtrusionColorEnabled(false);
+    aExtrusionPropMap.getValue(u"Color"_ustr) >>= bIsExtrusionColorEnabled;
+    CPPUNIT_ASSERT_MESSAGE("error: Extrusion color is not enabled", 
bIsExtrusionColorEnabled);
+    Color nColor;
+    xShapeProps->getPropertyValue(UNO_NAME_FILLCOLOR_2) >>= nColor;
+    CPPUNIT_ASSERT_EQUAL(Color(0x89c064), nColor);
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_obliqueTopRight)
+{
+    // MS Office applies the shape rotation after the camera rotations in case 
of oblique preset
+    // camera types. That needs to be converted to the 'first shape rotation' 
specification of ODF.
+    // That conversion results in angles not available in the UI of 
LibreOffice, but LO can
+    // render them. The shape has got these rotation in the UI of MS Office: 
60� shape ,
+    // 50� on x-axis, 10� on y-axis and 40� on z-axis.
+    loadFromFile(u"Scene3d_obliqueTopRight.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
+
+    // Prepare property maps
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
+    comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+
+    // For Skew and RotateAngle
+    drawing::EnhancedCustomShapeParameterPair aParaPair;
+
+    // Should be an oblique projection with non default skew values
+    aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, aParaPair.First.Value.get<double>(), 
1E-14);
+    // -135� from preset camera type, minus 60� shape rotation
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-195.0, aParaPair.Second.Value.get<double>(), 
1E-14);
+
+    // Because of the needed conversions the resulting angles are very 
different from
+    // those set in the UI of MS Office.
+    aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-45.7369327638437, 
aParaPair.First.Value.get<double>(), 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(24.9102537477636, 
aParaPair.Second.Value.get<double>(), 1E-12);
+    sal_Int32 nZRotate; // unit 1/100 degree
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(34597), nZRotate);
+
+    // The test uses BoundRect to verify position and size of the resulting 
scene.
+    // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+    awt::Rectangle aBoundRect;
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(8325, aBoundRect.Width, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7804, aBoundRect.Height, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(8272, aBoundRect.X, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(3510, aBoundRect.Y, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_orthographicFront)
+{
+    // MS Office users might use the z-rotation instead of the shape rotation 
without intending an
+    // extrusion. As of March 2024 we import the shape as 2D shape in such 
case, because extrusion
+    // looses the stroke, see tdf#159334.
+    loadFromFile(u"Scene3d_orthographicFront.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
+
+    // Make sure that in case extrusion properties exist, the extrusion is 
disabled.
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    if (aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq)
+    {
+        comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+        bool bIsExtruded(true);
+        aExtrusionPropMap.getValue(u"Extrusion"_ustr) >>= bIsExtruded;
+        CPPUNIT_ASSERT_MESSAGE("error: Extrusion is enabled", !bIsExtruded);
+    }
+
+    // Make sure the shape is nevertheless rotated.
+    sal_Int32 nZRotate; // unit 1/100 degree
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(9000), nZRotate);
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_perspectiveContrastingLeft)
+{
+    // The file contains a shape with the preset camera 
perspectiveContrastingLeft.
+    // Such shape cannot be produced in the UI of LibreOffice, but it can be 
rendered.
+    loadFromFile(u"Scene3d_perspectiveContrastingLeft.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
+
+    // Prepare property maps
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
+    comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+
+    // projection mode should be PERSPECTIVE
+    drawing::ProjectionMode eProjectionMode = drawing::ProjectionMode_PARALLEL;
+    aExtrusionPropMap.getValue(u"ProjectionMode"_ustr) >>= eProjectionMode;
+    CPPUNIT_ASSERT_EQUAL(drawing::ProjectionMode_PERSPECTIVE, eProjectionMode);
+
+    // A perspective projection needs a viewPoint. MS Office has it on the 
z-axis.
+    drawing::Position3D aViewPoint;
+    aExtrusionPropMap.getValue(u"ViewPoint"_ustr) >>= aViewPoint;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionX, 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionY, 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(38451.0, aViewPoint.PositionZ, 1E-12);
+
+    // Check rotation angles
+    drawing::EnhancedCustomShapeParameterPair aParaPair;
+    aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-6.94093344831102, 
aParaPair.First.Value.get<double>(), 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(44.4431265782766, 
aParaPair.Second.Value.get<double>(), 1E-12);
+    sal_Int32 nZRotate; // unit 1/100 degree
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
+    CPPUNIT_ASSERT_EQUAL(sal_Int32(35504), nZRotate);
+
+    // Use BoundRect as workaround, because there is no easy way to test the 
rendered line geometry.
+    // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+    awt::Rectangle aBoundRect;
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(6614, aBoundRect.Width, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(8577, aBoundRect.Height, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(5725, aBoundRect.X, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(4213, aBoundRect.Y, 10);
+}
+
+CPPUNIT_TEST_FIXTURE(TestScene3d, test_legacyPerspectiveTopRight)
+{
+    // The file contains a shape with the preset camera 
legacyPerspectiveTopLeft. These kind of
+    // camera corresponds directly to the extrusions available in the UI of 
LibreOffice. The
+    // non-frontal view is not done by rotation but by shifting the view point 
horizontal and
+    // vertical. That is tested here.
+    loadFromFile(u"Scene3d_legacyPerspectiveTopRight.pptx");
+    uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
+
+    // Prepare property maps
+    uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
+    uno::Sequence<beans::PropertyValue> aGeoPropSeq;
+    xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
+    comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
+    uno::Sequence<beans::PropertyValue> aExtrusionSeq;
+    aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
+    comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
+
+    // shifted view point
+    drawing::Position3D aViewPoint;
+    aExtrusionPropMap.getValue(u"ViewPoint"_ustr) >>= aViewPoint;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(3472.0, aViewPoint.PositionX, 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(-3472.0, aViewPoint.PositionY, 1E-12);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(25000.0, aViewPoint.PositionZ, 1E-12);
+
+    // Use BoundRect as workaround, because there is no easy way to test the 
rendered line geometry.
+    // The tolerance 10 is estimated and can be adjusted if required for HiDPI.
+    awt::Rectangle aBoundRect;
+    xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7004, aBoundRect.Width, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(7004, aBoundRect.Height, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(6292, aBoundRect.X, 10);
+    CPPUNIT_ASSERT_DOUBLES_EQUAL(3138, aBoundRect.Y, 10);
+}
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/customshapeproperties.cxx 
b/oox/source/drawingml/customshapeproperties.cxx
index 321bc9bf537a..533401f586dd 100644
--- a/oox/source/drawingml/customshapeproperties.cxx
+++ b/oox/source/drawingml/customshapeproperties.cxx
@@ -129,6 +129,11 @@ void CustomShapeProperties::pushToPropSet(
         aPropertyMap.setProperty( PROP_TextCameraZRotateAngle, 
mnTextCameraZRotateAngle );
         if (moTextAreaRotateAngle.has_value())
             aPropertyMap.setProperty(PROP_TextRotateAngle, 
moTextAreaRotateAngle.value());
+        if (!maExtrusionPropertyMap.empty())
+        {
+            Sequence< PropertyValue > aExtrusionSequence = 
maExtrusionPropertyMap.makePropertyValueSequence();
+            aPropertyMap.setAnyProperty( PROP_Extrusion, 
css::uno::Any(aExtrusionSequence));
+        }
         Sequence< PropertyValue > aSeq = 
aPropertyMap.makePropertyValueSequence();
         aPropSet.setProperty( PROP_CustomShapeGeometry, aSeq );
 
@@ -361,6 +366,11 @@ void CustomShapeProperties::pushToPropSet(
             aHandlesRange[ i ] = aHandle.makePropertyValueSequence();
         }
         aPropertyMap.setProperty( PROP_Handles, aHandles);
+        if (!maExtrusionPropertyMap.empty())
+        {
+            Sequence< PropertyValue > aExtrusionSequence = 
maExtrusionPropertyMap.makePropertyValueSequence();
+            aPropertyMap.setProperty( PROP_Extrusion, aExtrusionSequence);
+        }
 
 #ifdef DEBUG
         // Note that the script 
oox/source/drawingml/customshapes/generatePresetsData.pl looks
diff --git a/oox/source/drawingml/scene3dhelper.cxx 
b/oox/source/drawingml/scene3dhelper.cxx
new file mode 100755
index 000000000000..274cc460b825
--- /dev/null
+++ b/oox/source/drawingml/scene3dhelper.cxx
@@ -0,0 +1,442 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#include <drawingml/scene3dhelper.hxx>
+
+#include <basegfx/matrix/b3dhommatrix.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <oox/drawingml/drawingmltypes.hxx>
+#include <oox/helper/propertymap.hxx>
+#include <oox/token/properties.hxx>
+#include <oox/token/tokens.hxx>
+
+#include <com/sun/star/drawing/EnhancedCustomShapeParameter.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
+#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
+#include <com/sun/star/drawing/Position3D.hpp>
+#include <com/sun/star/drawing/ProjectionMode.hpp>
+
+namespace oox
+{
+/** This struct is used to hold values from the OOXML camera preset types.*/
+namespace
+{
+struct PrstCameraValues
+{
+    std::u16string_view msCameraPrstName; // identifies the value set
+
+    bool mbIsParallel;
+
+    // values as shown in the UI of MS Office, converted to 1/60000 deg
+    double mfRotateAngleX; // unit 1/60000 degree
+    double mfRotateAngleY; // unit 1/60000 degree
+    double mfRotateAngleZ; // unit 1/60000 degree
+
+    // Position of origin relative to the bounding box of the transformed 2D 
shape.
+    // LibreOffice can handle values outside the ODF range.
+    double mfOriginX; // ODF range [-0.5 (left).. 0.5 (right)], fraction of 
width
+    double mfOriginY; // ODF range [-0.5 (top) 0.5 (bottom)], fraction of 
height
+
+    // mandatory for PARALLEL, ignored for PERSPECTIVE
+    double mfSkewAmount; // range 0 to 100, percent of depth used as slant 
length
+    double mfSkewAngle; // unit degree
+
+    // mandatory for PERSPECTIVE, ignored for PARALLEL
+    // API type ::com::sun::star::drawing::Position3D; unit 1/100 mm
+    double mfViewPointX; // shift from Origin
+    double mfViewPointY; // shift from Origin
+    double mfViewPointZ; // absolute z-coordinate
+
+    // The OOXML camera attribute "zoom" is not contained, because it is not 
set in preset camera
+    // types and LO cannot render it in custom shape extrusion scene.
+};
+} // end anonymous namespace
+
+// The values were found experimental using MS Office. A spreadsheet with 
remarks is attached
+// to tdf#70039.
+constexpr sal_uInt16 nCameraPresetCount(62); // Fixed, specified in OOXML 
standard.
+constexpr PrstCameraValues aPrstCameraValuesArray[nCameraPresetCount] = {
+    { u"isometricBottomDown", true, 2124000, 18882000, 17988000, 0, 0, 0, 0, 
0, 0, 0 },
+    { u"isometricBottomUp", true, 2124000, 2718000, 3612000, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"isometricLeftDown", true, 2100000, 2700000, 0, 0, 0, 0, 0, 0, 0, 0 },
+    { u"isometricLeftUp", true, 19500000, 2700000, 0, 0, 0, 0, 0, 0, 0, 0 },
+    { u"isometricOffAxis1Left", true, 1080000, 3840000, 0, 0, 0, 0, 0, 0, 0, 0 
},
+    { u"isometricOffAxis1Right", true, 1080000, 20040000, 0, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"isometricOffAxis1Top", true, 18078000, 18390000, 3456000, 0, 0, 0, 0, 
0, 0, 0 },
+    { u"isometricOffAxis2Left", true, 1080000, 1560000, 0, 0, 0, 0, 0, 0, 0, 0 
},
+    { u"isometricOffAxis2Right", true, 1080000, 17760000, 0, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"isometricOffAxis2Top", true, 18078000, 3210000, 18144000, 0, 0, 0, 0, 
0, 0, 0 },
+    { u"isometricOffAxis3Bottom", true, 3522000, 18390000, 18144000, 0, 0, 0, 
0, 0, 0, 0 },
+    { u"isometricOffAxis3Left", true, 20520000, 3840000, 0, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"isometricOffAxis3Right", true, 20520000, 20040000, 0, 0, 0, 0, 0, 0, 
0, 0 },
+    { u"isometricOffAxis4Bottom", true, 3522000, 3210000, 3456000, 0, 0, 0, 0, 
0, 0, 0 },
+    { u"isometricOffAxis4Left", true, 20520000, 1560000, 0, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"isometricOffAxis4Right", true, 20520000, 17760000, 0, 0, 0, 0, 0, 0, 
0, 0 },
+    { u"isometricRightDown", true, 19500000, 18900000, 0, 0, 0, 0, 0, 0, 0, 0 
},
+    { u"isometricRightUp", true, 2100000, 18900000, 0, 0, 0, 0, 0, 0, 0, 0 },
+    { u"isometricTopDown", true, 19476000, 2718000, 17988000, 0, 0, 0, 0, 0, 
0, 0 },
+    { u"isometricTopUp", true, 19476000, 18882000, 3612000, 0, 0, 0, 0, 0, 0, 
0 },
+    { u"legacyObliqueBottom", true, 0, 0, 0, 0, 0.5, 50, 90, 0, 0, 0 },
+    { u"legacyObliqueBottomLeft", true, 0, 0, 0, -0.5, 0.5, 50, 45, 0, 0, 0 },
+    { u"legacyObliqueBottomRight", true, 0, 0, 0, 0.5, 0.5, 50, 135, 0, 0, 0 },
+    { u"legacyObliqueFront", true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+    { u"legacyObliqueLeft", true, 0, 0, 0, -0.5, 0, 50, -360, 0, 0, 0 },
+    { u"legacyObliqueRight", true, 0, 0, 0, 0.5, 0, 50, 180, 0, 0, 0 },
+    { u"legacyObliqueTop", true, 0, 0, 0, 0, -0.5, 50, -90, 0, 0, 0 },
+    { u"legacyObliqueTopLeft", true, 0, 0, 0, -0.5, -0.5, 50, -45, 0, 0, 0 },
+    { u"legacyObliqueTopRight", true, 0, 0, 0, 0.5, -0.5, 50, -135, 0, 0, 0 },
+    { u"legacyPerspectiveBottom", false, 0, 0, 0, 0, 0.5, 50, 90, 0, 3472, 
25000 },
+    { u"legacyPerspectiveBottomLeft", false, 0, 0, 0, -0.5, 0.5, 50, 45, 
-3472, 3472, 25000 },
+    { u"legacyPerspectiveBottomRight", false, 0, 0, 0, 0.5, 0.5, 50, 135, 
3472, 3472, 25000 },
+    { u"legacyPerspectiveFront", false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25000 },
+    { u"legacyPerspectiveLeft", false, 0, 0, 0, -0.5, 0, 50, -360, -3472, 0, 
25000 },
+    { u"legacyPerspectiveRight", false, 0, 0, 0, 0.5, 0, 50, 180, 3472, 0, 
25000 },
+    { u"legacyPerspectiveTop", false, 0, 0, 0, 0, -0.5, 50, -90, 0, -3472, 
25000 },
+    { u"legacyPerspectiveTopLeft", false, 0, 0, 0, -0.5, -0.5, 50, -45, -3472, 
-3472, 25000 },
+    { u"legacyPerspectiveTopRight", false, 0, 0, 0, 0.5, -0.5, 50, -135, 3472, 
-3472, 25000 },
+    { u"obliqueBottom", true, 0, 0, 0, 0, 0.5, 30, 90, 0, 0, 0 },
+    { u"obliqueBottomLeft", true, 0, 0, 0, -0.5, 0.5, 30, 45, 0, 0, 0 },
+    { u"obliqueBottomRight", true, 0, 0, 0, 0.5, 0.5, 30, 135, 0, 0, 0 },
+    { u"obliqueLeft", true, 0, 0, 0, -0.5, 0, 30, -360, 0, 0, 0 },
+    { u"obliqueRight", true, 0, 0, 0, 0.5, 0, 30, 180, 0, 0, 0 },
+    { u"obliqueTop", true, 0, 0, 0, 0, -0.5, 30, -90, 0, 0, 0 },
+    { u"obliqueTopLeft", true, 0, 0, 0, -0.5, -0.5, 30, -45, 0, 0, 0 },
+    { u"obliqueTopRight", true, 0, 0, 0, 0.5, -0.5, 30, -135, 0, 0, 0 },
+    { u"orthographicFront", true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+    { u"perspectiveAbove", false, 20400000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
+    { u"perspectiveAboveLeftFacing", false, 2358000, 858000, 20466000, 0, 0, 
0, 0, 0, 0, 38451 },
+    { u"perspectiveAboveRightFacing", false, 2358000, 20742000, 1134000, 0, 0, 
0, 0, 0, 0, 38451 },
+    { u"perspectiveBelow", false, 1200000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
+    { u"perspectiveContrastingLeftFacing", false, 624000, 2634000, 21384000, 
0, 0, 0, 0, 0, 0,
+      38451 },
+    { u"perspectiveContrastingRightFacing", false, 624000, 18966000, 216000, 
0, 0, 0, 0, 0, 0,
+      38451 },
+    { u"perspectiveFront", false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
+    { u"perspectiveHeroicExtremeLeftFacing", false, 486000, 2070000, 21426000, 
0, 0, 0, 0, 0, 0,
+      18981 },
+    { u"perspectiveHeroicExtremeRightFacing", false, 486000, 19530000, 174000, 
0, 0, 0, 0, 0, 0,
+      18981 },
+    { u"perspectiveHeroicLeftFacing", false, 20940000, 858000, 156000, 0, 0, 
0, 0, 0, 0, 38451 },
+    { u"perspectiveHeroicRightFacing", false, 20940000, 20742000, 21444000, 0, 
0, 0, 0, 0, 0,
+      38451 },
+    { u"perspectiveLeft", false, 0, 1200000, 0, 0, 0, 0, 0, 0, 0, 38451 },
+    { u"perspectiveRelaxed", false, 18576000, 0, 0, 0, 0, 0, 0, 0, 0, 38451 },
+    { u"perspectiveRelaxedModerately", false, 19488000, 0, 0, 0, 0, 0, 0, 0, 
0, 38451 },
+    { u"perspectiveRight", false, 0, 20400000, 0, 0, 0, 0, 0, 0, 0, 38451 }
+};
+
+namespace
+{
+/** Searches for the item in aPrstCameraValuesArray with given sPresetName.
+    @param [in] sPresetName name as specified in OOXML standard
+    @return returns the index if item exists, otherwise -1*/
+sal_Int16 getPrstCameraIndex(std::u16string_view sPresetName)
+{
+    sal_Int16 nIt(0);
+    while (nIt < nCameraPresetCount && 
aPrstCameraValuesArray[nIt].msCameraPrstName != sPresetName)
+        ++nIt;
+    if (nIt >= nCameraPresetCount)
+    {
+        nIt = -1; // Error is handled by caller
+    }
+    return nIt;
+}
+} // end anonymous namespace
+
+void Scene3DHelper::getAPIAnglesFromOOXAngle(const sal_Int32 nLat, const 
sal_Int32 nLon,
+                                             const sal_Int32 nRev, double& fX, 
double& fY,
+                                             double& fZ)
+{
+    // MS Office applies the rotations in the order first around y-axis by 
nLon, then around x-axis
+    // by nLat and last around z-axis by nRev. The extrusion mode in ODF and 
also the API
+    // first rotate around the z-axis, then around the y-axis and last around 
the x-axis. In ODF, the
+    // rotation around the z-axis is integrated into the shape transformation 
and the others are
+    // specified in the enhanced geometry of the shape.
+    // The orientation of the resulting angles equals the orientation in API, 
but the angles are in
+    // radians.
+
+    // First we build the total rotation matrix from the OOX angles. y-axis 
points down.
+    basegfx::B3DHomMatrix aXMat;
+    const double fLatRad = basegfx::deg2rad<60000>(nLat);
+    aXMat.set(1, 1, cos(fLatRad));
+    aXMat.set(2, 2, cos(fLatRad));
+    aXMat.set(1, 2, sin(fLatRad));
+    aXMat.set(2, 1, -sin(fLatRad));
+
+    basegfx::B3DHomMatrix aYMat;
+    const double fLonRad = basegfx::deg2rad<60000>(nLon);
+    aYMat.set(0, 0, cos(fLonRad));
+    aYMat.set(2, 2, cos(fLonRad));
+    aYMat.set(0, 2, -sin(fLonRad));
+    aYMat.set(2, 0, sin(fLonRad));
+
+    basegfx::B3DHomMatrix aZMat;
+    const double fRevRad = basegfx::deg2rad<60000>(nRev);
+    aZMat.set(0, 0, cos(fRevRad));
+    aZMat.set(1, 1, cos(fRevRad));
+    aZMat.set(0, 1, sin(fRevRad));
+    aZMat.set(1, 0, -sin(fRevRad));
+    basegfx::B3DHomMatrix aTotalMat = aZMat * aXMat * aYMat;
+
+    // Now we decompose it so that rotation around z-axis is the first 
rotation. We know it is a
+    // orthonormal matrix, so some steps seen in B3DHomMatrix::decompose() are 
not needed.
+    // The solution fY2 = pi - fY results in the same projection, thus we do 
not consider it.
+    fY = std::asin(-aTotalMat.get(0, 2));
+
+    if (basegfx::fTools::equalZero(cos(fY)))
+    {
+        // This case has zeros at positions (0,0), (0,1), (1,2) and (2,2) in 
aTotalMat.
+        // This special case means, that the shape is rotated around the 
y-axis so, that the user
+        // looks on the extruded faces. Front face and back face are 
orthogonal to the xy-plane. The
+        // rotation around the x-axis cannot be distinguished from an initial 
rotation of the shape
+        // outside 3D. Thus there exist no unique solution.
+        fX = 0.0;
+        fZ = std::atan2(aTotalMat.get(2, 1), aTotalMat.get(1, 1));
+    }
+    else
+    {
+        fX = std::atan2(-aTotalMat.get(1, 2) / cos(fY), aTotalMat.get(2, 2) / 
cos(fY));
+        fZ = std::atan2(aTotalMat.get(0, 1) / cos(fY), aTotalMat.get(0, 0) / 
cos(fY));
+    }
+}
+
+void Scene3DHelper::getAPIAnglesFrom3DProperties(
+    const oox::drawingml::Shape3DPropertiesPtr p3DProperties, const sal_Int32& 
rnMSOShapeRotation,
+    double& fX, double& fY, double& fZ)
+{
+    if (!p3DProperties)
+        return;
+
+    // on x-axis, unit 1/60000 deg
+    sal_Int32 nLatitude = 
(*p3DProperties).maCameraRotation.mnLatitude.value_or(0);
+    // on y-axis, unit 1/60000 deg
+    sal_Int32 nLongitude = 
(*p3DProperties).maCameraRotation.mnLongitude.value_or(0);
+    // on z-axis, unit 1/60000 deg
+    sal_Int32 nRevolution = 
(*p3DProperties).maCameraRotation.mnRevolution.value_or(0);
+
+    // Some projection types need special treatment:
+    if (29 <= mnPrstCameraIndex && mnPrstCameraIndex <= 37)
+    {
+        // legacyPerspective. MS Office does not use z-rotation but writes it 
to file. We need to
+        // ignore it. The preset cameras have no rotation.
+        nRevolution = 0;
+    }
+    else if (47 <= mnPrstCameraIndex && mnPrstCameraIndex <= 61)
+    {
+        // perspective. MS Office has a strange rendering behavior: If the 
shape rotation is not zero
+        // and the angle for rotation on x-axis (=latitude) is >90deg and 
<=270deg, then MSO renders
+        // the shape with an addition 180deg rotation on the z-axis. This 
happens only with user
+        // entered angles.
+        if (rnMSOShapeRotation != 0 && nLatitude > 5400000 && nLatitude <= 
16200000)
+            nRevolution += 10800000;
+    }
+
+    // In case attributes lat, lon and rev of the <rot> child element of the 
<scene3d> element in
+    // OOXML markup are given, they overwrite the values from the preset 
camera type. Otherwise the
+    // values from the preset camera are used. OOXML requires that all three 
attributes must exist at
+    // the same time. Thus it is enough to test one of them.
+    if (!(*p3DProperties).maCameraRotation.mnRevolution.has_value())
+    {
+        // The angles are given in 1/60000 deg in aPrstCameraValuesArray.
+        nLatitude = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleX;
+        nLongitude = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleY;
+        nRevolution = aPrstCameraValuesArray[mnPrstCameraIndex].mfRotateAngleZ;
+    }
+
+    // MS Office applies the shape rotation after the rotations from camera in 
case of non-legacy
+    // cameras, and before for legacy cameras. ODF specifies to first rotate 
the shape. Thus we need
+    // to add shape rotation to nRevolution in case of non-legacy cameras. The 
shape rotation has
+    // opposite orientation than camera z-rotation.
+    bool bIsLegacyCamera = 20 <= mnPrstCameraIndex && mnPrstCameraIndex <= 37;
+    if (!bIsLegacyCamera)
+        nRevolution -= rnMSOShapeRotation;
+
+    // Now calculate the angles for LO rotation order and orientation.
+    Scene3DHelper::getAPIAnglesFromOOXAngle(nLatitude, nLongitude, 
nRevolution, fX, fY, fZ);
+
+    if (bIsLegacyCamera)
+        fZ -= basegfx::deg2rad<60000>(rnMSOShapeRotation);
+}
+
+void Scene3DHelper::addRotateAngleToMap(oox::PropertyMap& rPropertyMap, const 
double fX,
+                                        const double fY)
+{
+    css::drawing::EnhancedCustomShapeParameterPair aAnglePair;
+    aAnglePair.First.Value <<= basegfx::rad2deg(fX);
+    aAnglePair.First.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    aAnglePair.Second.Value <<= basegfx::rad2deg(fY);
+    aAnglePair.Second.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    rPropertyMap.setAnyProperty(oox::PROP_RotateAngle, 
css::uno::Any(aAnglePair));
+}
+
+void Scene3DHelper::addExtrusionDepthToMap(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                           oox::PropertyMap& rPropertyMap)
+{
+    // Amount of extrusion and its position relative to the original shape 
face. This moves the
+    // shape inside the scene.
+    // The GetExtrusionDepth() method in EnhancedCustomShape3d.cxx expects 
type double for both.
+    sal_Int32 nDepthAmount = (*p3DProperties).mnExtrusionH.value_or(0); // 
unit EMU
+    double fDepthAmount = o3tl::convert(nDepthAmount, o3tl::Length::emu, 
o3tl::Length::mm100);
+    sal_Int32 nZPosition = (*p3DProperties).mnShapeZ.value_or(0); // unit EMU
+    double fZPosition = o3tl::convert(nZPosition, o3tl::Length::emu, 
o3tl::Length::mm100);
+    double fDepthRelPos = 0.0;
+    if (nDepthAmount == 0 && nZPosition != 0)
+    {
+        // We cannot express the position relative to the extrusion depth.
+        // Use an artifical, small depth of 1Hmm
+        fDepthRelPos = fZPosition;
+        fDepthAmount = 1.0; // unit Hmm
+    }
+    else if (nDepthAmount != 0)
+        fDepthRelPos = fZPosition / fDepthAmount;
+
+    css::drawing::EnhancedCustomShapeParameterPair aPair;
+    css::drawing::EnhancedCustomShapeParameter& rDepthAmount = aPair.First;
+    rDepthAmount.Value <<= fDepthAmount;
+    rDepthAmount.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    css::drawing::EnhancedCustomShapeParameter& rDepthFraction = aPair.Second;
+    rDepthFraction.Value <<= fDepthRelPos;
+    rDepthFraction.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    rPropertyMap.setProperty(oox::PROP_Depth, aPair);
+}
+
+void Scene3DHelper::addProjectionGeometryToMap(
+    const oox::drawingml::Shape3DPropertiesPtr p3DProperties, 
oox::PropertyMap& rPropertyMap,
+    const bool bIsParallel, const sal_Int32 rnMSOShapeRotation)
+{
+    // origin is needed for parallel and perspective as well
+    css::drawing::EnhancedCustomShapeParameterPair aOrigin;
+    aOrigin.First.Value <<= 
aPrstCameraValuesArray[mnPrstCameraIndex].mfOriginX;
+    aOrigin.First.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    aOrigin.Second.Value <<= 
aPrstCameraValuesArray[mnPrstCameraIndex].mfOriginY;
+    aOrigin.Second.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+    rPropertyMap.setProperty(oox::PROP_Origin, aOrigin);
+
+    if (bIsParallel)
+    {
+        // PARALLEL needs API property Skew.
+        // orthographicFront and isometric projections do not use skew. We 
write it nevertheless
+        // to prevent LO defaults. Zeros are contained in 
aPrstCameraValuesArray for these cases.
+        double fSkewAngle = 
aPrstCameraValuesArray[mnPrstCameraIndex].mfSkewAngle; // unit degree
+        double fSkewAmount = 
aPrstCameraValuesArray[mnPrstCameraIndex].mfSkewAmount;
+        // oblique projections (index [38..45]) need special treatment. MS 
Office rotates around the
+        // z-axis after the projection was created. Thus the rotation affects 
the skew direction. ODF
+        // rotates the shape before creating the projection. Thus we need to 
incorporate the shape
+        // rotation into the shew angle.
+        if (38 <= mnPrstCameraIndex && mnPrstCameraIndex <= 45)
+        {
+            fSkewAngle -= rnMSOShapeRotation / 60000.0;
+        }
+        css::drawing::EnhancedCustomShapeParameterPair aSkew;
+        aSkew.First.Value <<= fSkewAmount;
+        aSkew.First.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+        aSkew.Second.Value <<= fSkewAngle;
+        aSkew.Second.Type = 
css::drawing::EnhancedCustomShapeParameterType::NORMAL;
+        rPropertyMap.setProperty(oox::PROP_Skew, aSkew);
+    }
+    else
+    {
+        // PERSPECTIVE needs API property ViewPoint.
+        css::drawing::Position3D aViewPoint;
+
+        // x- and y-coordinate depend on preset camera type.
+        aViewPoint.PositionX = 
aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointX;
+        aViewPoint.PositionY = 
aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointY;
+
+        // The z-coordinate is determined bei a field of view angle in OOXML 
and by a
+        // distance in LibreOffice. MS Office users can change its value.
+        if ((*p3DProperties).mfFieldOfVision.has_value())
+        {
+            double fFov = (*p3DProperties).mfFieldOfVision.value();
+            fFov = std::clamp(fFov, 0.5, 179.5);
+            // 15976 = 25000 * tan(32.5�) as in legacy. Better ideas to 
calculate the distance are
+            // welcome.
+            aViewPoint.PositionZ = 15976.0 / tan(basegfx::deg2rad(fFov / 2.0));
+        }
+        else
+            aViewPoint.PositionZ = 
aPrstCameraValuesArray[mnPrstCameraIndex].mfViewPointZ;
+
+        rPropertyMap.setProperty(oox::PROP_ViewPoint, aViewPoint);
+    }
+    // ToDo: It is possible in OOXML to set a 3D-scene on a group. It is not 
clear yet how that can
+    // be mimicked in LO. In case of perspective projection, it produces a 
horizontal or vertical
+    // shift of the viewpoint in relation to the shapes of the group, for 
example.
+}
+
+bool Scene3DHelper::setExtrusionProperties(const 
oox::drawingml::Shape3DPropertiesPtr p3DProperties,
+                                           const sal_Int32& rnMSOShapeRotation,
+                                           oox::PropertyMap& rPropertyMap, 
double& rRotZ,
+                                           oox::drawingml::Color& 
rExtrusionColor)
+{
+    // We convert rnMSOShapeRotation, so that Shape::createAndInsert() can use 
rRotZ the same way in
+    // all cases.
+    rRotZ = basegfx::deg2rad<60000>(-rnMSOShapeRotation);
+
+    if (!p3DProperties || (p3DProperties && 
!(*p3DProperties).mnPreset.has_value()))
+        return false;
+
+    const sal_Int32 nCameraPrstID((*p3DProperties).mnPreset.value());
+    mnPrstCameraIndex
+        = 
getPrstCameraIndex(oox::drawingml::Generic3DProperties::getCameraPrstName(nCameraPrstID));
+    if (mnPrstCameraIndex < 0 or mnPrstCameraIndex >= nCameraPresetCount)
+        return false; // error in document. OOXML specifies a fixed set of 
preset camera types.
+
+    // We use extrusion, if there is a rotation around x-axis or y-axis,
+    // or if there is no such rotation but we have a perspective projection 
with true depth,
+    // or we have a parallel projection other than a 'front' type.
+    // In other cases the rendering as normal shape is better than any current 
extrusion.
+    double fX;
+    double fY;
+    Scene3DHelper::getAPIAnglesFrom3DProperties(p3DProperties, 
rnMSOShapeRotation, fX, fY, rRotZ);
+    sal_Int32 nDepthAmount = (*p3DProperties).mnExtrusionH.value_or(0);
+    bool bIsParallel = aPrstCameraValuesArray[mnPrstCameraIndex].mbIsParallel;
+    bool bIsParallelFrontType
+        = (nCameraPrstID == XML_legacyObliqueFront) || (nCameraPrstID == 
XML_orthographicFront);
+    bool bCreateExtrusion = (!basegfx::fTools::equalZero(fX) || 
!basegfx::fTools::equalZero(fY))
+                            || (!bIsParallel && nDepthAmount > 0)
+                            || (bIsParallel && !bIsParallelFrontType);
+
+    // Extrusion color is not handled as extrusion property but as shape 
property. Thus deliver it
+    // in any case, so that Shape::createAndInsert() knows about it.
+    rExtrusionColor = (*p3DProperties).maExtrusionColor;
+
+    if (!bCreateExtrusion)
+        return false;
+
+    // Create the extrusion properties in rPropertyMap so that they can be 
directly used.
+    // Turn extrusion on
+    rPropertyMap.setProperty(oox::PROP_Extrusion, true);
+
+    // Dummy value. Will be changed from evaluating the material properties, 
when implemented.
+    rPropertyMap.setProperty(oox::PROP_Diffusion, 100.0);
+
+    // Camera properties
+    css::drawing::ProjectionMode eProjectionMode = bIsParallel
+                                                       ? 
css::drawing::ProjectionMode_PARALLEL
+                                                       : 
css::drawing::ProjectionMode_PERSPECTIVE;
+    rPropertyMap.setProperty(oox::PROP_ProjectionMode, eProjectionMode);
+
+    Scene3DHelper::addRotateAngleToMap(rPropertyMap, fX, fY);
+
+    Scene3DHelper::addProjectionGeometryToMap(p3DProperties, rPropertyMap, 
bIsParallel,
+                                              rnMSOShapeRotation);
+
+    // Shape properties
+    Scene3DHelper::addExtrusionDepthToMap(p3DProperties, rPropertyMap);
+
+    // Extrusion color enabled?
+    rPropertyMap.setProperty(oox::PROP_Color, rExtrusionColor.isUsed());
+
+    return true;
+}
+
+} // end namespace oox
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/oox/source/drawingml/shape.cxx b/oox/source/drawingml/shape.cxx
index b4c50a91f730..8f3aedf3488a 100644
--- a/oox/source/drawingml/shape.cxx
+++ b/oox/source/drawingml/shape.cxx
@@ -28,6 +28,7 @@
 #include <drawingml/lineproperties.hxx>
 #include <drawingml/presetgeometrynames.hxx>
 #include <drawingml/shape3dproperties.hxx>
+#include <drawingml/scene3dhelper.hxx>
 #include "effectproperties.hxx"
 #include <oox/drawingml/shapepropertymap.hxx>
 #include <drawingml/textbody.hxx>
@@ -842,7 +843,7 @@ void 
lcl_doSpecialMSOWidthHeightToggle(basegfx::B2DHomMatrix& aTransformation)
     return;
 }
 
-void lcl_RotateAtCenter(basegfx::B2DHomMatrix& aTransformation,sal_Int32 
nMSORotationAngle)
+void lcl_RotateAtCenter(basegfx::B2DHomMatrix& aTransformation, sal_Int32 
nMSORotationAngle)
 {
     if (nMSORotationAngle == 0)
         return;
@@ -854,6 +855,15 @@ void lcl_RotateAtCenter(basegfx::B2DHomMatrix& 
aTransformation,sal_Int32 nMSORot
     aTransformation.translate(aCenter);
     return;
 }
+
+Degree100 lcl_MSORotateAngleToAPIAngle(const sal_Int32 nMSORotationAngle)
+{
+    // Converts a shape rotation angle from MSO to angle for API property 
RotateAngle
+    // from unit 1/60000 deg to unit 1/100 deg
+    Degree100 nAngle(nMSORotationAngle / 600);
+    // API RotateAngle has opposite direction than nMSORotationAngle, thus 
'minus'.
+    return NormAngle36000(-nAngle);
+}
 }
 
 Reference< XShape > const & Shape::createAndInsert(
@@ -937,7 +947,43 @@ Reference< XShape > const & Shape::createAndInsert(
 
     bool bIsCustomShape = (aServiceName == "com.sun.star.drawing.CustomShape" 
|| bIsCroppedGraphic);
     bool bIsConnectorShape = (aServiceName == 
"com.sun.star.drawing.ConnectorShape");
-    if(bIsCroppedGraphic)
+
+    // Look for 3D. Its z-rotation and extrusion color become shape 
properties. Do it early as
+    // graphics might use 3D too and in that case should be imported as custom 
shape as well.
+    double fShapeRotateInclCamera = 0.0; // unit rad; same orientation as 
shape property RotateAngle
+    Color aExtrusionColor;
+    Scene3DHelper aScene3DHelper;
+    bool bHas3DEffect = aScene3DHelper.setExtrusionProperties(
+        mp3DPropertiesPtr, mnRotation, 
getCustomShapeProperties()->getExtrusionPropertyMap(),
+        fShapeRotateInclCamera, aExtrusionColor);
+    // Currently the other places use unit 1/60000deg and MSO shape rotate 
orientation, thus convert.
+    sal_Int32 nShapeRotateInclCamera = 
-basegfx::rad2deg<60000>(fShapeRotateInclCamera);
+
+    bool bIs3DGraphic = aServiceName == 
"com.sun.star.drawing.GraphicObjectShape" && bHas3DEffect;
+    if (bIs3DGraphic)
+    {
+        // The parts which use bIs3DGraphic are commented out for now (3x) 
because the export does
+        // not re-create the image, and because the rendering of images with 
transparent parts is
+        // broken in extrusion mode (tdf#159515). In principle it works that 
way to get 3D-effects
+        // on images.
+        SAL_INFO("oox.drawingml",
+        "Shape::createAndInsert: image with 3D effect conversion disabled");
+    }
+    // bIsCustomShape |= bIs3DGraphic;
+
+    // The extrusion color does not belong to the extrusion properties but is 
secondary color in
+    // the style of the shape, FillColor2 in API.
+    if (aExtrusionColor.isUsed())
+    {
+        // FillColor2 is not yet transformed to ComplexColor.
+        ::Color aColor = 
aExtrusionColor.getColor(rFilterBase.getGraphicHelper());
+        maShapeProperties.setProperty(PROP_FillColor2, aColor);
+    }
+    // ToDo: MS Office 'automatic' color uses line color if it exists, LO uses 
fill color. We might
+    // need to change color here in case of 'automatic'.
+
+    // if (bIsCroppedGraphic || bIs3DGraphic), disabled for now, see comment 
#965
+    if (bIsCroppedGraphic)
     {
         aServiceName = "com.sun.star.drawing.CustomShape";
         mpGraphicPropertiesPtr->mbIsCustomShape = true;
@@ -1057,11 +1103,10 @@ Reference< XShape > const & Shape::createAndInsert(
     // The flip contained in aParentScale will affect orientation of object 
rotation angle.
     sal_Int16 nOrientation = ((aParentScale.getX() < 0) != 
(aParentScale.getY() < 0)) ? -1 : 1;
     // ToDo: Not sure about the restrictions given by bUseRotationTransform.
-    // Since LibreOffice doesn't have 3D camera options for 2D shapes, rotate 
the shape opposite of
-    // the camera Z axis rotation, in order to produce the same visual result 
from MSO
-    const sal_Int32 nCameraRotation = 
get3DProperties().maCameraRotation.mnRevolution.value_or(0);
-    if (bUseRotationTransform && (mnRotation != 0 || nCameraRotation != 0))
-        lcl_RotateAtCenter(aTransformation, nOrientation * (mnRotation - 
nCameraRotation));
+    if (bUseRotationTransform && nShapeRotateInclCamera != 0)
+    {
+        lcl_RotateAtCenter(aTransformation, nOrientation * 
nShapeRotateInclCamera);
+    }
 
     if (fParentRotate != 0.0)
         aTransformation.rotate(fParentRotate);
@@ -1319,8 +1364,9 @@ Reference< XShape > const & Shape::createAndInsert(
         // applying properties
         aShapeProps.assignUsed( getShapeProperties() );
         aShapeProps.assignUsed( maDefaultShapeProperties );
-        if(mnRotation != 0 && bIsCustomShape)
-            aShapeProps.setProperty( PROP_RotateAngle, sal_Int32( 
NormAngle36000( Degree100(mnRotation / -600) ) ));
+        if(nShapeRotateInclCamera != 0 && bIsCustomShape)
+            aShapeProps.setProperty(PROP_RotateAngle,
+                                    
sal_Int32(lcl_MSORotateAngleToAPIAngle(nShapeRotateInclCamera)));
         if( bIsEmbMedia ||
             bIsCustomShape ||
             aServiceName == "com.sun.star.drawing.GraphicObjectShape" ||
@@ -1341,7 +1387,8 @@ Reference< XShape > const & Shape::createAndInsert(
         FillProperties aFillProperties = getActualFillProperties(pTheme, 
&rShapeOrParentShapeFillProps);
         if (getFillProperties().moFillType.has_value() && 
getFillProperties().moFillType.value() == XML_grpFill)
             getFillProperties().assignUsed(aFillProperties);
-        if(!bIsCroppedGraphic)
+        // if(!bIsCroppedGraphic && !bIs3DGraphic), disabled for now, see 
comment #960
+        if (!bIsCroppedGraphic)
             aFillProperties.pushToPropMap(aShapeProps, rGraphicHelper, 
mnRotation, nFillPhClr,
                                           css::awt::Size(aShapeRectHmm.Width, 
aShapeRectHmm.Height),
                                           nFillPhClrTheme, mbFlipH, mbFlipV, 
bIsCustomShape);
@@ -1834,11 +1881,10 @@ Reference< XShape > const & Shape::createAndInsert(
 
         // tdf#133037: a bit hackish: force Shape to rotate in the opposite 
direction the camera would rotate
         PropertySet aPropertySet(mxShape);
-        if ( !bUseRotationTransform && (mnRotation != 0 || nCameraRotation != 
0) )
+        if ( !bUseRotationTransform && (nShapeRotateInclCamera !=0) )
         {
-            // use the same logic for rotation from VML exporter 
(SimpleShape::implConvertAndInsert at vmlshape.cxx)
-            Degree100 nAngle = NormAngle36000( Degree100((mnRotation - 
nCameraRotation) / -600) );
-            aPropertySet.setAnyProperty( PROP_RotateAngle, Any( sal_Int32( 
nAngle.get() ) ) );
+            Degree100 
nAngle(lcl_MSORotateAngleToAPIAngle(nShapeRotateInclCamera));
+            aPropertySet.setAnyProperty(PROP_RotateAngle, Any( 
sal_Int32(nAngle)));
             aPropertySet.setAnyProperty( PROP_HoriOrientPosition, Any( 
maPosition.X ) );
             aPropertySet.setAnyProperty( PROP_VertOrientPosition, Any( 
maPosition.Y ) );
         }
diff --git a/oox/source/token/properties.txt b/oox/source/token/properties.txt
index 339e648b5f5b..5a0e84708fe3 100644
--- a/oox/source/token/properties.txt
+++ b/oox/source/token/properties.txt
@@ -38,6 +38,7 @@ BottomBorder
 BottomBorderComplexColor
 BottomBorderDistance
 BottomMargin
+Brightness
 BuiltInUnit
 BulletChar
 BulletColor
@@ -141,9 +142,11 @@ DefaultState
 DefaultText
 DefaultValue
 DeletedLegendEntries
+Depth
 DiagonalBLTR
 DiagonalTLBR
 DialogLibraries
+Diffusion
 DisableComplexChartTypes
 DisableDataTableDialog
 DisplayLabels
@@ -168,6 +171,7 @@ ExternalDocLinks
 ExternalLinks
 ExtrapolateBackward
 ExtrapolateForward
+Extrusion
 FileFormat
 FillBackground
 FillBitmapMode
@@ -179,6 +183,7 @@ FillBitmapSizeX
 FillBitmapSizeY
 FillBitmap
 FillColor
+FillColor2
 FillColorTheme
 FillColorLumMod
 FillColorLumOff
@@ -195,6 +200,9 @@ FillUseSlideBackground
 Filter
 FilterCriteriaSource
 FilterOptions
+FirstLightDirection
+FirstLightHarsh
+FirstLightLevel
 FirstLineOffset
 FirstPageFooterContent
 FirstPageHeaderContent
@@ -321,6 +329,7 @@ LeftMargin
 LeftPageFooterContent
 LeftPageHeaderContent
 LegacyFragment
+LightFace
 LineCap
 LineColor
 LineComplexColor
@@ -348,6 +357,8 @@ MaxFieldCount
 MaxTextLen
 MediaType
 MediaURL
+Metal
+MetalType
 MinorTickmarks
 MirroredX
 MirroredY
@@ -370,10 +381,12 @@ NumberingIsNumber
 NumberingLevel
 NumberingRules
 NumberingType
+NumberOfLineSegments
 Offset
 OpCodeMap
 Opaque
 Orientation
+Origin
 OutputPosition
 Outline
 OverlapSequence
@@ -423,6 +436,7 @@ PrivateStream
 PrivateTempFileURL
 ProgressValueMax
 ProgressValueMin
+ProjectionMode
 Protected
 RadiusRangeMaximum
 RadiusRangeMinimum
@@ -460,6 +474,7 @@ RightPageHeaderContent
 Role
 RotateAngle
 RotateReference
+RotationCenter
 RotationHorizontal
 RotationVertical
 RowGrand
@@ -476,9 +491,13 @@ ScrollValue
 ScrollValueMax
 ScrollValueMin
 ScrollWidth
+SecondLightDirection
+SecondLightHarsh
+SecondLightLevel
 Segments
 SelectedItems
 SelectedPage
+ShadeMode
 Shadow
 ShadowAlignment
 ShadowColor
@@ -489,6 +508,7 @@ ShadowTransparence
 ShadowBlur
 ShadowXDistance
 ShadowYDistance
+Shininess
 Show
 ShowBorder
 ShowCharts
@@ -518,10 +538,12 @@ Size100thMM
 SizePixel
 SizeProtect
 SizeType
+Skew
 SkipDuplicates
 SortInfo
 Sound
 SoundOn
+Specularity
 Speed
 Spin
 SpinIncrement
@@ -614,6 +636,7 @@ VerticalAlign
 VerticalSplitMode
 VerticalSplitPositionTwips
 ViewBox
+ViewPoint
 Visible
 VisibleFlag
 VisibleSize

Reply via email to