diff --git a/stuff/profiles/layouts/rooms/Default/menubar_template.xml b/stuff/profiles/layouts/rooms/Default/menubar_template.xml
index 730a3e1..d17cb34 100644
--- a/stuff/profiles/layouts/rooms/Default/menubar_template.xml
+++ b/stuff/profiles/layouts/rooms/Default/menubar_template.xml
@@ -33,6 +33,7 @@
MI_StopMotionExportImageSequence
MI_ExportTvpJson
MI_ExportXsheetPDF
+ MI_ExportCameraTrack
MI_PrintXsheet
diff --git a/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml b/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml
index 86d788e..b268cec 100644
--- a/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml
+++ b/stuff/profiles/layouts/rooms/StudioGhibli/room4_menubar.xml
@@ -11,6 +11,7 @@
MI_PrintXsheet
MI_Export
+ MI_ExportCameraTrack
MI_Quit
diff --git a/toonz/sources/toonz/CMakeLists.txt b/toonz/sources/toonz/CMakeLists.txt
index 6499001..770d6b7 100644
--- a/toonz/sources/toonz/CMakeLists.txt
+++ b/toonz/sources/toonz/CMakeLists.txt
@@ -129,6 +129,7 @@ set(MOC_HEADERS
custompanelmanager.h
custompaneleditorpopup.h
convertfolderpopup.h
+ exportcameratrackpopup.h
)
if(PLATFORM EQUAL 64)
@@ -382,6 +383,7 @@ set(SOURCES
custompanelmanager.cpp
custompaneleditorpopup.cpp
convertfolderpopup.cpp
+ exportcameratrackpopup.cpp
# Tracker file
dummyprocessor.cpp
metnum.cpp
diff --git a/toonz/sources/toonz/exportcameratrackpopup.cpp b/toonz/sources/toonz/exportcameratrackpopup.cpp
new file mode 100644
index 0000000..9f4610f
--- /dev/null
+++ b/toonz/sources/toonz/exportcameratrackpopup.cpp
@@ -0,0 +1,986 @@
+
+
+#include "exportcameratrackpopup.h"
+
+// Tnz6 includes
+#include "tapp.h"
+#include "mainwindow.h"
+#include "filebrowser.h"
+#include "menubarcommandids.h"
+//// TnzQt includes
+#include "toonzqt/colorfield.h"
+#include "toonzqt/filefield.h"
+#include "toonzqt/doublefield.h"
+//// TnzLib includes
+#include "toonz/txsheet.h"
+#include "toonz/tcamera.h"
+#include "toonz/txshlevel.h"
+#include "toonz/txshsimplelevel.h"
+#include "toonz/txshcell.h"
+#include "toonz/tstageobjecttree.h"
+#include "toonz/toonzscene.h"
+#include "toonz/txshleveltypes.h"
+#include "toonz/dpiscale.h"
+#include "toonz/tproject.h"
+#include "toonz/txsheethandle.h"
+#include "toonz/tscenehandle.h"
+#include "filebrowserpopup.h"
+//// TnzCore includes
+#include "trop.h"
+#include "tsystem.h"
+#include "tenv.h"
+#include "tropcm.h"
+#include "tpalette.h"
+//// Qt includes
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+TEnv::DoubleVar CameraTrackExportBgOpacity("CameraTrackExportBgOpacity", 0.5);
+TEnv::StringVar CameraTrackExportLineColor(
+ "CameraTrackExportLineColor", QColor(Qt::red).name().toStdString());
+TEnv::IntVar CameraTrackExportCamRectOnKeys("CameraTrackExportCamRectOnKeys",
+ 1);
+TEnv::IntVar CameraTrackExportCamRectOnTags("CameraTrackExportCamRectOnTags",
+ 0);
+TEnv::IntVar CameraTrackExportLineTL("CameraTrackExportLineTL", 0);
+TEnv::IntVar CameraTrackExportLineTR("CameraTrackExportLineTR", 0);
+TEnv::IntVar CameraTrackExportLineCenter("CameraTrackExportLineCenter", 1);
+TEnv::IntVar CameraTrackExportLineBL("CameraTrackExportLineBL", 0);
+TEnv::IntVar CameraTrackExportLineBR("CameraTrackExportLineBR", 0);
+TEnv::IntVar CameraTrackExportGraduationInterval(
+ "CameraTrackExportGraduationInterval", 1);
+TEnv::IntVar CameraTrackExportNumberAt("CameraTrackExportNumberAt",
+ (int)Qt::TopLeftCorner);
+TEnv::IntVar CameraTrackExportNumbersOnLine("CameraTrackExportNumbersOnLine",
+ 1);
+TEnv::StringVar CameraTrackExportFont("CameraTrackExportFont", "");
+TEnv::IntVar CameraTrackExportFontSize("CameraTrackExportFontSize", 30);
+
+namespace {
+
+void getCameraPlacement(TAffine& aff, TXsheet* xsh, double row,
+ const TStageObjectId& objId,
+ const TStageObjectId& cameraId) {
+ TStageObject* pegbar =
+ xsh->getStageObjectTree()->getStageObject(objId, false);
+ if (!pegbar) return;
+
+ TAffine objAff = pegbar->getPlacement(row);
+ double objZ = pegbar->getZ(row);
+ double noScaleZ = pegbar->getGlobalNoScaleZ();
+
+ TStageObject* camera = xsh->getStageObject(cameraId);
+ TAffine cameraAff = camera->getPlacement(row);
+ double cameraZ = camera->getZ(row);
+
+ bool isVisible = TStageObject::perspective(aff, cameraAff, cameraZ, objAff,
+ objZ, noScaleZ);
+
+ aff = aff.inv() * cameraAff;
+}
+
+// recursively find key frame
+bool isKey(int frame, TStageObject* obj, TXsheet* xsh) {
+ if (obj->isKeyframe(frame)) return true;
+ if (obj->getParent() != TStageObjectId::NoneId)
+ return isKey(frame, xsh->getStageObject(obj->getParent()), xsh);
+ return false;
+}
+
+void drawOutlinedText(QPainter& p, const QPointF& pos, const QString& str) {
+ QPainterPath path;
+ path.addText(pos, p.font(), str);
+ p.setPen(QPen(Qt::white, 3));
+ p.drawPath(path);
+ p.setPen(Qt::NoPen);
+ p.drawPath(path);
+}
+
+// {0,1,2,3,6,8,9,10} => "1-4,7,9-11"
+QString framesToString(const QList& frames) {
+ QString frameStr;
+ bool prevIsHyphen = false;
+ for (int i = 0; i < frames.size(); i++) {
+ int f = frames.at(i);
+ if (i == 0) {
+ frameStr = QString::number(f + 1);
+ continue;
+ }
+ if (i == frames.size() - 1) {
+ if (prevIsHyphen)
+ frameStr += QString::number(f + 1);
+ else
+ frameStr += ", " + QString::number(f + 1);
+ break;
+ }
+ if (frames.at(i - 1) == f - 1) {
+ if (frames.at(i + 1) == f + 1) {
+ if (prevIsHyphen)
+ continue;
+ else {
+ frameStr += "-";
+ prevIsHyphen = true;
+ }
+ } else {
+ if (prevIsHyphen) {
+ frameStr += QString::number(f + 1);
+ prevIsHyphen = false;
+ } else {
+ frameStr += ", " + QString::number(f + 1);
+ }
+ }
+ } else {
+ frameStr += ", " + QString::number(f + 1);
+ }
+ }
+ return frameStr;
+}
+} // namespace
+
+//-----------------------------------------------------------------------------
+CameraTrackPreviewPane::CameraTrackPreviewPane(QWidget* parent)
+ : QWidget(parent), m_scaleFactor(1.0) {}
+
+void CameraTrackPreviewPane::paintEvent(QPaintEvent* event) {
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+ QSize pmSize((double)m_pixmap.width() * m_scaleFactor,
+ (double)m_pixmap.height() * m_scaleFactor);
+ painter.drawPixmap(
+ 0, 0,
+ m_pixmap.scaled(pmSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+}
+
+void CameraTrackPreviewPane::setPixmap(QPixmap pm) {
+ m_pixmap = pm;
+ resize(pm.size() * m_scaleFactor);
+ update();
+}
+
+void CameraTrackPreviewPane::doZoom(double d_scale) {
+ m_scaleFactor += d_scale;
+ if (m_scaleFactor > 1.0)
+ m_scaleFactor = 1.0;
+ else if (m_scaleFactor < 0.1)
+ m_scaleFactor = 0.1;
+
+ resize(m_pixmap.size() * m_scaleFactor);
+ update();
+}
+
+void CameraTrackPreviewPane::fitScaleTo(QSize size) {
+ double tmp_scaleFactor =
+ std::min((double)size.width() / (double)m_pixmap.width(),
+ (double)size.height() / (double)m_pixmap.height());
+
+ m_scaleFactor = tmp_scaleFactor;
+ if (m_scaleFactor > 1.0)
+ m_scaleFactor = 1.0;
+ else if (m_scaleFactor < 0.1)
+ m_scaleFactor = 0.1;
+
+ resize(m_pixmap.size() * m_scaleFactor);
+ update();
+}
+
+//-----------------------------------------------------------------------------
+void CameraTrackPreviewArea::mousePressEvent(QMouseEvent* e) {
+ m_mousePos = e->pos();
+}
+
+void CameraTrackPreviewArea::mouseMoveEvent(QMouseEvent* e) {
+ QPoint d = m_mousePos - e->pos();
+ horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d.x());
+ verticalScrollBar()->setValue(verticalScrollBar()->value() + d.y());
+ m_mousePos = e->pos();
+}
+
+void CameraTrackPreviewArea::contextMenuEvent(QContextMenuEvent* event) {
+ QMenu* menu = new QMenu(this);
+ QAction* fitAction = menu->addAction(tr("Fit To Window"));
+ connect(fitAction, SIGNAL(triggered()), this, SLOT(fitToWindow()));
+ menu->exec(event->globalPos());
+}
+
+void CameraTrackPreviewArea::fitToWindow() {
+ dynamic_cast(widget())->fitScaleTo(rect().size());
+}
+
+void CameraTrackPreviewArea::wheelEvent(QWheelEvent* event) {
+ int delta = 0;
+ switch (event->source()) {
+ case Qt::MouseEventNotSynthesized: {
+ delta = event->angleDelta().y();
+ }
+ case Qt::MouseEventSynthesizedBySystem: {
+ QPoint numPixels = event->pixelDelta();
+ QPoint numDegrees = event->angleDelta() / 8;
+ if (!numPixels.isNull()) {
+ delta = event->pixelDelta().y();
+ } else if (!numDegrees.isNull()) {
+ QPoint numSteps = numDegrees / 15;
+ delta = numSteps.y();
+ }
+ break;
+ }
+
+ default: // Qt::MouseEventSynthesizedByQt,
+ // Qt::MouseEventSynthesizedByApplication
+ {
+ std::cout << "not supported event: Qt::MouseEventSynthesizedByQt, "
+ "Qt::MouseEventSynthesizedByApplication"
+ << std::endl;
+ break;
+ }
+
+ } // end switch
+
+ if (delta == 0) {
+ event->accept();
+ return;
+ }
+
+ dynamic_cast(widget())->doZoom((delta > 0) ? 0.1
+ : -0.1);
+
+ event->accept();
+}
+
+//********************************************************************************
+// ExportCameraTrackPopup implementation
+//********************************************************************************
+
+ExportCameraTrackPopup::ExportCameraTrackPopup()
+ : DVGui::Dialog(TApp::instance()->getMainWindow(), false, false,
+ "ExportCameraTrack") {
+ setWindowTitle(tr("Export Camera Track"));
+
+ m_previewPane = new CameraTrackPreviewPane(this);
+ m_previewArea = new CameraTrackPreviewArea(this);
+
+ m_targetColumnCombo = new QComboBox(this);
+ m_bgOpacityField = new DVGui::DoubleField(this);
+ m_lineColorFld = new DVGui::ColorField(this, false, TPixel32(255, 0, 0));
+
+ m_cameraRectOnKeysCB = new QCheckBox(tr("Draw On Keyframes"), this);
+ m_cameraRectOnTagsCB = new QCheckBox(tr("Draw On Navigation Tags"), this);
+ m_cameraRectFramesEdit = new QLineEdit(this);
+
+ m_lineTL_CB = new QCheckBox(tr("Top Left"), this);
+ m_lineTR_CB = new QCheckBox(tr("Top Right"), this);
+ m_lineCenter_CB = new QCheckBox(tr("Center"), this);
+ m_lineBL_CB = new QCheckBox(tr("Bottom Left"), this);
+ m_lineBR_CB = new QCheckBox(tr("Bottom Right"), this);
+ m_graduationIntervalCombo = new QComboBox(this);
+
+ m_numberAtCombo = new QComboBox(this);
+ m_numbersOnLineCB = new QCheckBox(tr("Draw Numbers On Track Line"), this);
+ m_fontCombo = new QFontComboBox(this);
+ m_fontSizeEdit = new DVGui::IntLineEdit(this, 30, 5, 300);
+
+ QPushButton* exportButton = new QPushButton(tr("Export"), this);
+ QPushButton* cancelButton = new QPushButton(tr("Cancel"), this);
+
+ //----------
+ m_previewArea->setWidget(m_previewPane);
+ m_previewArea->setAlignment(Qt::AlignCenter);
+ m_previewArea->setBackgroundRole(QPalette::Dark);
+ m_previewArea->setStyleSheet("background-color:gray;");
+
+ m_targetColumnCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+ m_bgOpacityField->setRange(0.0, 1.0);
+ m_bgOpacityField->setValue(0.5);
+
+ m_cameraRectFramesEdit->setValidator(
+ new QRegExpValidator(QRegExp("^(\\d+,)*\\d+$"), this));
+ m_cameraRectFramesEdit->setToolTip(
+ tr("Specify frame numbers where the camera rectangles will be drawn. "
+ "Separate numbers by comma \",\" ."));
+
+ m_numberAtCombo->addItem(tr("Top Left"), (int)Qt::TopLeftCorner);
+ m_numberAtCombo->addItem(tr("Top Right"), (int)Qt::TopRightCorner);
+ m_numberAtCombo->addItem(tr("Bottom Left"), (int)Qt::BottomLeftCorner);
+ m_numberAtCombo->addItem(tr("Bottom Right"), (int)Qt::BottomRightCorner);
+
+ m_graduationIntervalCombo->addItem(tr("None"), 0);
+ m_graduationIntervalCombo->addItem(tr("All frames"), 1);
+ m_graduationIntervalCombo->addItem(tr("Every 2 frames"), 2);
+ m_graduationIntervalCombo->addItem(tr("Every 3 frames"), 3);
+ m_graduationIntervalCombo->addItem(tr("Every 4 frames"), 4);
+ m_graduationIntervalCombo->addItem(tr("Every 5 frames"), 5);
+ m_graduationIntervalCombo->addItem(tr("Every 6 frames"), 6);
+ m_graduationIntervalCombo->addItem(tr("Every 8 frames"), 8);
+ m_graduationIntervalCombo->addItem(tr("Every 10 frames"), 10);
+ m_graduationIntervalCombo->addItem(tr("Every 12 frames"), 12);
+
+ exportButton->setFocusPolicy(Qt::NoFocus);
+ cancelButton->setFocusPolicy(Qt::NoFocus);
+ //----------
+ QHBoxLayout* mainLay = new QHBoxLayout();
+ mainLay->setMargin(0);
+ mainLay->setSpacing(5);
+ {
+ mainLay->addWidget(m_previewArea, 1);
+
+ QVBoxLayout* rightLay = new QVBoxLayout();
+ rightLay->setMargin(10);
+ rightLay->setSpacing(10);
+
+ QGridLayout* appearanceLay = new QGridLayout();
+ appearanceLay->setMargin(0);
+ appearanceLay->setHorizontalSpacing(5);
+ appearanceLay->setVerticalSpacing(10);
+ {
+ appearanceLay->addWidget(new QLabel(tr("Target Column:"), this), 0, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ appearanceLay->addWidget(m_targetColumnCombo, 0, 1, Qt::AlignLeft);
+
+ appearanceLay->addWidget(new QLabel(tr("Background:"), this), 1, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ appearanceLay->addWidget(m_bgOpacityField, 1, 1);
+
+ appearanceLay->addWidget(new QLabel(tr("Line Color:"), this), 2, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ appearanceLay->addWidget(m_lineColorFld, 2, 1, Qt::AlignLeft);
+ }
+ appearanceLay->setColumnStretch(1, 1);
+ rightLay->addLayout(appearanceLay, 0);
+
+ QGroupBox* cameraRectGB = new QGroupBox(tr("Camera Rectangles"), this);
+ QGridLayout* cameraRectLay = new QGridLayout();
+ cameraRectLay->setMargin(10);
+ cameraRectLay->setHorizontalSpacing(5);
+ cameraRectLay->setVerticalSpacing(10);
+ {
+ cameraRectLay->addWidget(m_cameraRectOnKeysCB, 0, 0, 1, 2, Qt::AlignLeft);
+ cameraRectLay->addWidget(m_cameraRectOnTagsCB, 1, 0, 1, 2, Qt::AlignLeft);
+
+ cameraRectLay->addWidget(new QLabel(tr("Specify Frames Manually:"), this),
+ 2, 0, Qt::AlignRight | Qt::AlignVCenter);
+ cameraRectLay->addWidget(m_cameraRectFramesEdit, 2, 1);
+ }
+ cameraRectLay->setColumnStretch(1, 1);
+ cameraRectGB->setLayout(cameraRectLay);
+ rightLay->addWidget(cameraRectGB, 0);
+
+ QGroupBox* trackLineGB = new QGroupBox(tr("Track Lines"), this);
+ QGridLayout* trackLineLay = new QGridLayout();
+ trackLineLay->setMargin(10);
+ trackLineLay->setHorizontalSpacing(10);
+ trackLineLay->setVerticalSpacing(10);
+ {
+ trackLineLay->addWidget(m_lineTL_CB, 0, 0);
+ trackLineLay->addWidget(m_lineTR_CB, 0, 1);
+ trackLineLay->addWidget(m_lineCenter_CB, 1, 0, Qt::AlignRight);
+ trackLineLay->addWidget(m_lineBL_CB, 2, 0);
+ trackLineLay->addWidget(m_lineBR_CB, 2, 1);
+ trackLineLay->addWidget(
+ new QLabel(tr("Graduation Marks Interval:"), this), 3, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ trackLineLay->addWidget(m_graduationIntervalCombo, 3, 1, Qt::AlignLeft);
+ }
+ trackLineLay->setColumnStretch(1, 1);
+ trackLineGB->setLayout(trackLineLay);
+ rightLay->addWidget(trackLineGB, 0);
+
+ QGroupBox* frameNumberGB = new QGroupBox(tr("Frame Numbers"), this);
+ QGridLayout* frameNumberLay = new QGridLayout();
+ frameNumberLay->setMargin(10);
+ frameNumberLay->setHorizontalSpacing(5);
+ frameNumberLay->setVerticalSpacing(10);
+ {
+ frameNumberLay->addWidget(new QLabel(tr("Camera Rect Corner:"), this), 0,
+ 0, Qt::AlignRight | Qt::AlignVCenter);
+ frameNumberLay->addWidget(m_numberAtCombo, 0, 1, Qt::AlignLeft);
+
+ frameNumberLay->addWidget(m_numbersOnLineCB, 1, 0, 1, 2);
+
+ frameNumberLay->addWidget(new QLabel(tr("Font Family:"), this), 2, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ frameNumberLay->addWidget(m_fontCombo, 2, 1, Qt::AlignLeft);
+
+ frameNumberLay->addWidget(new QLabel(tr("Font Size:"), this), 3, 0,
+ Qt::AlignRight | Qt::AlignVCenter);
+ frameNumberLay->addWidget(m_fontSizeEdit, 3, 1, Qt::AlignLeft);
+ }
+ frameNumberLay->setColumnStretch(1, 1);
+ frameNumberGB->setLayout(frameNumberLay);
+ rightLay->addWidget(frameNumberGB, 0);
+
+ rightLay->addStretch(1);
+
+ QHBoxLayout* buttonsLay = new QHBoxLayout();
+ buttonsLay->setMargin(5);
+ buttonsLay->setSpacing(20);
+ {
+ buttonsLay->addWidget(exportButton, 0);
+ buttonsLay->addWidget(cancelButton, 0);
+ }
+ rightLay->setAlignment(Qt::AlignCenter);
+ rightLay->addLayout(buttonsLay, 0);
+
+ mainLay->addLayout(rightLay, 0);
+ }
+ m_topLayout->addLayout(mainLay, 1);
+
+ //----------
+
+ loadSettings();
+
+ connect(m_targetColumnCombo, SIGNAL(activated(int)), this,
+ SLOT(updatePreview()));
+ connect(m_bgOpacityField, SIGNAL(valueEditedByHand()), this,
+ SLOT(updatePreview()));
+ connect(m_lineColorFld, SIGNAL(colorChanged(const TPixel32&, bool)), this,
+ SLOT(updatePreview()));
+ connect(m_cameraRectOnKeysCB, SIGNAL(clicked(bool)), this,
+ SLOT(updatePreview()));
+ connect(m_cameraRectOnTagsCB, SIGNAL(clicked(bool)), this,
+ SLOT(updatePreview()));
+ connect(m_cameraRectFramesEdit, SIGNAL(editingFinished()), this,
+ SLOT(updatePreview()));
+ connect(m_lineTL_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview()));
+ connect(m_lineTR_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview()));
+ connect(m_lineCenter_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview()));
+ connect(m_lineBL_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview()));
+ connect(m_lineBR_CB, SIGNAL(clicked(bool)), this, SLOT(updatePreview()));
+ connect(m_graduationIntervalCombo, SIGNAL(activated(int)), this,
+ SLOT(updatePreview()));
+
+ connect(m_numberAtCombo, SIGNAL(activated(int)), this, SLOT(updatePreview()));
+ connect(m_numbersOnLineCB, SIGNAL(clicked(bool)), this,
+ SLOT(updatePreview()));
+ connect(m_fontCombo, SIGNAL(currentFontChanged(const QFont&)), this,
+ SLOT(updatePreview()));
+ connect(m_fontSizeEdit, SIGNAL(editingFinished()), this,
+ SLOT(updatePreview()));
+
+ connect(cancelButton, SIGNAL(clicked()), this, SLOT(close()));
+ connect(exportButton, SIGNAL(clicked()), this, SLOT(onExport()));
+}
+
+//--------------------------------------------------------------
+
+// register settings to the user env file on close
+void ExportCameraTrackPopup::saveSettings() {
+ CameraTrackExportBgOpacity = m_bgOpacityField->getValue();
+ TPixel32 col = m_lineColorFld->getColor();
+ CameraTrackExportLineColor = QColor(col.r, col.g, col.b).name().toStdString();
+ CameraTrackExportCamRectOnKeys = (m_cameraRectOnKeysCB->isChecked()) ? 1 : 0;
+ CameraTrackExportCamRectOnTags = (m_cameraRectOnTagsCB->isChecked()) ? 1 : 0;
+ CameraTrackExportLineTL = (m_lineTL_CB->isChecked()) ? 1 : 0;
+ CameraTrackExportLineTR = (m_lineTR_CB->isChecked()) ? 1 : 0;
+ CameraTrackExportLineCenter = (m_lineCenter_CB->isChecked()) ? 1 : 0;
+ CameraTrackExportLineBL = (m_lineBL_CB->isChecked()) ? 1 : 0;
+ CameraTrackExportLineBR = (m_lineBR_CB->isChecked()) ? 1 : 0;
+ CameraTrackExportGraduationInterval =
+ m_graduationIntervalCombo->currentData().toInt();
+ CameraTrackExportNumberAt = m_numberAtCombo->currentData().toInt();
+ CameraTrackExportNumbersOnLine = (m_numbersOnLineCB->isChecked()) ? 1 : 0;
+ CameraTrackExportFont = m_fontCombo->currentFont().family().toStdString();
+ CameraTrackExportFontSize = m_fontSizeEdit->getValue();
+}
+
+//--------------------------------------------------------------
+//
+// load settings from the user env file on ctor
+void ExportCameraTrackPopup::loadSettings() {
+ m_bgOpacityField->setValue(CameraTrackExportBgOpacity);
+ QColor lineColor(QString::fromStdString(CameraTrackExportLineColor));
+ m_lineColorFld->setColor(
+ TPixel32(lineColor.red(), lineColor.green(), lineColor.blue()));
+ m_cameraRectOnKeysCB->setChecked(CameraTrackExportCamRectOnKeys != 0);
+ m_cameraRectOnTagsCB->setChecked(CameraTrackExportCamRectOnTags != 0);
+ m_lineTL_CB->setChecked(CameraTrackExportLineTL != 0);
+ m_lineTR_CB->setChecked(CameraTrackExportLineTR != 0);
+ m_lineCenter_CB->setChecked(CameraTrackExportLineCenter != 0);
+ m_lineBL_CB->setChecked(CameraTrackExportLineBL != 0);
+ m_lineBR_CB->setChecked(CameraTrackExportLineBR != 0);
+ m_graduationIntervalCombo->setCurrentIndex(
+ m_graduationIntervalCombo->findData(
+ (int)CameraTrackExportGraduationInterval));
+ m_numberAtCombo->setCurrentIndex(
+ m_numberAtCombo->findData((int)CameraTrackExportNumberAt));
+ m_numbersOnLineCB->setChecked(CameraTrackExportNumbersOnLine != 0);
+ QString tmplFont = QString::fromStdString(CameraTrackExportFont);
+ if (!tmplFont.isEmpty()) m_fontCombo->setCurrentFont(QFont(tmplFont));
+ m_fontSizeEdit->setValue(CameraTrackExportFontSize);
+}
+
+//--------------------------------------------------------------
+
+void ExportCameraTrackPopup::initialize() {
+ updateTargetColumnComboItems();
+ updatePreview();
+}
+
+//--------------------------------------------------------------
+
+void ExportCameraTrackPopup::updateTargetColumnComboItems() {
+ m_targetColumnCombo->clear();
+
+ ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene();
+ TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
+
+ for (int col = 0; col < xsh->getColumnCount(); col++) {
+ TXshLevelP level;
+ int r0, r1;
+ xsh->getCellRange(col, r0, r1);
+ if (r1 < 0) continue;
+ for (int r = r0; r <= r1; r++)
+ if (level = xsh->getCell(r, col).m_level) {
+ break;
+ }
+
+ if (!level) continue;
+
+ int type = level->getType();
+ if (!(type & RASTER_TYPE)) continue;
+
+ TXshSimpleLevelP sl = level->getSimpleLevel();
+ if (!sl) continue;
+
+ QString itemName = tr("Col %1 (%2)")
+ .arg(col + 1)
+ .arg(QString::fromStdWString(sl->getName()));
+
+ m_targetColumnCombo->addItem(itemName, col);
+ }
+}
+
+//--------------------------------------------------------------
+
+QImage ExportCameraTrackPopup::generateCameraTrackImg(
+ const ExportCameraTrackInfo& info, bool isPreview) {
+ ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene();
+ TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
+
+ // obtain target level
+ TXshLevelP level;
+ TFrameId fId;
+ int r0, r1;
+ xsh->getCellRange(info.columnId, r0, r1);
+ if (r1 < 0) return QImage();
+ for (int r = r0; r <= r1; r++)
+ if (level = xsh->getCell(r, info.columnId).m_level) {
+ fId = xsh->getCell(r, info.columnId).getFrameId();
+ break;
+ }
+ if (!level) return QImage();
+ int type = level->getType();
+ if (!(type & RASTER_TYPE)) return QImage();
+ TXshSimpleLevelP sl = level->getSimpleLevel();
+ if (!sl) return QImage();
+
+ // construct output image
+ TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
+ TCamera* camera = xsh->getStageObject(cameraId)->getCamera();
+ TDimension camDim = camera->getRes();
+
+ TDimension imgRes = sl->getResolution();
+
+ TImageP tImg = sl->getFullsampledFrame(fId, ImageManager::dontPutInCache);
+ TRaster32P imgRas(tImg->raster()->getSize());
+ TRaster32P src32 = tImg->raster();
+ TRasterCM32P srcCM = tImg->raster();
+ if (src32)
+ imgRas = src32;
+ else if (srcCM)
+ TRop::convert(imgRas, srcCM, tImg->getPalette(), TRect());
+ else
+ TRop::convert(imgRas, tImg->raster());
+
+ QImage colImg(imgRas->getRawData(), imgRes.lx, imgRes.ly,
+ QImage::Format_ARGB32_Premultiplied);
+
+ QImage img = colImg.mirrored(false, true);
+
+ TPointD imgDpi = sl->getImageDpi();
+ if (imgDpi != TPointD()) {
+ img.setDotsPerMeterX((int)std::round(imgDpi.x / 0.0254));
+ img.setDotsPerMeterY((int)std::round(imgDpi.y / 0.0254));
+ }
+
+ // draw
+
+ enum CornerId {
+ TopLeft = Qt::TopLeftCorner,
+ TopRight = Qt::TopRightCorner,
+ BottomLeft = Qt::BottomLeftCorner,
+ BottomRight = Qt::BottomRightCorner,
+ Center = Qt::BottomRightCorner + 1
+ };
+
+ QMap trackPaths; // [ CornerId, Path data ]
+ QList>
+ cornerPointsTrack; // [ CornerId, Position ] for each frame
+
+ if (info.lineTL) trackPaths.insert((int)TopLeft, QPainterPath());
+ if (info.lineTR) trackPaths.insert((int)TopRight, QPainterPath());
+ if (info.lineCenter) trackPaths.insert((int)Center, QPainterPath());
+ if (info.lineBL) trackPaths.insert((int)BottomLeft, QPainterPath());
+ if (info.lineBR) trackPaths.insert((int)BottomRight, QPainterPath());
+
+ TAffine aff;
+ TAffine dpiAffInv = getDpiAffine(sl.getPointer(), fId, true).inv();
+ TAffine camDpiAff = getDpiAffine(camera);
+
+ TStageObjectId colId = TStageObjectId::ColumnId(info.columnId);
+
+ QMap camCorners = {
+ {(int)TopLeft, TPointD(-camDim.lx / 2, camDim.ly / 2)},
+ {(int)TopRight, TPointD(camDim.lx / 2, camDim.ly / 2)},
+ {(int)BottomLeft, TPointD(-camDim.lx / 2, -camDim.ly / 2)},
+ {(int)BottomRight, TPointD(camDim.lx / 2, -camDim.ly / 2)},
+ {(int)Center, TPointD()}};
+
+ for (int f = 0; f < scene->getFrameCount(); f++) {
+ getCameraPlacement(aff, xsh, (double)f, colId, cameraId);
+ TAffine affTmp = dpiAffInv * aff * camDpiAff;
+ // corner points
+ QMap cornerPoints;
+ for (int c = TopLeft; c <= Center; c++) {
+ TPointD p = affTmp * camCorners[c];
+ cornerPoints.insert(c, QPointF(p.x, -p.y));
+ }
+ cornerPointsTrack.append(cornerPoints);
+
+ if (trackPaths.isEmpty()) continue;
+
+ // track paths will plot every 0.1 frames
+ for (int df = 0; df < 10; df++) {
+ double tmpF = (double)f + (double)df * 0.1;
+ getCameraPlacement(aff, xsh, (double)tmpF, colId, cameraId);
+ affTmp = dpiAffInv * aff * camDpiAff;
+
+ for (int c = TopLeft; c <= Center; c++) {
+ if (!trackPaths.contains(c)) continue;
+
+ TPointD p = affTmp * camCorners[c];
+ if (f == 0 && df == 0)
+ trackPaths[c].moveTo(QPointF(p.x, -p.y));
+ else
+ trackPaths[c].lineTo(QPointF(p.x, -p.y));
+ }
+
+ if (f == scene->getFrameCount() - 1) break;
+ }
+ }
+
+ QPainter p(&img);
+ p.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
+
+ p.setBrush(QColor(255, 255, 255, 255. * (1. - info.bgOpacity)));
+ p.setPen(Qt::NoPen);
+ p.drawRect(0, 0, imgRes.lx, imgRes.ly);
+ p.translate(imgRes.lx / 2.0, imgRes.ly / 2.0);
+
+ // camera rect
+ QSet rectFrames = info.cameraRectFrames;
+ if (info.cameraRectOnKeys || info.cameraRectOnTags) {
+ // check keyframes
+ for (int f = 0; f < scene->getFrameCount(); f++) {
+ if (rectFrames.contains(f)) continue;
+
+ if (info.cameraRectOnKeys &&
+ (isKey(f, xsh->getStageObject(cameraId), xsh) ||
+ isKey(f, xsh->getStageObject(colId), xsh)))
+ rectFrames.insert(f);
+
+ else if (info.cameraRectOnTags && xsh->isFrameTagged(f))
+ rectFrames.insert(f);
+ }
+ }
+
+ struct cameraRectData {
+ QPolygonF polygon;
+ QVector centerCrossPoints;
+ QPointF textPos;
+ QVector2D offsetVec;
+ QList frames;
+ };
+
+ QList
+ camRectDataList; // gather frames with the same camera rectangle
+
+ for (auto rectFrame : rectFrames) {
+ if (rectFrame < 0 || cornerPointsTrack.size() <= rectFrame) continue;
+ QMap cornerPoints = cornerPointsTrack.at(rectFrame);
+ QPolygonF camRectPolygon({cornerPoints[TopLeft], cornerPoints[TopRight],
+ cornerPoints[BottomRight],
+ cornerPoints[BottomLeft]});
+ QVector centerCrossPoints = {
+ cornerPoints[TopLeft] * 0.51 + cornerPoints[BottomRight] * 0.49,
+ cornerPoints[TopLeft] * 0.49 + cornerPoints[BottomRight] * 0.51,
+ cornerPoints[TopRight] * 0.51 + cornerPoints[BottomLeft] * 0.49,
+ cornerPoints[TopRight] * 0.49 + cornerPoints[BottomLeft] * 0.51};
+ QPointF textPos = cornerPoints[(int)info.numberAt];
+ int oppositeId;
+ switch ((int)info.numberAt) {
+ case TopLeft:
+ oppositeId = TopRight;
+ break;
+ case TopRight:
+ oppositeId = TopLeft;
+ break;
+ case BottomLeft:
+ oppositeId = BottomRight;
+ break;
+ case BottomRight:
+ oppositeId = BottomLeft;
+ break;
+ }
+ QVector2D offsetVec =
+ QVector2D(textPos - cornerPoints[oppositeId]).normalized();
+
+ bool found = false;
+ for (int i = 0; i < camRectDataList.size(); i++)
+ if (camRectDataList[i].polygon == camRectPolygon) {
+ found = true;
+ camRectDataList[i].frames.append(rectFrame);
+ break;
+ }
+ if (!found)
+ camRectDataList.append(
+ {camRectPolygon, centerCrossPoints, textPos, offsetVec, {rectFrame}});
+ }
+
+ p.setFont(info.font);
+
+ for (auto& data : camRectDataList) {
+ p.setPen(QPen(info.lineColor, 2));
+ p.setBrush(Qt::NoBrush);
+ p.drawPolygon(data.polygon);
+
+ // draw cross mark at center of the frame
+ bool drawCross = true;
+ // if the graduation mark is at the camera center, do not draw the cross
+ if (info.lineCenter && info.graduationInterval > 0) {
+ for (auto frame : data.frames)
+ if (frame % info.graduationInterval == 0) {
+ drawCross = false;
+ break;
+ }
+ }
+ if (drawCross) {
+ p.setPen(QPen(info.lineColor, 1));
+ p.drawLines(data.centerCrossPoints);
+ }
+
+ // generate frame number string
+ std::sort(data.frames.begin(), data.frames.end());
+ QString frameStr = framesToString(data.frames);
+
+ // draw frame number string
+ QFontMetricsF fm(info.font);
+ QRectF textRect = fm.boundingRect(frameStr).adjusted(-5, 0, 5, 0);
+ QPointF textPos = data.textPos + QPointF(5, 0);
+ textRect.translate(textPos);
+
+ while (data.polygon.intersects(textRect)) {
+ textRect.translate(data.offsetVec.toPointF());
+ textPos += QPointF(data.offsetVec.toPointF());
+ }
+
+ p.setBrush(info.lineColor);
+ drawOutlinedText(p, textPos, frameStr);
+ }
+
+ QFont smallFont(info.font);
+ smallFont.setPixelSize(info.font.pixelSize() * 2 / 3);
+ p.setFont(smallFont);
+
+ // track lines
+ QMap::const_iterator itr = trackPaths.constBegin();
+ while (itr != trackPaths.constEnd()) {
+ if (info.lineCenter && itr.key() != Center)
+ p.setPen(QPen(info.lineColor, 1, Qt::DashLine));
+ else
+ p.setPen(QPen(info.lineColor, 1));
+ p.setBrush(Qt::NoBrush);
+
+ p.drawPath(itr.value());
+
+ if (info.graduationInterval == 0 ||
+ (info.lineCenter && itr.key() != Center)) {
+ ++itr;
+ continue;
+ }
+
+ // draw graduation
+ QList>> graduations;
+
+ for (int f = 0; f < scene->getFrameCount(); f++) {
+ if (f % info.graduationInterval != 0) continue;
+
+ QPointF gPos = cornerPointsTrack[f].value(itr.key());
+ QPointF prev =
+ (f == 0) ? gPos : cornerPointsTrack[f - 1].value(itr.key());
+ QPointF next = (f == scene->getFrameCount() - 1)
+ ? gPos
+ : cornerPointsTrack[f + 1].value(itr.key());
+
+ QPointF graduationVec =
+ QVector2D(next - prev).normalized().toPointF() * 5.0;
+ graduationVec = QPointF(-graduationVec.y(), graduationVec.x());
+
+ p.drawLine(gPos - graduationVec, gPos + graduationVec);
+
+ // draw frame
+ if (!info.numbersOnLine) continue;
+ if (itr.key() != Center && rectFrames.contains(f)) continue;
+
+ bool found = false;
+
+ for (auto& g : graduations) {
+ if (g.first == gPos) {
+ found = true;
+ g.second.append(f);
+ break;
+ }
+ }
+ if (!found) graduations.append(QPair>(gPos, {f}));
+ }
+
+ for (auto& g : graduations) {
+ std::sort(g.second.begin(), g.second.end());
+ QString frameStr = framesToString(g.second);
+ QFontMetricsF fm(smallFont);
+ QRectF textRect = fm.boundingRect(frameStr).adjusted(-5, 0, 5, 0);
+ QPointF pos = g.first + QPointF(5, 0);
+ if (info.numberAt == Qt::TopLeftCorner ||
+ info.numberAt == Qt::BottomLeftCorner)
+ pos += QPointF(-textRect.width(), 0);
+
+ p.setBrush(info.lineColor);
+ drawOutlinedText(p, pos, frameStr);
+ }
+
+ ++itr;
+ }
+
+ return img;
+}
+
+//--------------------------------------------------------------
+
+void ExportCameraTrackPopup::getInfoFromUI(ExportCameraTrackInfo& info) {
+ // target column
+ if (m_targetColumnCombo->count() == 0) return;
+ info.columnId = m_targetColumnCombo->currentData().toInt();
+ // appearance settimgs
+ info.bgOpacity = m_bgOpacityField->getValue();
+ TPixel32 lineTCol = m_lineColorFld->getColor();
+ info.lineColor = QColor((int)lineTCol.r, (int)lineTCol.g, (int)lineTCol.b);
+ // camera rect settings
+ info.cameraRectOnKeys = m_cameraRectOnKeysCB->isChecked();
+ info.cameraRectOnTags = m_cameraRectOnTagsCB->isChecked();
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QStringList framesStrList =
+ m_cameraRectFramesEdit->text().split(",", Qt::SkipEmptyParts);
+#else
+ QStringList framesStrList =
+ m_cameraRectFramesEdit->text().split(",", QString::SkipEmptyParts);
+#endif
+ for (auto fStr : framesStrList) {
+ bool ok;
+ int f = fStr.toInt(&ok);
+ if (ok) info.cameraRectFrames.insert(f - 1);
+ }
+ // track line settings
+ info.lineTL = m_lineTL_CB->isChecked();
+ info.lineTR = m_lineTR_CB->isChecked();
+ info.lineCenter = m_lineCenter_CB->isChecked();
+ info.lineBL = m_lineBL_CB->isChecked();
+ info.lineBR = m_lineBR_CB->isChecked();
+ info.graduationInterval = m_graduationIntervalCombo->currentData().toInt();
+ // frame number settings
+ info.numberAt = (Qt::Corner)(m_numberAtCombo->currentData().toInt());
+ info.numbersOnLine = m_numbersOnLineCB->isChecked();
+ ;
+ info.font = m_fontCombo->currentFont();
+ info.font.setPixelSize(m_fontSizeEdit->getValue());
+}
+
+//--------------------------------------------------------------
+
+void ExportCameraTrackPopup::updatePreview() {
+ ExportCameraTrackInfo info;
+ getInfoFromUI(info);
+ if (info.columnId == -1) return;
+
+ QImage img = generateCameraTrackImg(info, true);
+
+ m_previewPane->setPixmap(QPixmap::fromImage(img));
+}
+
+//--------------------------------------------------------------
+
+void ExportCameraTrackPopup::onExport() {
+ ExportCameraTrackInfo info;
+ getInfoFromUI(info);
+ if (info.columnId == -1) return;
+
+ QImage img = generateCameraTrackImg(info, false);
+
+ ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene();
+
+ static GenericSaveFilePopup* savePopup = 0;
+ if (!savePopup) {
+ savePopup =
+ new GenericSaveFilePopup(QObject::tr("Export Camera Track Image"));
+ savePopup->setFilterTypes({"jpg", "jpeg", "bmp", "png", "tif"});
+ }
+
+ if (!scene->isUntitled())
+ savePopup->setFolder(scene->getScenePath().getParentDir());
+ else
+ savePopup->setFolder(
+ TProjectManager::instance()->getCurrentProject()->getScenesPath());
+
+ TXsheet* xsh = TApp::instance()->getCurrentXsheet()->getXsheet();
+ TStageObjectId cameraId = xsh->getStageObjectTree()->getCurrentCameraId();
+ std::string cameraName = xsh->getStageObject(cameraId)->getName();
+ savePopup->setFilename(TFilePath(cameraName + ".tif"));
+
+ TFilePath fp = savePopup->getPath();
+ if (fp.isEmpty()) return;
+
+ std::string type = fp.getType();
+ if (type == "")
+ fp = fp.withType("tif");
+ else if (type != "jpg" && type != "jpeg" && type != "bmp" && type != "png" &&
+ type != "tif") {
+ DVGui::MsgBoxInPopup(DVGui::WARNING,
+ tr("Please specify one of the following file formats; "
+ "jpg, jpeg, bmp, png, and tif"));
+ return;
+ }
+
+ img.save(fp.getQString());
+}
+
+//********************************************************************************
+// Export Camera Track Command instantiation
+//********************************************************************************
+
+OpenPopupCommandHandler ExportCameraTrackPopupCommand(
+ MI_ExportCameraTrack);
diff --git a/toonz/sources/toonz/exportcameratrackpopup.h b/toonz/sources/toonz/exportcameratrackpopup.h
new file mode 100644
index 0000000..fe2a4d8
--- /dev/null
+++ b/toonz/sources/toonz/exportcameratrackpopup.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#ifndef EXPORTCAMERATRACKPOPUP_H
+#define EXPORTCAMERATRACKPOPUP_H
+
+// Tnz6 includes
+#include "toonzqt/dvdialog.h"
+#include
+#include
+#include
+
+namespace DVGui {
+class FileField;
+class ColorField;
+class IntLineEdit;
+class DoubleField;
+} // namespace DVGui
+
+class QComboBox;
+class QCheckBox;
+class QFontComboBox;
+class QLineEdit;
+
+//*********************************************************************************
+// Export Camera Track Popup declaration
+//*********************************************************************************
+
+struct ExportCameraTrackInfo {
+ // target column
+ int columnId = -1;
+ // appearance settimgs
+ double bgOpacity;
+ QColor lineColor;
+ // camera rect settings
+ bool cameraRectOnKeys;
+ bool cameraRectOnTags;
+ QSet cameraRectFrames;
+ // track line settings
+ bool lineTL, lineTR, lineCenter, lineBL, lineBR;
+ int graduationInterval;
+ // frame number settings
+ Qt::Corner numberAt;
+ bool numbersOnLine;
+ QFont font;
+};
+
+//-----------------------------------------------------------------------------
+
+class CameraTrackPreviewPane final : public QWidget {
+ Q_OBJECT
+ QPixmap m_pixmap;
+ double m_scaleFactor;
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+
+public:
+ CameraTrackPreviewPane(QWidget* parent);
+ void setPixmap(QPixmap pm);
+ void doZoom(double d_scale);
+ void fitScaleTo(QSize size);
+};
+
+class CameraTrackPreviewArea final : public QScrollArea {
+ Q_OBJECT
+ QPoint m_mousePos;
+
+protected:
+ void mousePressEvent(QMouseEvent* e) override;
+ void mouseMoveEvent(QMouseEvent* e) override;
+ void contextMenuEvent(QContextMenuEvent* event) override;
+ void wheelEvent(QWheelEvent* event) override;
+
+public:
+ CameraTrackPreviewArea(QWidget* parent) : QScrollArea(parent) {}
+protected slots:
+ void fitToWindow();
+};
+
+//-----------------------------------------------------------------------------
+
+class ExportCameraTrackPopup final : public DVGui::Dialog {
+ Q_OBJECT
+ CameraTrackPreviewPane* m_previewPane;
+ CameraTrackPreviewArea* m_previewArea;
+
+ QComboBox* m_targetColumnCombo;
+ DVGui::DoubleField* m_bgOpacityField;
+ DVGui::ColorField* m_lineColorFld;
+
+ QCheckBox *m_cameraRectOnKeysCB, *m_cameraRectOnTagsCB;
+ QLineEdit* m_cameraRectFramesEdit;
+
+ QCheckBox *m_lineTL_CB, *m_lineTR_CB, *m_lineCenter_CB, *m_lineBL_CB,
+ *m_lineBR_CB;
+ QComboBox* m_graduationIntervalCombo;
+
+ QComboBox* m_numberAtCombo;
+ QCheckBox* m_numbersOnLineCB;
+ QFontComboBox* m_fontCombo;
+ DVGui::IntLineEdit* m_fontSizeEdit;
+
+public:
+ ExportCameraTrackPopup();
+
+ // bool execute() override;
+
+private:
+ void initialize();
+ void saveSettings();
+ void loadSettings();
+ void updateTargetColumnComboItems();
+
+ QImage generateCameraTrackImg(const ExportCameraTrackInfo& info,
+ bool isPreview);
+ void getInfoFromUI(ExportCameraTrackInfo& info);
+
+protected:
+ void showEvent(QShowEvent* event) override { initialize(); }
+ void closeEvent(QCloseEvent* event) override { saveSettings(); }
+
+private slots:
+ void updatePreview();
+ void onExport();
+};
+
+#endif // EXPORTCAMERATRACKPOPUP_H
\ No newline at end of file
diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp
index aca211a..938b692 100644
--- a/toonz/sources/toonz/mainwindow.cpp
+++ b/toonz/sources/toonz/mainwindow.cpp
@@ -1752,6 +1752,8 @@ void MainWindow::defineActions() {
"", "clear_cache");
createMenuFileAction(MI_ExportCurrentScene,
QT_TR_NOOP("&Export Current Scene"), "");
+ createMenuFileAction(MI_ExportCameraTrack, QT_TR_NOOP("&Export Camera Track"),
+ "");
// Menu - Edit
diff --git a/toonz/sources/toonz/menubar.cpp b/toonz/sources/toonz/menubar.cpp
index 885355e..290ad9c 100644
--- a/toonz/sources/toonz/menubar.cpp
+++ b/toonz/sources/toonz/menubar.cpp
@@ -1117,6 +1117,7 @@ QMenuBar *StackedMenuBar::createFullMenuBar() {
addMenuItem(exportMenu, MI_StopMotionExportImageSequence);
#endif
addMenuItem(exportMenu, MI_ExportTvpJson);
+ addMenuItem(exportMenu, MI_ExportCameraTrack);
}
fileMenu->addSeparator();
addMenuItem(fileMenu, MI_PrintXsheet);
diff --git a/toonz/sources/toonz/menubarcommandids.h b/toonz/sources/toonz/menubarcommandids.h
index 11fe2fe..504a282 100644
--- a/toonz/sources/toonz/menubarcommandids.h
+++ b/toonz/sources/toonz/menubarcommandids.h
@@ -470,6 +470,7 @@
#define MI_ExportOCA "MI_ExportOCA"
#define MI_ExportTvpJson "MI_ExportTvpJson"
#define MI_ExportXsheetPDF "MI_ExportXsheetPDF"
+#define MI_ExportCameraTrack "MI_ExportCameraTrack"
// mark id is added for each actual command (i.g. MI_SetCellMark1)
#define MI_SetCellMark "MI_SetCellMark"