TVP JSON export

This commit is contained in:
shun-iwasawa 2021-06-28 15:26:19 +09:00 committed by manongjohn
parent 0b63743b26
commit 51b067b19b
9 changed files with 831 additions and 9 deletions

View file

@ -182,6 +182,7 @@ set(HEADERS
metnum.h
processor.h
predict3d.h
tvpjson_io.h
)
set(SOURCES
@ -350,6 +351,7 @@ set(SOURCES
xdtsimportpopup.cpp
expressionreferencemanager.cpp
tooloptionsshortcutinvoker.cpp
tvpjson_io.cpp
# Tracker file
dummyprocessor.cpp
metnum.cpp

View file

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="60px"
height="60px"
version="1.1"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
id="svg44"
sodipodi:docname="json_icon.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
id="metadata50"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs48">
</defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1273"
inkscape:window-height="826"
id="namedview46"
showgrid="false"
showguides="false"
inkscape:zoom="3.9333333"
inkscape:cx="0.69877966"
inkscape:cy="48.877998"
inkscape:window-x="458"
inkscape:window-y="28"
inkscape:window-maximized="0"
inkscape:current-layer="svg44" />
<rect
x="0"
y="0"
width="60"
height="60"
style="fill:rgb(184,184,184);fill-opacity:0;"
id="rect2" />
<path
d="M 52,58 H 7 V 2 h 34 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 z"
style="fill:#a2e5db;fill-rule:nonzero;fill-opacity:1;stroke:none"
id="path4"
inkscape:connector-curvature="0" /><path
d="M 40,14 V 2 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 z"
style="fill:#cfecef;fill-rule:nonzero;fill-opacity:1"
id="path7"
inkscape:connector-curvature="0" /><g
id="g13"
transform="translate(-1,1)">
<path
inkscape:connector-curvature="0"
id="path11"
style="fill-opacity:0.25"
d="M 41,13 H 52 V 24 Z" />
</g><path
d="M 52,58 H 7 V 2 h 34 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 v 1 h 1 z M 40,3 H 8 V 57 H 51 V 14 Z"
style="fill:#3a839a;fill-opacity:1"
id="path15"
inkscape:connector-curvature="0" />
<g
aria-label="JSON"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
id="text884"><path
d="m 15.929203,42.828612 h 1.920573 v 6.041667 q 0,1.184896 -0.208334,1.822916 -0.279947,0.833334 -1.015625,1.341146 -0.735677,0.501302 -1.940104,0.501302 -1.41276,0 -2.174479,-0.78776 -0.761718,-0.794271 -0.768229,-2.324219 l 1.816406,-0.208333 q 0.03255,0.820312 0.240886,1.158854 0.3125,0.514323 0.950521,0.514323 0.644531,0 0.911458,-0.364583 0.266927,-0.371094 0.266927,-1.529948 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.33333302px;font-family:Arial;-inkscape-font-specification:'Arial Bold'"
id="path890" /><path
d="m 19.418786,49.267414 1.875,-0.182291 q 0.169271,0.94401 0.683594,1.386718 0.520833,0.442709 1.39974,0.442709 0.930989,0 1.399739,-0.390625 0.475261,-0.397136 0.475261,-0.92448 0,-0.338541 -0.201823,-0.572916 -0.195313,-0.240886 -0.690105,-0.416667 -0.338541,-0.117187 -1.542968,-0.416667 -1.549479,-0.384114 -2.174479,-0.94401 -0.878907,-0.78776 -0.878907,-1.920573 0,-0.729166 0.410157,-1.360677 0.416666,-0.638021 1.191406,-0.970052 0.78125,-0.332031 1.88151,-0.332031 1.796875,0 2.701823,0.78776 0.911458,0.787761 0.957031,2.102865 l -1.927083,0.08463 q -0.123698,-0.735677 -0.533854,-1.054687 -0.403646,-0.325521 -1.217448,-0.325521 -0.839844,0 -1.315104,0.345052 -0.30599,0.221354 -0.30599,0.592448 0,0.338542 0.286459,0.579427 0.364583,0.305989 1.770833,0.638021 1.40625,0.332031 2.076823,0.690104 0.677083,0.351562 1.054687,0.970052 0.384115,0.611979 0.384115,1.516927 0,0.820312 -0.455729,1.536458 -0.455729,0.716146 -1.289063,1.067709 -0.833333,0.345052 -2.076823,0.345052 -1.809895,0 -2.779948,-0.833334 -0.970052,-0.839843 -1.158854,-2.441406 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.33333302px;font-family:Arial;-inkscape-font-specification:'Arial Bold'"
id="path892" /><path
d="m 28.422693,47.659341 q 0,-1.458333 0.436198,-2.447916 0.32552,-0.729167 0.885416,-1.308594 0.566406,-0.579427 1.236979,-0.859375 0.891927,-0.377604 2.057292,-0.377604 2.109375,0 3.372396,1.308594 1.269531,1.308593 1.269531,3.639322 0,2.311198 -1.256511,3.619792 -1.25651,1.302083 -3.359374,1.302083 -2.128907,0 -3.385417,-1.295573 -1.25651,-1.302083 -1.25651,-3.580729 z m 1.985677,-0.0651 q 0,1.621094 0.748698,2.460938 0.748697,0.833333 1.901041,0.833333 1.152344,0 1.888021,-0.826823 0.742187,-0.833333 0.742187,-2.49349 0,-1.640625 -0.722656,-2.447916 -0.716146,-0.807292 -1.907552,-0.807292 -1.191406,0 -1.920573,0.820313 -0.729166,0.813802 -0.729166,2.460937 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.33333302px;font-family:Arial;-inkscape-font-specification:'Arial Bold'"
id="path894" /><path
d="m 39.197431,52.372883 v -9.544271 h 1.875 l 3.90625,6.373698 v -6.373698 h 1.790364 v 9.544271 h -1.933593 l -3.847657,-6.223958 v 6.223958 z"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:13.33333302px;font-family:Arial;-inkscape-font-specification:'Arial Bold'"
id="path896" /></g><g
aria-label="{;}"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
id="text888"><path
d="m 25.14849,36.250741 h -1.5625 q -1.864583,0 -3.03125,-1.041666 -1.15625,-1.03125 -1.15625,-2.989584 v -1.552083 q 0,-1.760417 -0.864583,-2.75 -0.864584,-1 -2.645834,-1 h -0.53125 v -1.625 h 0.53125 q 1.78125,0 2.645834,-0.989584 0.864583,-1 0.864583,-2.760416 v -1.552084 q 0,-1.958333 1.15625,-2.989583 1.166667,-1.041667 3.03125,-1.041667 h 1.5625 v 1.4375 h -1.1875 q -1.416666,0 -2.0625,0.65625 -0.635416,0.65625 -0.635416,2.114584 v 1.822916 q 0,1.447917 -0.802084,2.4375 -0.802083,0.979167 -2.229167,1.552084 v 0.25 q 1.427084,0.572916 2.229167,1.5625 0.802084,0.979167 0.802084,2.427083 v 1.822917 q 0,1.458333 0.635416,2.114583 0.645834,0.65625 2.0625,0.65625 h 1.1875 z"
style="font-size:21.33333397px"
id="path899" /><path
d="m 30.902022,23.500741 h -2.489584 v -2.96875 h 2.489584 z m 0.760416,5.697917 -2.9375,6.822917 h -1.520833 l 1.8125,-6.822917 z"
style="font-size:21.33333397px"
id="path901" /><path
d="m 43.205342,26.917408 h -0.53125 q -1.78125,0 -2.645833,1 -0.864584,0.989583 -0.864584,2.75 v 1.552083 q 0,1.958334 -1.166666,2.989584 -1.156251,1.041666 -3.020834,1.041666 h -1.5625 v -1.4375 h 1.1875 q 1.416667,0 2.052083,-0.65625 0.645834,-0.65625 0.645834,-2.114583 v -1.822917 q 0,-1.447916 0.802083,-2.427083 0.802084,-0.989584 2.229167,-1.5625 v -0.25 q -1.427083,-0.572917 -2.229167,-1.552084 -0.802083,-0.989583 -0.802083,-2.4375 v -1.822916 q 0,-1.458334 -0.645834,-2.114584 -0.635416,-0.65625 -2.052083,-0.65625 h -1.1875 v -1.4375 h 1.5625 q 1.864583,0 3.020834,1.041667 1.166666,1.03125 1.166666,2.989583 v 1.552084 q 0,1.760416 0.864584,2.760416 0.864583,0.989584 2.645833,0.989584 h 0.53125 z"
style="font-size:21.33333397px"
id="path903" /></g></svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -1789,6 +1789,9 @@ void MainWindow::defineActions() {
QT_TRANSLATE_NOOP("MainWindow",
"Export Exchange Digital Time Sheet (XDTS)"),
"");
createMenuFileAction(
MI_ExportTvpJson,
QT_TRANSLATE_NOOP("MainWindow", "Export TVPaint JSON File"), "");
createMenuFileAction("MI_RunScript", QT_TR_NOOP("Run Script..."), "",
"run_script", tr("Run a script to perform a series of actions on a scene."));
createMenuFileAction("MI_OpenScriptConsole",

View file

@ -345,6 +345,7 @@ void TopBar::loadMenubar() {
addMenuItem(exportMenu, MI_SoundTrack);
addMenuItem(exportMenu, MI_ExportXDTS);
addMenuItem(exportMenu, MI_StopMotionExportImageSequence);
addMenuItem(exportMenu, MI_ExportTvpJson);
}
fileMenu->addSeparator();
addMenuItem(fileMenu, MI_PrintXsheet);

View file

@ -468,6 +468,7 @@
#define MI_FlipPrevGuideStroke "MI_FlipPrevGuideStroke"
#define MI_ExportXDTS "MI_ExportXDTS"
#define MI_ExportTvpJson "MI_ExportTvpJson"
#define MI_ToggleAutoCreate "MI_ToggleAutoCreate"
#define MI_ToggleCreationInHoldCells "MI_ToggleCreationInHoldCells"

View file

@ -173,7 +173,8 @@
<file>icons/dark/mimetypes/60/svg_icon.svg</file>
<file>icons/dark/mimetypes/60/audio_icon.svg</file>
<file>icons/dark/mimetypes/60/script_icon.svg</file>
<file>icons/dark/mimetypes/60/broken_icon.svg</file>
<file>icons/dark/mimetypes/60/broken_icon.svg</file>
<file>icons/dark/mimetypes/60/json_icon.svg</file>
<!-- Render -->
<file>icons/dark/actions/16/render.svg</file>

View file

@ -0,0 +1,516 @@
#include "tvpjson_io.h"
#include "tsystem.h"
#include "toonz/toonzscene.h"
#include "toonz/tproject.h"
#include "toonz/levelset.h"
#include "toonz/txsheet.h"
#include "toonz/txshcell.h"
#include "toonz/txshsimplelevel.h"
#include "toonz/txshchildlevel.h"
#include "toonz/txsheethandle.h"
#include "toonz/tscenehandle.h"
#include "toonz/preferences.h"
#include "toonz/sceneproperties.h"
#include "toonz/tstageobject.h"
#include "toutputproperties.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/tcamera.h"
#include "toonzqt/menubarcommand.h"
#include "toonzqt/gutil.h"
#include "tapp.h"
#include "menubarcommandids.h"
#include "filebrowserpopup.h"
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonDocument>
#include <QDesktopServices>
#include <QPair>
using namespace TvpJsonIo;
namespace {
QMap<QString, BlendMode> BlendingMatch = {{"Color", BlendMode_Color},
{"Darken", BlendMode_Darken}};
QString getFrameNumberWithLetters(int frame) {
int letterNum = frame % 10;
QChar letter;
switch (letterNum) {
case 0:
letter = QChar();
break;
case 1:
letter = 'A';
break;
case 2:
letter = 'B';
break;
case 3:
letter = 'C';
break;
case 4:
letter = 'D';
break;
case 5:
letter = 'E';
break;
case 6:
letter = 'F';
break;
case 7:
letter = 'G';
break;
case 8:
letter = 'H';
break;
case 9:
letter = 'I';
break;
default:
letter = QChar();
break;
}
QString number;
if (frame >= 10) {
number = QString::number(frame);
number.chop(1);
} else
number = "0";
return (QChar(letter).isNull()) ? number : number.append(letter);
}
} // namespace
//-----------------------------------------------------------------------------
void TvpJsonLayerLinkItem::read(const QJsonObject& json) {
m_instance_index = json["instance-index"].toInt();
m_instance_name = json["instance-name"].toString();
m_file = json["file"].toString();
QJsonArray imagesArray = json["images"].toArray();
for (int imageIndex = 0; imageIndex < imagesArray.size(); imageIndex++) {
m_images.append(imagesArray[imageIndex].toInt());
}
}
void TvpJsonLayerLinkItem::write(QJsonObject& json) const {
json["instance-index"] = m_instance_index;
json["instance-name"] = m_instance_name;
json["file"] = m_file;
QJsonArray imagesArray;
for (const auto frame : m_images) {
imagesArray.append(frame);
}
json["images"] = imagesArray;
}
void TvpJsonLayerLinkItem::build(int instance_index, QString instance_name,
QString file, QList<int> images) {
m_instance_index = instance_index;
m_instance_name = instance_name;
m_file = file;
m_images = images;
}
//-----------------------------------------------------------------------------
void TvpJsonLayer::read(const QJsonObject& json) {
m_name = json["name"].toString();
m_visible = json["visible"].toBool();
m_position = json["position"].toInt();
m_opacity = json["opacity"].toInt();
m_start = json["start"].toInt();
m_end = json["end"].toInt();
QString blendModeName = json["blending-mode"].toString("Color");
m_blending_mode = BlendingMatch.value(blendModeName, BlendMode_Color);
QJsonArray linkArray = json["link"].toArray();
for (int linkIndex = 0; linkIndex < linkArray.size(); linkIndex++) {
QJsonObject linkItemObject = linkArray[linkIndex].toObject();
TvpJsonLayerLinkItem linkItem;
linkItem.read(linkItemObject);
m_link.append(linkItem);
}
}
void TvpJsonLayer::write(QJsonObject& json) const {
json["name"] = m_name;
json["visible"] = m_visible;
json["position"] = m_position;
json["opacity"] = m_opacity;
json["start"] = m_start;
json["end"] = m_end;
QString blendModeName = BlendingMatch.key(m_blending_mode, "Color");
json["blending-mode"] = blendModeName;
QJsonArray linkArray;
for (const auto linkItem : m_link) {
QJsonObject linkItemObject;
linkItem.write(linkItemObject);
linkArray.append(linkItemObject);
}
json["link"] = linkArray;
}
void TvpJsonLayer::build(int index, ToonzScene* scene, TXshCellColumn* column) {
m_position = index;
column->getRange(m_start, m_end);
TXshCell prevCell;
TFilePath sceneFolder =
scene->decodeFilePath(scene->getScenePath().getParentDir());
// JSONに記入可能かどうかチェック済みのレベル
QMap<TXshLevel*, TFilePath> checkedLevels;
// register the firstly-found level
TXshLevel* firstFoundLevel = nullptr;
QList<TXshCell> cellList;
QList<QList<int>> imgsList;
QMap<TXshLevel*, QPair<int, char>> frameFormats;
for (int row = m_start; row <= m_end; row++) {
TXshCell cell = column->getCell(row);
// continue if the cell is continuous
if (prevCell == cell) continue;
// try to register the level.
TXshLevel* level = cell.getSimpleLevel();
if (!level)
cell = TXshCell();
else if (checkedLevels.contains(level)) {
if (checkedLevels[level].isEmpty())
cell = TXshCell();
else {
} // do nothing
} else // newly found level
{
// Only raster levels are exported
// currently, only the files located under the scene folder will be
// exported
if (level->getType() != OVL_XSHLEVEL)
cell = TXshCell();
else {
TFilePath levelFullPath = scene->decodeFilePath(level->getPath());
if (sceneFolder.isAncestorOf(levelFullPath)) {
checkedLevels[level] = levelFullPath - sceneFolder;
if (!firstFoundLevel) firstFoundLevel = level;
std::vector<TFrameId> fids;
level->getFids(fids);
frameFormats[level] = QPair<int, char>(fids[0].getZeroPadding(),
fids[0].getStartSeqInd());
} else
cell = TXshCell();
}
if (cell.isEmpty()) checkedLevels[level] = TFilePath();
}
// continue if the cell is continuous
if (prevCell == cell) continue;
if (!cellList.contains(cell)) {
cellList.append(cell);
imgsList.append(QList<int>());
}
imgsList[cellList.indexOf(cell)].append(row);
prevCell = cell;
}
if (imgsList.isEmpty()) return;
//-------
for (int i = 0; i < imgsList.size(); ++i) {
TXshCell cell = cellList[i];
int instance_index = imgsList[i].at(0);
QString instance_name;
QString file;
if (cell.isEmpty()) { // 空コマの場合
instance_name = QString::number(0);
TFrameId zeroFid(0, 0, frameFormats[firstFoundLevel].first,
frameFormats[firstFoundLevel].second);
TFilePath imgPath = checkedLevels[firstFoundLevel].withFrame(zeroFid);
file = imgPath.getQString();
} else {
TFrameId fid = cell.getFrameId();
// convert the last one digit of the frame number to alphabet
// Ex. 12 -> 1B 21 -> 2A 30 -> 3
if (Preferences::instance()->isShowFrameNumberWithLettersEnabled())
instance_name = getFrameNumberWithLetters(fid.getNumber());
else {
std::string frameNumber("");
// set number
if (fid.getNumber() >= 0) frameNumber = std::to_string(fid.getNumber());
// add letter
if (fid.getLetter() != 0) frameNumber.append(1, fid.getLetter());
instance_name = QString::fromStdString(frameNumber);
}
fid.setZeroPadding(frameFormats[cell.m_level.getPointer()].first);
fid.setStartSeqInd(frameFormats[cell.m_level.getPointer()].second);
TFilePath imgPath =
checkedLevels[cell.m_level.getPointer()].withFrame(fid);
file = imgPath.getQString();
}
TvpJsonLayerLinkItem linkItem;
linkItem.build(instance_index, instance_name, file, imgsList[i]);
m_link.append(linkItem);
}
if (firstFoundLevel)
m_name = QString::fromStdWString(firstFoundLevel->getName());
else
m_name = QString();
m_visible = column->isPreviewVisible();
m_opacity = (int)column->getOpacity();
}
//-----------------------------------------------------------------------------
void TvpJsonClip::read(const QJsonObject& json) {
m_name = json["name"].toString("Untitled");
m_cameraSize.setWidth(json["width"].toInt(1920));
m_cameraSize.setHeight(json["height"].toInt(1080));
m_framerate = json["framerate"].toDouble(24.0);
m_pixelaspectratio = json["pixelaspectratio"].toDouble(1.0);
m_image_count = json["image-count"].toInt();
QJsonObject bgObject = json["bg"].toObject();
m_bg.setRed(bgObject["red"].toInt(255));
m_bg.setGreen(bgObject["green"].toInt(255));
m_bg.setBlue(bgObject["blue"].toInt(255));
m_bg.setAlpha(255);
QJsonArray layersArray = json["layers"].toArray();
for (int layerIndex = 0; layerIndex < layersArray.size(); layerIndex++) {
QJsonObject layerObject = layersArray[layerIndex].toObject();
TvpJsonLayer layer;
layer.read(layerObject);
m_layers.append(layer);
}
}
void TvpJsonClip::write(QJsonObject& json) const {
json["name"] = m_name;
json["width"] = m_cameraSize.width();
json["height"] = m_cameraSize.height();
json["framerate"] = m_framerate;
json["pixelaspectratio"] = m_pixelaspectratio;
json["image-count"] = m_image_count;
QJsonObject bgObject;
bgObject["red"] = m_bg.red();
bgObject["green"] = m_bg.green();
bgObject["blue"] = m_bg.blue();
json["bg"] = bgObject;
QJsonArray layersArray;
for (const auto layer : m_layers) {
QJsonObject layerObject;
layer.write(layerObject);
layersArray.append(layerObject);
}
json["layers"] = layersArray;
}
void TvpJsonClip::build(ToonzScene* scene, TXsheet* xsheet) {
TFilePath fp = scene->getScenePath();
m_name = QString::fromStdString(fp.getName());
TStageObjectId cameraId = xsheet->getStageObjectTree()->getCurrentCameraId();
TCamera* currentCamera = xsheet->getStageObject(cameraId)->getCamera();
TDimension cameraRes = currentCamera->getRes();
m_cameraSize = QSize(cameraRes.lx, cameraRes.ly);
TOutputProperties* outputProperties =
scene->getProperties()->getOutputProperties();
m_framerate = outputProperties->getFrameRate();
TPointD cameraDpi = currentCamera->getDpi();
m_pixelaspectratio = cameraDpi.y / cameraDpi.x;
// if the current xsheet is top xsheet in the scene and the output
// frame range is specified, set the "to" frame value as duration
// TOutputProperties* oprop = scene->getProperties()->getOutputProperties();
int from, to, step;
if (scene->getTopXsheet() == xsheet &&
outputProperties->getRange(from, to, step))
m_image_count = to + 1;
else
m_image_count = xsheet->getFrameCount();
TPixel bgColor = scene->getProperties()->getBgColor();
m_bg = QColor((int)bgColor.r, (int)bgColor.g, (int)bgColor.b);
if (xsheet->getColumnCount() == 0) return;
for (int col = xsheet->getColumnCount() - 1; col >= 0; col--) {
if (xsheet->isColumnEmpty(col)) {
continue;
}
TXshCellColumn* column = xsheet->getColumn(col)->getCellColumn();
if (!column) {
continue;
}
TvpJsonLayer layer;
layer.build(col, scene, column);
if (!layer.isEmpty()) m_layers.append(layer);
}
}
//-----------------------------------------------------------------------------
void TvpJsonProject::read(const QJsonObject& json) {
if (json.contains("format")) {
QJsonObject formatObject = json["format"].toObject();
if (formatObject.contains("extension"))
m_formatExtension = formatObject["extension"].toString();
}
QJsonObject clipObject = json["clip"].toObject();
m_clip.read(clipObject);
}
void TvpJsonProject::write(QJsonObject& json) const {
QJsonObject formatObject;
formatObject["extension"] = m_formatExtension;
json["format"] = formatObject;
QJsonObject clipObject;
m_clip.write(clipObject);
json["clip"] = clipObject;
}
void TvpJsonProject::build(ToonzScene* scene, TXsheet* xsheet) {
TOutputProperties* outputProperties =
scene->getProperties()->getOutputProperties();
m_formatExtension =
QString::fromStdString(outputProperties->getPath().getType());
m_clip.build(scene, xsheet);
}
//-----------------------------------------------------------------------------
void TvpJsonVersion::read(const QJsonObject& json) {
m_major = json["major"].toInt();
m_minor = json["minor"].toInt();
}
void TvpJsonVersion::write(QJsonObject& json) const {
json["major"] = m_major;
json["minor"] = m_minor;
}
void TvpJsonVersion::setVersion(int major, int minor) {
m_major = major;
m_minor = minor;
}
//-----------------------------------------------------------------------------
void TvpJsonData::read(const QJsonObject& json) {
QJsonObject versionObject = json["version"].toObject();
m_version.read(versionObject);
QJsonObject projectObject = json["project"].toObject();
m_project.read(projectObject);
}
void TvpJsonData::write(QJsonObject& json) const {
QJsonObject versionObject;
m_version.write(versionObject);
json["version"] = versionObject;
QJsonObject projectObject;
m_project.write(projectObject);
json["project"] = projectObject;
}
void TvpJsonData::build(ToonzScene* scene, TXsheet* xsheet) {
// currently this i/o is based on version 5.1 files
m_version.setVersion(5, 1);
m_project.build(scene, xsheet);
}
//-----------------------------------------------------------------------------
class ExportTvpJsonCommand final : public MenuItemHandler {
public:
ExportTvpJsonCommand() : MenuItemHandler(MI_ExportTvpJson) {}
void execute() override;
} exportTvpJsonCommand;
void ExportTvpJsonCommand::execute() {
ToonzScene* scene = TApp::instance()->getCurrentScene()->getScene();
if (scene->isUntitled()) {
DVGui::error(
QObject::tr("TVPaint JSON file cannot be exported from untitled scene. "
"Save the scene first."));
return;
}
TXsheet* xsheet = TApp::instance()->getCurrentXsheet()->getXsheet();
TFilePath fp = scene->getScenePath().withType("json");
TvpJsonData tvpJsonData;
tvpJsonData.build(scene, xsheet);
if (tvpJsonData.isEmpty()) {
DVGui::error(
QObject::tr("No columns can be exported. Please note the followings:\n "
"- The level files must be placed at the same or child "
"folder relative to the scene file.\n - Currently only the "
"columns containing raster levels can be exported."));
return;
}
static GenericSaveFilePopup* savePopup = 0;
if (!savePopup) {
savePopup =
new GenericSaveFilePopup(QObject::tr("Export TVPaint JSON File"));
savePopup->addFilterType("json");
}
if (!scene->isUntitled())
savePopup->setFolder(fp.getParentDir());
else
savePopup->setFolder(
TProjectManager::instance()->getCurrentProject()->getScenesPath());
savePopup->setFilename(fp.withoutParentDir());
fp = savePopup->getPath();
if (fp.isEmpty()) return;
QFile saveFile(fp.getQString());
if (!saveFile.open(QIODevice::WriteOnly)) {
qWarning("Couldn't open save file.");
return;
}
QJsonObject tvpJsonObject;
tvpJsonData.write(tvpJsonObject);
QJsonDocument saveDoc(tvpJsonObject);
QByteArray jsonByteArrayData = saveDoc.toJson();
saveFile.write(jsonByteArrayData);
QString str = QObject::tr("The file %1 has been exported successfully.")
.arg(QString::fromStdString(fp.getLevelName()));
std::vector<QString> buttons = {QObject::tr("OK"),
QObject::tr("Open containing folder")};
int ret = DVGui::MsgBox(DVGui::INFORMATION, str, buttons);
if (ret == 2) {
TFilePath folderPath = fp.getParentDir();
if (TSystem::isUNC(folderPath))
QDesktopServices::openUrl(QUrl(folderPath.getQString()));
else
QDesktopServices::openUrl(QUrl::fromLocalFile(folderPath.getQString()));
}
}

View file

@ -0,0 +1,182 @@
#pragma once
#ifndef TVPJSON_IO_H
#define TVPJSON_IO_H
#include "tfilepath.h"
#include <QString>
#include <QSize>
#include <QColor>
#include <QList>
#include <QPair>
#include <QStringList>
#include <iostream>
class ToonzScene;
class TFilePath;
class TXsheet;
class TXshCellColumn;
class QJsonObject;
namespace TvpJsonIo {
// Currently only supports "Color" and "Darken" brend modes
enum BlendMode {
BlendMode_Color,
// BlendMode_Behind,
// BlendMode_Erase,
// BlendMode_Shade,
// BlendMode_Light,
// BlendMode_Colorize,
// BlendMode_Hue,
// BlendMode_Saturation,
// BlendMode_Value,
// BlendMode_Add,
// BlendMode_Sub,
// BlendMode_Multiply,
// BlendMode_Screen,
// BlendMode_Replace,
// BlendMode_Copy,
// BlendMode_Difference,
// BlendMode_Divide,
// BlendMode_Overlay,
// BlendMode_Overlay2,
// BlendMode_Light2,
// BlendMode_Shade2,
// BlendMode_HardLight,
// BlendMode_SoftLight,
// BlendMode_GrainExtract,
// BlendMode_GrainMerge,
// BlendMode_Sub2,
BlendMode_Darken,
// BlendMode_Lighten,
BlendModeCount
};
// "description": "information of each cell and instance",
class TvpJsonLayerLinkItem {
// instance-index "description": "first placed frame of instance (the first
// frame is 0)"
int m_instance_index;
// instance-name "description": "drawing number (starting from 1. 0 is used
// for empty frame)"
QString m_instance_name;
// "description": "instance file path, relative to the scene file"
QString m_file;
// "description": "each start frame of the image (the first frame is 0)"
QList<int> m_images;
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
void build(int instance_index, QString instance_name, QString file,
QList<int> images);
};
class TvpJsonLayer {
// "description": "layer name (= level name)"
QString m_name;
// "description": "visibility"
bool m_visible;
// "description": "layer index (stacking order), starting from 0"
int m_position;
// "description": "layer opacity 0-255"
int m_opacity;
// "description": "start frame of the layer"
int m_start;
// "description": "end frame of the layer"
int m_end;
// pre_behavior, post_behavior : Unknown. Put 0 for now.
// "description": "composite mode of the layers. "Color"Normal,
// "Darken"Darken"
BlendMode m_blending_mode;
// group/red,green,blue "description": "Unknown. Put 0 for now"
// QColor m_group
// "description": "information of each cell and instance",
QList<TvpJsonLayerLinkItem> m_link;
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
void build(int index, ToonzScene*, TXshCellColumn* column);
bool isEmpty() { return m_link.isEmpty(); }
};
class TvpJsonClip {
// "description": "scene name"
QString m_name;
// "width": "description": "camera width in pixels"
// "height": "description": "camera height in pixels"
QSize m_cameraSize;
// "description": "frame rate"
double m_framerate;
// "description": "pixel aspect ratio"
double m_pixelaspectratio;
// "image-count": "description": "scene frame length"
int m_image_count;
// bg/red,green,blue "description": "camera BG color0-255 for each
// channel"
QColor m_bg;
// camera : camera information. Ignore it for now.
// markin, markout : Ignore for now.
//"description": "layer information"
QList<TvpJsonLayer> m_layers;
// repeat : Ignore for now.
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
bool isEmpty() { return m_layers.isEmpty(); }
void build(ToonzScene*, TXsheet*);
};
class TvpJsonProject {
// format/extension "description": "Output format extension??"
QString m_formatExtension;
// "description": "scene information"
TvpJsonClip m_clip;
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
void build(ToonzScene*, TXsheet*);
bool isEmpty() { return m_clip.isEmpty(); }
};
class TvpJsonVersion {
// "description": "major version"
int m_major;
// "description": "minor version"
int m_minor;
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
void setVersion(int, int);
};
class TvpJsonData {
// "description": "version number of this tvp-json format",
TvpJsonVersion m_version;
// "description": "project information"
TvpJsonProject m_project;
public:
void read(const QJsonObject& json);
void write(QJsonObject& json) const;
void build(ToonzScene*, TXsheet*);
bool isEmpty() { return m_project.isEmpty(); }
};
} // namespace TvpJsonIo
#endif

View file

@ -118,7 +118,7 @@ bool getIcon(const std::string &iconName, QPixmap &pix,
ras->getSize().lx > standardSize.lx &&
ras->getSize().ly > standardSize.ly)
isHighDpi = true;
pix = rasterToQPixmap(ras, false, isHighDpi);
pix = rasterToQPixmap(ras, false, isHighDpi);
return true;
}
@ -187,7 +187,7 @@ void makeChessBackground(const TRaster32P &ras) {
int yCol = (y & 4);
for (int x = 0; pix != lineEnd; ++x, ++pix)
for (int x = 0; pix != lineEnd; ++x, ++pix)
if (pix->m != 255) *pix = overPix((x & 4) == yCol ? gray1 : gray2, *pix);
}
@ -605,8 +605,8 @@ TRaster32P SplineIconRenderer::generateRaster(
double scaleX = 1, scaleY = 1;
if (sbbox.getLx() > 0.0) scaleX = (double)iconSize.lx / sbbox.getLx();
if (sbbox.getLy() > 0.0) scaleY = (double)iconSize.ly / sbbox.getLy();
double scale = 0.8 * std::min(scaleX, scaleY);
TPointD centerStroke = 0.5 * (sbbox.getP00() + sbbox.getP11());
double scale = 0.8 * std::min(scaleX, scaleY);
TPointD centerStroke = 0.5 * (sbbox.getP00() + sbbox.getP11());
TPointD centerPixmap(iconSize.lx * 0.5, iconSize.ly * 0.5);
glPushMatrix();
tglMultMatrix(TScale(scale).place(centerStroke, centerPixmap));
@ -976,10 +976,10 @@ TRaster32P IconGenerator::generateVectorFileIcon(const TFilePath &path,
TLevelReaderP lr(path);
TLevelP level = lr->loadInfo();
if (level->begin() == level->end()) return TRaster32P();
TFrameId frameId = fid;
TFrameId frameId = fid;
if (fid == TFrameId::NO_FRAME) frameId = level->begin()->first;
TImageP img = lr->getFrameReader(frameId)->load();
TVectorImageP vi = img;
TImageP img = lr->getFrameReader(frameId)->load();
TVectorImageP vi = img;
if (!vi) return TRaster32P();
vi->setPalette(level->getPalette());
VectorImageIconRenderer vir("", iconSize, vi.getPointer(),
@ -1117,7 +1117,7 @@ TRaster32P IconGenerator::generateMeshFileIcon(const TFilePath &path,
TLevelP level = lr->loadInfo();
if (level->begin() == level->end()) return TRaster32P();
TFrameId frameId = fid;
TFrameId frameId = fid;
if (fid == TFrameId::NO_FRAME) frameId = level->begin()->first;
TMeshImageP mi = lr->getFrameReader(frameId)->load();
@ -1241,6 +1241,12 @@ void FileIconRenderer::run() {
QSize(iconSize.lx, iconSize.ly), Qt::KeepAspectRatio));
setIcon(rasterFromQPixmap(script));
return;
} else if (type == "json") {
QPixmap json(svgToPixmap(getIconThemePath("mimetypes/60/json_icon.svg"),
QSize(iconSize.lx, iconSize.ly),
Qt::KeepAspectRatio));
setIcon(rasterFromQPixmap(json));
return;
}
else {