[gimp/gimp-2-10] app, cursors: add GimpToolTransform3DGrid tool widget



commit 855eb0a1505d99ff262b657690237f9d362e7a0c
Author: Ell <ell_se yahoo com>
Date:   Mon Jan 6 16:39:56 2020 +0200

    app, cursors: add GimpToolTransform3DGrid tool widget
    
    Add a new GimpToolTransform3DGrid tool widget, subclassed from
    GimpToolTransformGrid, which can be used to perform 3D
    transformations.
    
    The widget can be in one of three modes:
    
      CAMERA - allows adjusting the primary vanishing point by moving a
      handle.
    
      MOVE   - allows moving the object through dragging.
    
      ROTATE - allows rotating the object through dragging.
    
    By default, controlling the transformation through dragging applies
    to the X and Y axes.  Holding Shift (or setting the "constrain-
    axis" property) restricts the motion to only one of the axes.
    
    For the MOVE and ROTATE mode, holding Ctrl (or setting the "z-axis"
    property) allows controlling the Z axis instead.
    
    For the same modes, holding Alt (or setting the "local-frame"
    property), applies the adjustments in the object's local frame of
    reference, instead of the display's global frame of reference.

 app/display/Makefile.am                 |    2 +
 app/display/display-enums.c             |   31 +
 app/display/display-enums.h             |   12 +
 app/display/gimptooltransform3dgrid.c   | 1110 +++++++++++++++++++++++++++++++
 app/display/gimptooltransform3dgrid.h   |   65 ++
 app/widgets/gimpcursor.c                |    1 +
 app/widgets/widgets-enums.h             |    1 +
 cursors/Makefile.am                     |    2 +
 cursors/gimp-tool-cursors-x2.xcf        |  Bin 167642 -> 172862 bytes
 cursors/gimp-tool-cursors.xcf           |  Bin 78349 -> 81587 bytes
 cursors/tool-transform-3d-camera-x2.png |  Bin 0 -> 396 bytes
 cursors/tool-transform-3d-camera.png    |  Bin 0 -> 333 bytes
 po/POTFILES.in                          |    1 +
 13 files changed, 1225 insertions(+)
---
diff --git a/app/display/Makefile.am b/app/display/Makefile.am
index f5585917bf..0f20ca7ec5 100644
--- a/app/display/Makefile.am
+++ b/app/display/Makefile.am
@@ -196,6 +196,8 @@ libappdisplay_a_sources = \
        gimptoolrotategrid.h                    \
        gimptoolsheargrid.c                     \
        gimptoolsheargrid.h                     \
+       gimptooltransform3dgrid.c               \
+       gimptooltransform3dgrid.h               \
        gimptooltransformgrid.c                 \
        gimptooltransformgrid.h                 \
        gimptoolwidget.c                        \
diff --git a/app/display/display-enums.c b/app/display/display-enums.c
index ec3d941791..47e94251be 100644
--- a/app/display/display-enums.c
+++ b/app/display/display-enums.c
@@ -389,6 +389,37 @@ gimp_rectangle_precision_get_type (void)
   return type;
 }
 
+GType
+gimp_transform_3d_mode_get_type (void)
+{
+  static const GEnumValue values[] =
+  {
+    { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", "camera" },
+    { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", "move" },
+    { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", "rotate" },
+    { 0, NULL, NULL }
+  };
+
+  static const GimpEnumDesc descs[] =
+  {
+    { GIMP_TRANSFORM_3D_MODE_CAMERA, "GIMP_TRANSFORM_3D_MODE_CAMERA", NULL },
+    { GIMP_TRANSFORM_3D_MODE_MOVE, "GIMP_TRANSFORM_3D_MODE_MOVE", NULL },
+    { GIMP_TRANSFORM_3D_MODE_ROTATE, "GIMP_TRANSFORM_3D_MODE_ROTATE", NULL },
+    { 0, NULL, NULL }
+  };
+
+  static GType type = 0;
+
+  if (G_UNLIKELY (! type))
+    {
+      type = g_enum_register_static ("GimpTransform3DMode", values);
+      gimp_type_set_translation_context (type, "transform3-dmode");
+      gimp_enum_set_value_descriptions (type, descs);
+    }
+
+  return type;
+}
+
 GType
 gimp_transform_function_get_type (void)
 {
diff --git a/app/display/display-enums.h b/app/display/display-enums.h
index 72456cb82a..b805952b35 100644
--- a/app/display/display-enums.h
+++ b/app/display/display-enums.h
@@ -171,6 +171,18 @@ typedef enum
 } GimpRectanglePrecision;
 
 
+#define GIMP_TYPE_TRANSFORM_3D_MODE (gimp_transform_3d_mode_get_type ())
+
+GType gimp_transform_3d_mode_get_type (void) G_GNUC_CONST;
+
+typedef enum /*< lowercase_name=gimp_transform_3d_mode >*/
+{
+  GIMP_TRANSFORM_3D_MODE_CAMERA,
+  GIMP_TRANSFORM_3D_MODE_MOVE,
+  GIMP_TRANSFORM_3D_MODE_ROTATE
+} GimpTransform3DMode;
+
+
 #define GIMP_TYPE_TRANSFORM_FUNCTION (gimp_transform_function_get_type ())
 
 GType gimp_transform_function_get_type (void) G_GNUC_CONST;
diff --git a/app/display/gimptooltransform3dgrid.c b/app/display/gimptooltransform3dgrid.c
new file mode 100644
index 0000000000..41a10b9012
--- /dev/null
+++ b/app/display/gimptooltransform3dgrid.c
@@ -0,0 +1,1110 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool3dtransformgrid.c
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "core/gimp-transform-3d-utils.h"
+#include "core/gimp-utils.h"
+
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-transform.h"
+#include "gimptooltransform3dgrid.h"
+
+#include "gimp-intl.h"
+
+
+#define CONSTRAINT_MIN_DIST   8.0
+#define PIXELS_PER_REVOLUTION 1000
+
+
+enum
+{
+  PROP_0,
+  PROP_MODE,
+  PROP_CONSTRAIN_AXIS,
+  PROP_Z_AXIS,
+  PROP_LOCAL_FRAME,
+  PROP_CAMERA_X,
+  PROP_CAMERA_Y,
+  PROP_CAMERA_Z,
+  PROP_OFFSET_X,
+  PROP_OFFSET_Y,
+  PROP_OFFSET_Z,
+  PROP_ROTATION_ORDER,
+  PROP_ANGLE_X,
+  PROP_ANGLE_Y,
+  PROP_ANGLE_Z,
+  PROP_PIVOT_3D_X,
+  PROP_PIVOT_3D_Y,
+  PROP_PIVOT_3D_Z
+};
+
+typedef enum
+{
+  AXIS_NONE,
+  AXIS_X,
+  AXIS_Y
+} Axis;
+
+struct _GimpToolTransform3DGridPrivate
+{
+  GimpTransform3DMode mode;
+
+  gboolean            constrain_axis;
+  gboolean            z_axis;
+  gboolean            local_frame;
+
+  gdouble             camera_x;
+  gdouble             camera_y;
+  gdouble             camera_z;
+
+  gdouble             offset_x;
+  gdouble             offset_y;
+  gdouble             offset_z;
+
+  gint                rotation_order;
+  gdouble             angle_x;
+  gdouble             angle_y;
+  gdouble             angle_z;
+
+  gdouble             pivot_x;
+  gdouble             pivot_y;
+  gdouble             pivot_z;
+
+  GimpTransformHandle handle;
+
+  gdouble             orig_x;
+  gdouble             orig_y;
+  gdouble             orig_offset_x;
+  gdouble             orig_offset_y;
+  gdouble             orig_offset_z;
+  GimpMatrix3         orig_transform;
+
+  gdouble             last_x;
+  gdouble             last_y;
+
+  Axis                constrained_axis;
+};
+
+
+/*  local function prototypes  */
+
+static void       gimp_tool_transform_3d_grid_constructed            (GObject                 *object);
+static void       gimp_tool_transform_3d_grid_set_property           (GObject                 *object,
+                                                                      guint                    property_id,
+                                                                      const GValue            *value,
+                                                                      GParamSpec              *pspec);
+static void       gimp_tool_transform_3d_grid_get_property           (GObject                 *object,
+                                                                      guint                    property_id,
+                                                                      GValue                  *value,
+                                                                      GParamSpec              *pspec);
+
+static gint       gimp_tool_transform_3d_grid_button_press           (GimpToolWidget          *widget,
+                                                                      const GimpCoords        *coords,
+                                                                      guint32                  time,
+                                                                      GdkModifierType          state,
+                                                                      GimpButtonPressType      press_type);
+static void       gimp_tool_transform_3d_grid_motion                 (GimpToolWidget          *widget,
+                                                                      const GimpCoords        *coords,
+                                                                      guint32                  time,
+                                                                      GdkModifierType          state);
+static void       gimp_tool_transform_3d_grid_hover                  (GimpToolWidget          *widget,
+                                                                      const GimpCoords        *coords,
+                                                                      GdkModifierType          state,
+                                                                      gboolean                 proximity);
+static void       gimp_tool_transform_3d_grid_hover_modifier         (GimpToolWidget          *widget,
+                                                                      GdkModifierType          key,
+                                                                      gboolean                 press,
+                                                                      GdkModifierType          state);
+static gboolean   gimp_tool_transform_3d_grid_get_cursor              (GimpToolWidget          *widget,
+                                                                      const GimpCoords        *coords,
+                                                                      GdkModifierType          state,
+                                                                      GimpCursorType          *cursor,
+                                                                      GimpToolCursorType      *tool_cursor,
+                                                                      GimpCursorModifier      *modifier);
+
+static void       gimp_tool_transform_3d_grid_set_mode               (GimpToolTransform3DGrid *grid,
+                                                                      GimpTransform3DMode      mode);
+static void       gimp_tool_transform_3d_grid_reset_motion           (GimpToolTransform3DGrid *grid);
+static gboolean   gimp_tool_transform_3d_grid_constrain              (GimpToolTransform3DGrid *grid,
+                                                                      gdouble                  x,
+                                                                      gdouble                  y,
+                                                                      gdouble                  ox,
+                                                                      gdouble                  oy,
+                                                                      gdouble                 *tx,
+                                                                      gdouble                 *ty);
+
+static gboolean   gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid,
+                                                                      gdouble                  x,
+                                                                      gdouble                  y);
+static gboolean   gimp_tool_transform_3d_grid_motion_move            (GimpToolTransform3DGrid *grid,
+                                                                      gdouble                  x,
+                                                                      gdouble                  y);
+static gboolean   gimp_tool_transform_3d_grid_motion_rotate          (GimpToolTransform3DGrid *grid,
+                                                                      gdouble                  x,
+                                                                      gdouble                  y);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolTransform3DGrid, gimp_tool_transform_3d_grid,
+                            GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_transform_3d_grid_parent_class
+
+
+static void
+gimp_tool_transform_3d_grid_class_init (GimpToolTransform3DGridClass *klass)
+{
+  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
+  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+  object_class->constructed     = gimp_tool_transform_3d_grid_constructed;
+  object_class->set_property    = gimp_tool_transform_3d_grid_set_property;
+  object_class->get_property    = gimp_tool_transform_3d_grid_get_property;
+
+  widget_class->button_press    = gimp_tool_transform_3d_grid_button_press;
+  widget_class->motion          = gimp_tool_transform_3d_grid_motion;
+  widget_class->hover           = gimp_tool_transform_3d_grid_hover;
+  widget_class->hover_modifier  = gimp_tool_transform_3d_grid_hover_modifier;
+  widget_class->get_cursor      = gimp_tool_transform_3d_grid_get_cursor;
+
+  g_object_class_install_property (object_class, PROP_MODE,
+                                   g_param_spec_enum ("mode",
+                                                      NULL, NULL,
+                                                      GIMP_TYPE_TRANSFORM_3D_MODE,
+                                                      GIMP_TRANSFORM_3D_MODE_CAMERA,
+                                                      GIMP_PARAM_READWRITE |
+                                                      G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_CONSTRAIN_AXIS,
+                                   g_param_spec_boolean ("constrain-axis",
+                                                         NULL, NULL,
+                                                         FALSE,
+                                                         GIMP_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_Z_AXIS,
+                                   g_param_spec_boolean ("z-axis",
+                                                         NULL, NULL,
+                                                         FALSE,
+                                                         GIMP_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_LOCAL_FRAME,
+                                   g_param_spec_boolean ("local-frame",
+                                                         NULL, NULL,
+                                                         FALSE,
+                                                         GIMP_PARAM_READWRITE |
+                                                         G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_CAMERA_X,
+                                   g_param_spec_double ("camera-x",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_CAMERA_Y,
+                                   g_param_spec_double ("camera-y",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_CAMERA_Z,
+                                   g_param_spec_double ("camera-z",
+                                                        NULL, NULL,
+                                                        -(1.0 / 0.0),
+                                                        1.0 / 0.0,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_OFFSET_X,
+                                   g_param_spec_double ("offset-x",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_OFFSET_Y,
+                                   g_param_spec_double ("offset-y",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_OFFSET_Z,
+                                   g_param_spec_double ("offset-z",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ROTATION_ORDER,
+                                   g_param_spec_int ("rotation-order",
+                                                     NULL, NULL,
+                                                     0, 6, 0,
+                                                     GIMP_PARAM_READWRITE |
+                                                     G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ANGLE_X,
+                                   g_param_spec_double ("angle-x",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ANGLE_Y,
+                                   g_param_spec_double ("angle-y",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_ANGLE_Z,
+                                   g_param_spec_double ("angle-z",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_PIVOT_3D_X,
+                                   g_param_spec_double ("pivot-3d-x",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_PIVOT_3D_Y,
+                                   g_param_spec_double ("pivot-3d-y",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+
+  g_object_class_install_property (object_class, PROP_PIVOT_3D_Z,
+                                   g_param_spec_double ("pivot-3d-z",
+                                                        NULL, NULL,
+                                                        -G_MAXDOUBLE,
+                                                        G_MAXDOUBLE,
+                                                        0.0,
+                                                        GIMP_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_transform_3d_grid_init (GimpToolTransform3DGrid *grid)
+{
+  grid->priv = gimp_tool_transform_3d_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_transform_3d_grid_constructed (GObject *object)
+{
+  G_OBJECT_CLASS (parent_class)->constructed (object);
+
+  g_object_set (object,
+                "clip-guides",         TRUE,
+                "dynamic-handle-size", FALSE,
+                NULL);
+}
+
+static void
+gimp_tool_transform_3d_grid_set_property (GObject      *object,
+                                          guint         property_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GimpToolTransform3DGrid        *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object);
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  switch (property_id)
+    {
+    case PROP_MODE:
+      gimp_tool_transform_3d_grid_set_mode (grid, g_value_get_enum (value));
+      break;
+
+    case PROP_CONSTRAIN_AXIS:
+      priv->constrain_axis  = g_value_get_boolean (value);
+      gimp_tool_transform_3d_grid_reset_motion (grid);
+      break;
+    case PROP_Z_AXIS:
+      priv->z_axis = g_value_get_boolean (value);
+      gimp_tool_transform_3d_grid_reset_motion (grid);
+      break;
+    case PROP_LOCAL_FRAME:
+      priv->local_frame = g_value_get_boolean (value);
+      gimp_tool_transform_3d_grid_reset_motion (grid);
+      break;
+
+    case PROP_CAMERA_X:
+      priv->camera_x = g_value_get_double (value);
+      g_object_set (grid,
+                    "pivot-x", priv->camera_x,
+                    NULL);
+      break;
+    case PROP_CAMERA_Y:
+      priv->camera_y = g_value_get_double (value);
+      g_object_set (grid,
+                    "pivot-y", priv->camera_y,
+                    NULL);
+      break;
+    case PROP_CAMERA_Z:
+      priv->camera_z = g_value_get_double (value);
+      break;
+
+    case PROP_OFFSET_X:
+      priv->offset_x = g_value_get_double (value);
+      break;
+    case PROP_OFFSET_Y:
+      priv->offset_y = g_value_get_double (value);
+      break;
+    case PROP_OFFSET_Z:
+      priv->offset_z = g_value_get_double (value);
+      break;
+
+    case PROP_ROTATION_ORDER:
+      priv->rotation_order = g_value_get_int (value);
+      break;
+    case PROP_ANGLE_X:
+      priv->angle_x = g_value_get_double (value);
+      break;
+    case PROP_ANGLE_Y:
+      priv->angle_y = g_value_get_double (value);
+      break;
+    case PROP_ANGLE_Z:
+      priv->angle_z = g_value_get_double (value);
+      break;
+
+    case PROP_PIVOT_3D_X:
+      priv->pivot_x = g_value_get_double (value);
+      break;
+    case PROP_PIVOT_3D_Y:
+      priv->pivot_y = g_value_get_double (value);
+      break;
+    case PROP_PIVOT_3D_Z:
+      priv->pivot_z = g_value_get_double (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gimp_tool_transform_3d_grid_get_property (GObject    *object,
+                                          guint       property_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GimpToolTransform3DGrid        *grid = GIMP_TOOL_TRANSFORM_3D_GRID (object);
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  switch (property_id)
+    {
+    case PROP_MODE:
+      g_value_set_enum (value, priv->mode);
+      break;
+
+    case PROP_CONSTRAIN_AXIS:
+      g_value_set_boolean (value, priv->constrain_axis);
+      break;
+    case PROP_Z_AXIS:
+      g_value_set_boolean (value, priv->z_axis);
+      break;
+    case PROP_LOCAL_FRAME:
+      g_value_set_boolean (value, priv->local_frame);
+      break;
+
+    case PROP_CAMERA_X:
+      g_value_set_double (value, priv->camera_x);
+      break;
+    case PROP_CAMERA_Y:
+      g_value_set_double (value, priv->camera_y);
+      break;
+    case PROP_CAMERA_Z:
+      g_value_set_double (value, priv->camera_z);
+      break;
+
+    case PROP_OFFSET_X:
+      g_value_set_double (value, priv->offset_x);
+      break;
+    case PROP_OFFSET_Y:
+      g_value_set_double (value, priv->offset_y);
+      break;
+    case PROP_OFFSET_Z:
+      g_value_set_double (value, priv->offset_z);
+      break;
+
+    case PROP_ROTATION_ORDER:
+      g_value_set_int (value, priv->rotation_order);
+      break;
+    case PROP_ANGLE_X:
+      g_value_set_double (value, priv->angle_x);
+      break;
+    case PROP_ANGLE_Y:
+      g_value_set_double (value, priv->angle_y);
+      break;
+    case PROP_ANGLE_Z:
+      g_value_set_double (value, priv->angle_z);
+      break;
+
+    case PROP_PIVOT_3D_X:
+      g_value_set_double (value, priv->pivot_x);
+      break;
+    case PROP_PIVOT_3D_Y:
+      g_value_set_double (value, priv->pivot_y);
+      break;
+    case PROP_PIVOT_3D_Z:
+      g_value_set_double (value, priv->pivot_z);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static gint
+gimp_tool_transform_3d_grid_button_press (GimpToolWidget      *widget,
+                                          const GimpCoords    *coords,
+                                          guint32              time,
+                                          GdkModifierType      state,
+                                          GimpButtonPressType  press_type)
+{
+  GimpToolTransform3DGrid        *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  priv->handle = GIMP_TOOL_WIDGET_CLASS (parent_class)->button_press (
+    widget, coords, time, state, press_type);
+
+  priv->orig_x           = coords->x;
+  priv->orig_y           = coords->y;
+  priv->orig_offset_x    = priv->offset_x;
+  priv->orig_offset_y    = priv->offset_y;
+  priv->orig_offset_z    = priv->offset_z;
+  priv->last_x           = coords->x;
+  priv->last_y           = coords->y;
+
+  gimp_tool_transform_3d_grid_reset_motion (grid);
+
+  return priv->handle;
+}
+
+void
+gimp_tool_transform_3d_grid_motion (GimpToolWidget   *widget,
+                                    const GimpCoords *coords,
+                                    guint32           time,
+                                    GdkModifierType   state)
+{
+  GimpToolTransform3DGrid        *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+  GimpMatrix3                     transform;
+  gboolean                        update = TRUE;
+
+  switch (priv->handle)
+    {
+    case GIMP_TRANSFORM_HANDLE_PIVOT:
+      update = gimp_tool_transform_3d_grid_motion_vanishing_point (
+        grid, coords->x, coords->y);
+      break;
+
+    case GIMP_TRANSFORM_HANDLE_CENTER:
+      update = gimp_tool_transform_3d_grid_motion_move (
+        grid, coords->x, coords->y);
+      break;
+
+    case GIMP_TRANSFORM_HANDLE_ROTATION:
+      update = gimp_tool_transform_3d_grid_motion_rotate (
+        grid, coords->x, coords->y);
+    break;
+
+    default:
+      g_return_if_reached ();
+    }
+
+  if (update)
+    {
+      gimp_transform_3d_matrix (&transform,
+
+                                priv->camera_x,
+                                priv->camera_y,
+                                priv->camera_z,
+
+                                priv->offset_x,
+                                priv->offset_y,
+                                priv->offset_z,
+
+                                priv->rotation_order,
+                                priv->angle_x,
+                                priv->angle_y,
+                                priv->angle_z,
+
+                                priv->pivot_x,
+                                priv->pivot_y,
+                                priv->pivot_z);
+
+      g_object_set (widget,
+                    "transform", &transform,
+                    NULL);
+    }
+}
+
+static void
+gimp_tool_transform_3d_grid_hover (GimpToolWidget   *widget,
+                                   const GimpCoords *coords,
+                                   GdkModifierType   state,
+                                   gboolean          proximity)
+{
+  GIMP_TOOL_WIDGET_CLASS (parent_class)->hover (widget,
+                                                coords, state, proximity);
+
+  if (proximity &&
+      gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) ==
+      GIMP_TRANSFORM_HANDLE_PIVOT)
+    {
+      gimp_tool_widget_set_status (widget,
+                                   _("Click-Drag to move the vanishing point"));
+    }
+}
+
+static void
+gimp_tool_transform_3d_grid_hover_modifier (GimpToolWidget  *widget,
+                                            GdkModifierType  key,
+                                            gboolean         press,
+                                            GdkModifierType  state)
+{
+  GimpToolTransform3DGrid        *grid = GIMP_TOOL_TRANSFORM_3D_GRID (widget);
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  GIMP_TOOL_WIDGET_CLASS (parent_class)->hover_modifier (widget,
+                                                         key, press, state);
+
+  priv->local_frame = (state & gimp_get_extend_selection_mask ()) != 0;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_get_cursor (GimpToolWidget     *widget,
+                                        const GimpCoords   *coords,
+                                        GdkModifierType     state,
+                                        GimpCursorType     *cursor,
+                                        GimpToolCursorType *tool_cursor,
+                                        GimpCursorModifier *modifier)
+{
+  if (! GIMP_TOOL_WIDGET_CLASS (parent_class)->get_cursor (widget,
+                                                           coords,
+                                                           state,
+                                                           cursor,
+                                                           tool_cursor,
+                                                           modifier))
+    {
+      return FALSE;
+    }
+
+  if (gimp_tool_transform_grid_get_handle (GIMP_TOOL_TRANSFORM_GRID (widget)) ==
+      GIMP_TRANSFORM_HANDLE_PIVOT)
+    {
+      *tool_cursor = GIMP_TOOL_CURSOR_TRANSFORM_3D_CAMERA;
+    }
+
+  return TRUE;
+}
+
+static void
+gimp_tool_transform_3d_grid_set_mode (GimpToolTransform3DGrid *grid,
+                                      GimpTransform3DMode      mode)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  priv->mode = mode;
+
+  switch (mode)
+    {
+    case GIMP_TRANSFORM_3D_MODE_CAMERA:
+      g_object_set (grid,
+                    "inside-function",  GIMP_TRANSFORM_FUNCTION_NONE,
+                    "outside-function", GIMP_TRANSFORM_FUNCTION_NONE,
+                    "use-pivot-handle", TRUE,
+                    NULL);
+      break;
+
+    case GIMP_TRANSFORM_3D_MODE_MOVE:
+      g_object_set (grid,
+                    "inside-function",  GIMP_TRANSFORM_FUNCTION_MOVE,
+                    "outside-function", GIMP_TRANSFORM_FUNCTION_MOVE,
+                    "use-pivot-handle", FALSE,
+                    NULL);
+      break;
+
+    case GIMP_TRANSFORM_3D_MODE_ROTATE:
+      g_object_set (grid,
+                    "inside-function",  GIMP_TRANSFORM_FUNCTION_ROTATE,
+                    "outside-function", GIMP_TRANSFORM_FUNCTION_ROTATE,
+                    "use-pivot-handle", FALSE,
+                    NULL);
+      break;
+    }
+}
+
+static void
+gimp_tool_transform_3d_grid_reset_motion (GimpToolTransform3DGrid *grid)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+  GimpMatrix3                    *transform;
+
+  priv->constrained_axis = AXIS_NONE;
+
+  g_object_get (grid,
+                "transform", &transform,
+                NULL);
+
+  priv->orig_transform = *transform;
+
+  g_free (transform);
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_constrain (GimpToolTransform3DGrid *grid,
+                                       gdouble                  x,
+                                       gdouble                  y,
+                                       gdouble                  ox,
+                                       gdouble                  oy,
+                                       gdouble                 *tx,
+                                       gdouble                 *ty)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+
+  if (! priv->constrain_axis)
+    return TRUE;
+
+  if (priv->constrained_axis == AXIS_NONE)
+    {
+      GimpDisplayShell *shell;
+      gdouble           x1, y1;
+      gdouble           x2, y2;
+
+      shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid));
+
+      gimp_display_shell_transform_xy_f (shell,
+                                         priv->last_x, priv->last_y,
+                                         &x1,          &y1);
+      gimp_display_shell_transform_xy_f (shell,
+                                         x,            y,
+                                         &x2,          &y2);
+
+      if (hypot (x2 - x1, y2 - y1) < CONSTRAINT_MIN_DIST)
+        return FALSE;
+
+      if (fabs (*tx - ox) >= fabs (*ty - oy))
+        priv->constrained_axis = AXIS_X;
+      else
+        priv->constrained_axis = AXIS_Y;
+    }
+
+  if (priv->constrained_axis == AXIS_X)
+    *ty = oy;
+  else
+    *tx = ox;
+
+  return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_vanishing_point (GimpToolTransform3DGrid *grid,
+                                                    gdouble                  x,
+                                                    gdouble                  y)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+  GimpCoords                      c    = {};
+  gdouble                         pivot_x;
+  gdouble                         pivot_y;
+
+  if (! gimp_tool_transform_3d_grid_constrain (grid,
+                                               x,            y,
+                                               priv->last_x, priv->last_y,
+                                               &x,           &y))
+    {
+      return FALSE;
+    }
+
+  c.x = x;
+  c.y = y;
+
+  GIMP_TOOL_WIDGET_CLASS (parent_class)->motion (GIMP_TOOL_WIDGET (grid),
+                                                 &c, 0, 0);
+
+  g_object_get (grid,
+                "pivot-x", &pivot_x,
+                "pivot-y", &pivot_y,
+                NULL);
+
+  g_object_set (grid,
+                "camera-x", pivot_x,
+                "camera-y", pivot_y,
+                NULL);
+
+  priv->last_x = c.x;
+  priv->last_y = c.y;
+
+  return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_move (GimpToolTransform3DGrid *grid,
+                                         gdouble                  x,
+                                         gdouble                  y)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+  GimpMatrix4                     matrix;
+
+  if (! priv->z_axis)
+    {
+      gdouble x1, y1, z1, w1;
+      gdouble x2, y2, z2, w2;
+
+      if (! priv->local_frame)
+        {
+          gimp_matrix4_identity (&matrix);
+        }
+      else
+        {
+          GimpMatrix3 transform_inv = priv->orig_transform;;
+
+          gimp_matrix3_invert (&transform_inv);
+
+          gimp_transform_3d_matrix3_to_matrix4 (&transform_inv, &matrix, 2);
+        }
+
+      w1 = gimp_matrix4_transform_point (&matrix,
+                                         priv->last_x, priv->last_y, 0.0,
+                                         &x1,          &y1,          &z1);
+      w2 = gimp_matrix4_transform_point (&matrix,
+                                         x,            y,            0.0,
+                                         &x2,          &y2,          &z2);
+
+      if (w1 <= 0.0)
+        return FALSE;
+
+      if (! gimp_tool_transform_3d_grid_constrain (grid,
+                                                   x,   y,
+                                                   x1,  y1,
+                                                   &x2, &y2))
+        {
+          return FALSE;
+        }
+
+      if (priv->local_frame)
+        {
+          gimp_matrix4_identity (&matrix);
+
+          gimp_transform_3d_matrix4_rotate_euler (&matrix,
+                                                  priv->rotation_order,
+                                                  priv->angle_x,
+                                                  priv->angle_y,
+                                                  priv->angle_z,
+                                                  0.0, 0.0, 0.0);
+
+          gimp_matrix4_transform_point (&matrix,
+                                        x1,  y1,  z1,
+                                        &x1, &y1, &z1);
+          gimp_matrix4_transform_point (&matrix,
+                                        x2,  y2,  z2,
+                                        &x2, &y2, &z2);
+        }
+
+      if (w2 > 0.0)
+        {
+          g_object_set (grid,
+                        "offset-x", priv->offset_x + (x2 - x1),
+                        "offset-y", priv->offset_y + (y2 - y1),
+                        "offset-z", priv->offset_z + (z2 - z1),
+                        NULL);
+
+          priv->last_x = x;
+          priv->last_y = y;
+        }
+      else
+        {
+          g_object_set (grid,
+                        "offset-x", priv->orig_offset_x,
+                        "offset-y", priv->orig_offset_y,
+                        "offset-z", priv->orig_offset_z,
+                        NULL);
+
+          priv->last_x = priv->orig_x;
+          priv->last_y = priv->orig_y;
+        }
+    }
+  else
+    {
+      GimpVector3 axis;
+      gdouble     amount;
+
+      if (! priv->local_frame)
+        {
+          axis.x = 0.0;
+          axis.y = 0.0;
+          axis.z = 1.0;
+        }
+      else
+        {
+          gimp_matrix4_identity (&matrix);
+
+          gimp_transform_3d_matrix4_rotate_euler (&matrix,
+                                                  priv->rotation_order,
+                                                  priv->angle_x,
+                                                  priv->angle_y,
+                                                  priv->angle_z,
+                                                  0.0, 0.0, 0.0);
+
+          axis.x = matrix.coeff[0][2];
+          axis.y = matrix.coeff[1][2];
+          axis.z = matrix.coeff[2][2];
+
+          if (axis.x < 0.0)
+            gimp_vector3_neg (&axis);
+        }
+
+      amount = x - priv->last_x;
+
+      g_object_set (grid,
+                    "offset-x", priv->offset_x + axis.x * amount,
+                    "offset-y", priv->offset_y + axis.y * amount,
+                    "offset-z", priv->offset_z + axis.z * amount,
+                    NULL);
+
+      priv->last_x = x;
+      priv->last_y = y;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gimp_tool_transform_3d_grid_motion_rotate (GimpToolTransform3DGrid *grid,
+                                           gdouble                  x,
+                                           gdouble                  y)
+{
+  GimpToolTransform3DGridPrivate *priv = grid->priv;
+  GimpDisplayShell               *shell;
+  GimpMatrix4                     matrix;
+  GimpMatrix2                     basis_inv;
+  GimpVector3                     omega;
+  gdouble                         z_sign;
+  gboolean                        local_frame;
+
+  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (grid));
+
+  local_frame = priv->local_frame && (priv->constrain_axis || priv->z_axis);
+
+  if (! local_frame)
+    {
+      gimp_matrix2_identity (&basis_inv);
+      z_sign = 1.0;
+    }
+  else
+    {
+      GimpVector2 o, u, v;
+
+      gimp_matrix4_identity (&matrix);
+
+      gimp_transform_3d_matrix4_rotate_euler (&matrix,
+                                              priv->rotation_order,
+                                              priv->angle_x,
+                                              priv->angle_y,
+                                              priv->angle_z,
+                                              0.0, 0.0, 0.0);
+
+      z_sign = matrix.coeff[2][2] >= 0.0 ? +1.0 : -1.0;
+
+      gimp_matrix3_transform_point (&priv->orig_transform,
+                                    priv->pivot_x, priv->pivot_y,
+                                    &o.x, &o.y);
+      gimp_matrix3_transform_point (&priv->orig_transform,
+                                    priv->pivot_x + 1.0, priv->pivot_y,
+                                    &u.x, &u.y);
+      gimp_matrix3_transform_point (&priv->orig_transform,
+                                    priv->pivot_x, priv->pivot_y + 1.0,
+                                    &v.x, &v.y);
+
+      gimp_vector2_sub (&u, &u, &o);
+      gimp_vector2_sub (&v, &v, &o);
+
+      gimp_vector2_normalize (&u);
+      gimp_vector2_normalize (&v);
+
+      basis_inv.coeff[0][0] = u.x;
+      basis_inv.coeff[1][0] = u.y;
+      basis_inv.coeff[0][1] = v.x;
+      basis_inv.coeff[1][1] = v.y;
+
+      gimp_matrix2_invert (&basis_inv);
+    }
+
+  if (! priv->z_axis)
+    {
+      GimpVector2 scale;
+      gdouble     norm;
+
+      gimp_matrix2_transform_point (&basis_inv,
+                                    -(y - priv->last_y),
+                                    x - priv->last_x,
+                                    &omega.x, &omega.y);
+
+      omega.z = 0.0;
+
+      if (! gimp_tool_transform_3d_grid_constrain (grid,
+                                                   x,        y,
+                                                   0.0,      0.0,
+                                                   &omega.x, &omega.y))
+        {
+          return FALSE;
+        }
+
+      norm = gimp_vector3_length (&omega);
+
+      if (norm > 0.0)
+        {
+          scale.x = shell->scale_x * omega.y / norm;
+          scale.y = shell->scale_y * omega.x / norm;
+
+          gimp_vector3_mul (&omega, gimp_vector2_length (&scale));
+          gimp_vector3_mul (&omega, 2.0 * G_PI / PIXELS_PER_REVOLUTION);
+        }
+    }
+  else
+    {
+      GimpVector2 o;
+      GimpVector2 v1 = {priv->last_x,  priv->last_y};
+      GimpVector2 v2 = {x,             y};
+
+      g_warn_if_fail (priv->pivot_z == 0.0);
+
+      gimp_matrix3_transform_point (&priv->orig_transform,
+                                    priv->pivot_x, priv->pivot_y,
+                                    &o.x,          &o.y);
+
+      gimp_vector2_sub (&v1, &v1, &o);
+      gimp_vector2_sub (&v2, &v2, &o);
+
+      gimp_vector2_normalize (&v1);
+      gimp_vector2_normalize (&v2);
+
+      omega.x = 0.0;
+      omega.y = 0.0;
+      omega.z = atan2 (gimp_vector2_cross_product (&v1, &v2).y,
+                       gimp_vector2_inner_product (&v1, &v2));
+
+      omega.z *= z_sign;
+    }
+
+  gimp_matrix4_identity (&matrix);
+
+  if (local_frame)
+    gimp_transform_3d_matrix4_rotate (&matrix, &omega);
+
+  gimp_transform_3d_matrix4_rotate_euler (&matrix,
+                                          priv->rotation_order,
+                                          priv->angle_x,
+                                          priv->angle_y,
+                                          priv->angle_z,
+                                          0.0, 0.0, 0.0);
+
+  if (! local_frame)
+    gimp_transform_3d_matrix4_rotate (&matrix, &omega);
+
+  gimp_transform_3d_matrix4_rotate_euler_decompose (&matrix,
+                                                    priv->rotation_order,
+                                                    &priv->angle_x,
+                                                    &priv->angle_y,
+                                                    &priv->angle_z);
+
+  priv->last_x = x;
+  priv->last_y = y;
+
+  g_object_set (grid,
+                "angle-x", priv->angle_x,
+                "angle-y", priv->angle_y,
+                "angle-z", priv->angle_z,
+                NULL);
+
+  return TRUE;
+}
+
+
+/*  public functions  */
+
+GimpToolWidget *
+gimp_tool_transform_3d_grid_new (GimpDisplayShell *shell,
+                                 gdouble           x1,
+                                 gdouble           y1,
+                                 gdouble           x2,
+                                 gdouble           y2,
+                                 gdouble           camera_x,
+                                 gdouble           camera_y,
+                                 gdouble           camera_z)
+{
+  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+  return g_object_new (GIMP_TYPE_TOOL_TRANSFORM_3D_GRID,
+                       "shell",      shell,
+                       "x1",         x1,
+                       "y1",         y1,
+                       "x2",         x2,
+                       "y2",         y2,
+                       "camera-x",   camera_x,
+                       "camera-y",   camera_y,
+                       "camera-z",   camera_z,
+                       "pivot-3d-x", (x1 + x2) / 2.0,
+                       "pivot-3d-y", (y1 + y2) / 2.0,
+                       "pivot-3d-z", 0.0,
+                       NULL);
+}
diff --git a/app/display/gimptooltransform3dgrid.h b/app/display/gimptooltransform3dgrid.h
new file mode 100644
index 0000000000..42ac3eaa49
--- /dev/null
+++ b/app/display/gimptooltransform3dgrid.h
@@ -0,0 +1,65 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptool3dtransformgrid.h
+ * Copyright (C) 2019 Ell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GIMP_TOOL_TRANSFORM_3D_GRID_H__
+#define __GIMP_TOOL_TRANSFORM_3D_GRID_H__
+
+
+#include "gimptooltransformgrid.h"
+
+
+#define GIMP_TYPE_TOOL_TRANSFORM_3D_GRID            (gimp_tool_transform_3d_grid_get_type ())
+#define GIMP_TOOL_TRANSFORM_3D_GRID(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGrid))
+#define GIMP_TOOL_TRANSFORM_3D_GRID_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), 
GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass))
+#define GIMP_IS_TOOL_TRANSFORM_3D_GRID(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GIMP_TYPE_TOOL_TRANSFORM_3D_GRID))
+#define GIMP_IS_TOOL_TRANSFORM_3D_GRID_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GIMP_TYPE_TOOL_TRANSFORM_3D_GRID))
+#define GIMP_TOOL_TRANSFORM_3D_GRID_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GIMP_TYPE_TOOL_TRANSFORM_3D_GRID, GimpToolTransform3DGridClass))
+
+
+typedef struct _GimpToolTransform3DGrid        GimpToolTransform3DGrid;
+typedef struct _GimpToolTransform3DGridPrivate GimpToolTransform3DGridPrivate;
+typedef struct _GimpToolTransform3DGridClass   GimpToolTransform3DGridClass;
+
+struct _GimpToolTransform3DGrid
+{
+  GimpToolTransformGrid     parent_instance;
+
+  GimpToolTransform3DGridPrivate *priv;
+};
+
+struct _GimpToolTransform3DGridClass
+{
+  GimpToolTransformGridClass  parent_class;
+};
+
+
+GType            gimp_tool_transform_3d_grid_get_type (void) G_GNUC_CONST;
+
+GimpToolWidget * gimp_tool_transform_3d_grid_new      (GimpDisplayShell    *shell,
+                                                       gdouble              x1,
+                                                       gdouble              y1,
+                                                       gdouble              x2,
+                                                       gdouble              y2,
+                                                       gdouble              camera_x,
+                                                       gdouble              camera_y,
+                                                       gdouble              camera_z);
+
+
+#endif /* __GIMP_TOOL_TRANSFORM_3D_GRID_H__ */
diff --git a/app/widgets/gimpcursor.c b/app/widgets/gimpcursor.c
index a48ce87c88..7509f48068 100644
--- a/app/widgets/gimpcursor.c
+++ b/app/widgets/gimpcursor.c
@@ -177,6 +177,7 @@ static GimpCursor gimp_tool_cursors[] =
   { "tool-rotate" },
   { "tool-shear" },
   { "tool-perspective" },
+  { "tool-transform-3d-camera" },
   { "tool-flip-horizontal" },
   { "tool-flip-vertical" },
   { "tool-text" },
diff --git a/app/widgets/widgets-enums.h b/app/widgets/widgets-enums.h
index 26788ca142..59b7ed4928 100644
--- a/app/widgets/widgets-enums.h
+++ b/app/widgets/widgets-enums.h
@@ -228,6 +228,7 @@ typedef enum  /*< skip >*/
   GIMP_TOOL_CURSOR_ROTATE,
   GIMP_TOOL_CURSOR_SHEAR,
   GIMP_TOOL_CURSOR_PERSPECTIVE,
+  GIMP_TOOL_CURSOR_TRANSFORM_3D_CAMERA,
   GIMP_TOOL_CURSOR_FLIP_HORIZONTAL,
   GIMP_TOOL_CURSOR_FLIP_VERTICAL,
   GIMP_TOOL_CURSOR_TEXT,
diff --git a/cursors/Makefile.am b/cursors/Makefile.am
index 307800e301..4c2c679ed2 100644
--- a/cursors/Makefile.am
+++ b/cursors/Makefile.am
@@ -151,6 +151,8 @@ CURSOR_IMAGES = \
        tool-smudge-x2.png                      \
        tool-text.png                           \
        tool-text-x2.png                        \
+       tool-transform-3d-camera.png            \
+       tool-transform-3d-camera-x2.png         \
        tool-warp.png                           \
        tool-warp-x2.png                        \
        tool-zoom.png                           \
diff --git a/cursors/gimp-tool-cursors-x2.xcf b/cursors/gimp-tool-cursors-x2.xcf
index ff0e27e383..32e1da5c24 100644
Binary files a/cursors/gimp-tool-cursors-x2.xcf and b/cursors/gimp-tool-cursors-x2.xcf differ
diff --git a/cursors/gimp-tool-cursors.xcf b/cursors/gimp-tool-cursors.xcf
index 5092f680ad..67aff4427e 100644
Binary files a/cursors/gimp-tool-cursors.xcf and b/cursors/gimp-tool-cursors.xcf differ
diff --git a/cursors/tool-transform-3d-camera-x2.png b/cursors/tool-transform-3d-camera-x2.png
new file mode 100644
index 0000000000..3a7765717e
Binary files /dev/null and b/cursors/tool-transform-3d-camera-x2.png differ
diff --git a/cursors/tool-transform-3d-camera.png b/cursors/tool-transform-3d-camera.png
new file mode 100644
index 0000000000..69f2ca8ca5
Binary files /dev/null and b/cursors/tool-transform-3d-camera.png differ
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6330d7e997..631a94ffb3 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -267,6 +267,7 @@ app/display/gimptoolline.c
 app/display/gimptoolpath.c
 app/display/gimptoolpolygon.c
 app/display/gimptoolrectangle.c
+app/display/gimptooltransform3dgrid.c
 app/display/gimptooltransformgrid.c
 
 app/file/file-open.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]