// TnzCore includes
#include "tconvert.h"
// TnzBase includes
#include "tdoubleparam.h"
#include "tparamcontainer.h"
#include "tparamset.h"
#include "texpression.h"
#include "tparser.h"
#include "tfx.h"
#include "tzeraryfx.h"
#include "tcolumnset.h"
#include "tw/stringtable.h"
#include "tunit.h"
// TnzLib includes
#include "toonz/txsheet.h"
#include "toonz/txshcell.h"
#include "toonz/tstageobjectid.h"
#include "toonz/tstageobject.h"
#include "toonz/fxdag.h"
#include "toonz/tstageobjecttree.h"
#include "toonz/tcolumnfxset.h"
#include "toonz/tcolumnfx.h"
// Boost includes
#include "boost/noncopyable.hpp"
// Qt includes
#include <QString>
#include "toonz/txsheetexpr.h"
#include <memory>
using namespace TSyntax;
//******************************************************************************
// Local namespace stuff
//******************************************************************************
namespace {
//===================================================================
class ParamDependencyFinder final : public TSyntax::CalculatorNodeVisitor {
TDoubleParam *m_possiblyDependentParam;
bool m_found;
public:
ParamDependencyFinder(TDoubleParam *possiblyDependentParam)
: m_possiblyDependentParam(possiblyDependentParam), m_found(false) {}
void check(TDoubleParam *param) {
if (param == m_possiblyDependentParam) m_found = true;
}
bool found() const { return m_found; }
};
//===================================================================
/*
class ColumnReferenceFinder final : public TSyntax::CalculatorNodeVisitor {
QSet<int> m_indices;
public:
ColumnReferenceFinder() {}
void registerColumnIndex(int columnIndex) { m_indices.insert(columnIndex); }
QSet<int> indices() const { return m_indices; }
};
*/
//===================================================================
class ParamReferenceFinder final : public TSyntax::CalculatorNodeVisitor {
QSet<TDoubleParam *> m_refParams;
QSet<int> m_columnIndices;
public:
ParamReferenceFinder() {}
void registerRefParam(TDoubleParam *param) { m_refParams.insert(param); }
void registerColumnIndex(int columnIndex) {
m_columnIndices.insert(columnIndex);
}
QSet<int> columnIndices() const { return m_columnIndices; }
QSet<TDoubleParam *> refParams() const { return m_refParams; }
};
//===================================================================
//
// Calculator Nodes
//
//-------------------------------------------------------------------
class ParamCalculatorNode : public CalculatorNode,
public TParamObserver,
public boost::noncopyable {
std::unique_ptr<CalculatorNode> m_frame;
protected:
TDoubleParamP m_param;
public:
ParamCalculatorNode(Calculator *calculator, const TDoubleParamP ¶m,
std::unique_ptr<CalculatorNode> frame)
: CalculatorNode(calculator), m_param(param), m_frame(std::move(frame)) {
param->addObserver(this);
}
~ParamCalculatorNode() { m_param->removeObserver(this); }
double compute(double vars[3]) const override {
double value = m_param->getValue(m_frame->compute(vars) - 1);
TMeasure *measure = m_param->getMeasure();
if (measure) {
const TUnit *unit = measure->getCurrentUnit();
if (unit) value = unit->convertTo(value);
}
return value;
}
void accept(TSyntax::CalculatorNodeVisitor &visitor) override {
ParamReferenceFinder *prf = dynamic_cast<ParamReferenceFinder *>(&visitor);
if (prf) {
prf->registerRefParam(m_param.getPointer());
return;
}
ParamDependencyFinder *pdf =
dynamic_cast<ParamDependencyFinder *>(&visitor);
if (pdf) {
pdf->check(m_param.getPointer());
if (!pdf->found()) m_param->accept(visitor);
}
}
void onChange(const TParamChange ¶mChange) override {
// The referenced parameter changed. This means the parameter owning the
// expression this node is part of, changes too.
// A param change is thus propagated for this parameter, with the 'keyframe'
// parameter turned off - since no keyframe value is actually altered.
if (TDoubleParam *ownerParam = getCalculator()->getOwnerParameter()) {
const std::set<TParamObserver *> &observers = ownerParam->observers();
TParamChange propagatedChange(ownerParam, 0, 0, false,
paramChange.m_dragging,
paramChange.m_undoing);
std::set<TParamObserver *>::const_iterator ot, oEnd = observers.end();
for (ot = observers.begin(); ot != oEnd; ++ot)
(*ot)->onChange(propagatedChange);
}
}
bool hasReference() const override { return true; }
};
// ParamCalculatorNode subclass to monitor referenced columns
class ColumnParamCalculatorNode final : public ParamCalculatorNode {
int m_columnIndex;
public:
ColumnParamCalculatorNode(Calculator *calculator, const TDoubleParamP ¶m,
std::unique_ptr<CalculatorNode> frame,
int columnIndex)
: ParamCalculatorNode(calculator, param, std::move(frame))
, m_columnIndex(columnIndex) {}
void accept(TSyntax::CalculatorNodeVisitor &visitor) override {
ParamReferenceFinder *prf = dynamic_cast<ParamReferenceFinder *>(&visitor);
if (prf) {
prf->registerRefParam(m_param.getPointer());
prf->registerColumnIndex(m_columnIndex);
return;
}
ParamDependencyFinder *pdf =
dynamic_cast<ParamDependencyFinder *>(&visitor);
if (pdf) {
pdf->check(m_param.getPointer());
if (!pdf->found()) m_param->accept(visitor);
}
}
};
//-------------------------------------------------------------------
class XsheetDrawingCalculatorNode final : public CalculatorNode,
public boost::noncopyable {
TXsheet *m_xsh;
int m_columnIndex;
std::unique_ptr<CalculatorNode> m_frame;
public:
XsheetDrawingCalculatorNode(Calculator *calc, TXsheet *xsh, int columnIndex,
std::unique_ptr<CalculatorNode> frame)
: CalculatorNode(calc)
, m_xsh(xsh)
, m_columnIndex(columnIndex)
, m_frame(std::move(frame)) {}
double compute(double vars[3]) const override {
double f = m_frame->compute(vars);
int i = tfloor(f);
f = f - (double)i;
TXshCell cell;
cell = m_xsh->getCell(i, m_columnIndex);
int d0 = cell.isEmpty() ? 0 : cell.m_frameId.getNumber();
cell = m_xsh->getCell(i + 1, m_columnIndex);
int d1 = cell.isEmpty() ? 0 : cell.m_frameId.getNumber();
double d = (1 - f) * d0 + f * d1;
return d;
}
void accept(TSyntax::CalculatorNodeVisitor &visitor) override {
ParamReferenceFinder *prf = dynamic_cast<ParamReferenceFinder *>(&visitor);
if (prf) prf->registerColumnIndex(m_columnIndex);
}
bool hasReference() const override { return true; }
};
//===================================================================
//
// Patterns
//
//-------------------------------------------------------------------
class XsheetReferencePattern final : public Pattern {
TXsheet *m_xsh;
public:
XsheetReferencePattern(TXsheet *xsh) : m_xsh(xsh) {
setDescription(std::string("object.action\nTransformation reference\n") +
"object can be: tab, table, cam<n>, camera<n>, col<n>, "
"peg<n>, pegbar<n>\n" +
"action can be: "
"ns,ew,rot,ang,angle,z,zdepth,sx,sy,sc,scale,scalex,scaley,"
"path,pos,shx,shy");
}
TStageObjectId matchObjectName(const Token &token) const {
std::string s = toLower(token.getText());
int len = (int)s.length(), i, j;
for (i = 0; i < len && isascii(s[i]) && isalpha(s[i]); i++) {
}
if (i == 0) return TStageObjectId::NoneId;
std::string a = s.substr(0, i);
int index = 0;
for (j = i; j < len && isascii(s[j]) && isdigit(s[j]); j++)
index = index * 10 + (s[j] - '0');
if (j < len) return TStageObjectId::NoneId;
if (i == j) index = -1;
if ((a == "table" || a == "tab") && index < 0)
return TStageObjectId::TableId;
else if (a == "col" && index >= 1)
return TStageObjectId::ColumnId(index - 1);
else if ((a == "cam" || a == "camera") && index >= 1)
return TStageObjectId::CameraId(index - 1);
else if ((a == "peg" || a == "pegbar") && index >= 1)
return TStageObjectId::PegbarId(index - 1);
else
return TStageObjectId::NoneId;
}
TStageObjectId matchExistingObjectName(const Token &token) const {
TStageObjectId objId = matchObjectName(token);
if (objId != TStageObjectId::NoneId &&
m_xsh->getStageObjectTree()->getStageObject(objId, false))
return objId;
else
return TStageObjectId::NoneId;
}
TStageObject::Channel matchChannelName(const Token &token) const {
std::string s = toLower(token.getText());
if (s == "y" || s == "ns")
return TStageObject::T_Y;
else if (s == "x" || s == "ew")
return TStageObject::T_X;
else if (s == "rot" || s == "ang" || s == "angle")
return TStageObject::T_Angle;
else if (s == "z" || s == "zdepth")
return TStageObject::T_Z;
else if (s == "sx" || s == "scalex" || s == "xscale" || s == "xs" ||
s == "sh" || s == "scaleh" || s == "hscale" || s == "hs")
return TStageObject::T_ScaleX;
else if (s == "sy" || s == "scaley" || s == "yscale" || s == "ys" ||
s == "sv" || s == "scalev" || s == "vscale" || s == "vs")
return TStageObject::T_ScaleY;
else if (s == "sc" || s == "scale")
return TStageObject::T_Scale;
else if (s == "path" || s == "pos")
return TStageObject::T_Path;
else if (s == "shearx" || s == "shx" || s == "shearh" || s == "shh")
return TStageObject::T_ShearX;
else if (s == "sheary" || s == "shy" || s == "shearv" || s == "shv")
return TStageObject::T_ShearY;
else
return TStageObject::T_ChannelCount;
}
bool expressionExpected(
const std::vector<Token> &previousTokens) const override {
return previousTokens.size() == 4;
}
bool matchToken(const std::vector<Token> &previousTokens,
const Token &token) const override {
int i = (int)previousTokens.size();
if (i == 0)
return matchExistingObjectName(token) != TStageObjectId::NoneId;
else if ((i == 1 && token.getText() == ".") ||
(i == 3 && token.getText() == "(") ||
(i == 5 && token.getText() == ")"))
return true;
else if (i == 2) {
if (matchChannelName(token) < TStageObject::T_ChannelCount)
return true;
else
return token.getText() == "cell" &&
matchExistingObjectName(previousTokens[0]).isColumn();
} else
return false;
}
bool isFinished(const std::vector<Token> &previousTokens,
const Token &token) const override {
return previousTokens.size() >= 6;
}
bool isComplete(const std::vector<Token> &previousTokens,
const Token &token) const override {
return previousTokens.size() >= 6 || previousTokens.size() == 3;
}
TSyntax::TokenType getTokenType(const std::vector<Token> &previousTokens,
const Token &token) const override {
return TSyntax::Operator;
}
void getAcceptableKeywords(
std::vector<std::string> &keywords) const override {
keywords.insert(keywords.end(),
{"table", "tab", "col", "cam", "camera", "peg", "pegbar"});
}
void createNode(Calculator *calc, std::vector<CalculatorNode *> &stack,
const std::vector<Token> &tokens) const override {
assert(tokens.size() >= 3);
std::unique_ptr<CalculatorNode> frameNode(
(tokens.size() == 6) ? popNode(stack)
: new VariableNode(calc, CalculatorNode::FRAME));
TStageObjectId objectId = matchObjectName(tokens[0]);
std::string field = toLower(tokens[2].getText());
if (field == "cell" || field == "cel" || field == "cels") {
int columnIndex = objectId.getIndex();
stack.push_back(new XsheetDrawingCalculatorNode(calc, m_xsh, columnIndex,
std::move(frameNode)));
} else {
// do not create object if it does not exist
TStageObject *object =
m_xsh->getStageObjectTree()->getStageObject(objectId, false);
if (!object) return;
TStageObject::Channel channelName = matchChannelName(tokens[2]);
TDoubleParam *channel = object->getParam(channelName);
if (channel) {
if (objectId.isColumn())
stack.push_back(new ColumnParamCalculatorNode(
calc, channel, std::move(frameNode), objectId.getIndex()));
else
stack.push_back(
new ParamCalculatorNode(calc, channel, std::move(frameNode)));
}
}
}
};
//-------------------------------------------------------------------
class FxReferencePattern final : public Pattern {
TXsheet *m_xsh;
public:
FxReferencePattern(TXsheet *xsh) : m_xsh(xsh) {}
TFx *getFx(const Token &token) const {
TFx *fx =
m_xsh->getFxDag()->getFxById(::to_wstring(toLower(token.getText())));
// removed fx cannot be referenced
if (!fx)
return nullptr;
else if (fx->isZerary()) {
TZeraryFx *zFx = dynamic_cast<TZeraryFx *>(fx);
// For now we cannot use zFx->getColumnFx()->getColumnIndex() < 0 to check
// existence of the column. See a comment in tcolumnset.h for details.
if (!zFx || !zFx->getColumnFx()->getXshColumn()->inColumnsSet())
return nullptr;
} else if (!m_xsh->getFxDag()->getInternalFxs()->containsFx(fx))
return nullptr;
return fx;
}
TParam *getParam(const TFx *fx, const Token &token) const {
int i;
for (i = 0; i < fx->getParams()->getParamCount(); i++) {
TParam *param = fx->getParams()->getParam(i);
std::string paramName = ::to_string(
TStringTable::translate(fx->getFxType() + "." + param->getName()));
int i = paramName.find_first_of(" -");
while (i != std::string::npos) {
paramName.erase(i, 1);
i = paramName.find_first_of(" -");
}
std::string paramNameToCheck = token.getText();
if (paramName == paramNameToCheck ||
toLower(paramName) == toLower(paramNameToCheck))
return param;
}
return 0;
}
TParam *getLeafParam(const TFx *fx, const TParamSet *paramSet,
const Token &token) const {
int i;
for (i = 0; i < paramSet->getParamCount(); i++) {
TParam *param = paramSet->getParam(i).getPointer();
std::string paramName = param->getName();
int i = paramName.find(" ");
while (i != std::string::npos) {
paramName.erase(i, 1);
i = paramName.find(" ");
}
std::string paramNameToCheck = token.getText();
if (paramName == paramNameToCheck ||
toLower(paramName) == toLower(paramNameToCheck))
return param;
}
return 0;
}
std::string getFirstKeyword() const override { return "fx"; }
bool expressionExpected(
const std::vector<Token> &previousTokens) const override {
return !previousTokens.empty() && previousTokens.back().getText() == "(";
}
bool matchToken(const std::vector<Token> &previousTokens,
const Token &token) const override {
int i = (int)previousTokens.size();
std::string s = toLower(token.getText());
if (i == 0 && s == "fx")
return true;
else if (i == 1)
return s == ".";
else if (i & 1) {
if (previousTokens[i - 2].getText() == "(")
return s == ")";
else
return s == "." || s == "(";
} else if (i == 2) {
// nome fx
return getFx(token) != 0;
} else if (i == 4) {
TFx *fx = getFx(previousTokens[2]);
if (!fx) return false;
TParam *param = getParam(fx, token);
return !!param;
} else if (i == 6) {
TFx *fx = getFx(previousTokens[2]);
if (!fx) return false;
TParam *param = getParam(fx, previousTokens[4]);
TParamSet *paramSet = dynamic_cast<TParamSet *>(param);
if (!paramSet) return false;
TParam *leafParam = getLeafParam(fx, paramSet, token);
return !!param;
} else
return false;
}
bool isFinished(const std::vector<Token> &previousTokens,
const Token &token) const override {
return !previousTokens.empty() && previousTokens.back().getText() == ")";
}
bool isComplete(const std::vector<Token> &previousTokens,
const Token &token) const override {
int n = (int)previousTokens.size();
return n >= 2 && (n & 1) == 1 && previousTokens[n - 2].getText() != "(";
}
TSyntax::TokenType getTokenType(const std::vector<Token> &previousTokens,
const Token &token) const override {
return TSyntax::Operator;
}
void createNode(Calculator *calc, std::vector<CalculatorNode *> &stack,
const std::vector<Token> &tokens) const override {
int tokenSize = tokens.size();
std::unique_ptr<CalculatorNode> frameNode(
(tokenSize > 0 && tokens.back().getText() == ")")
? popNode(stack)
: new VariableNode(calc, CalculatorNode::FRAME));
TFx *fx = getFx(tokens[2]);
if (!fx || tokenSize < 4) return;
TParamP param = getParam(fx, tokens[4]);
if (!param.getPointer()) return;
TDoubleParamP channel;
TParamSet *paramSet = dynamic_cast<TParamSet *>(param.getPointer());
if (paramSet && tokenSize > 6)
channel =
dynamic_cast<TDoubleParam *>(getLeafParam(fx, paramSet, tokens[6]));
else
channel = param;
if (channel.getPointer())
stack.push_back(
new ParamCalculatorNode(calc, channel, std::move(frameNode)));
}
};
//-------------------------------------------------------------------
class PlasticVertexPattern final : public Pattern {
TXsheet *m_xsh;
/*
Full pattern layout:
vertex ( columnNumber , " vertexName " ) . component ( expr )
0 1 2 3 4 5 6 7 8 9 10 11 12
*/
enum Positions {
OBJECT,
L1,
COLUMN_NUMBER,
COMMA,
QUOTE1,
VERTEX_NAME,
QUOTE2,
R1,
SELECTOR,
COMPONENT,
L2,
EXPR,
R2,
POSITIONS_COUNT
};
public:
PlasticVertexPattern(TXsheet *xsh) : m_xsh(xsh) {
setDescription(
"vertex(columnNumber, \"vertexName\").action\nVertex data\n"
"columnNumber must be the number of the column containing the desired "
"skeleton\n"
"vertexName must be the name of a Plastic Skeleton vertex\n"
"action must be one of the parameter names available for a Plastic "
"Skeleton vertex");
}
std::string getFirstKeyword() const override { return "vertex"; }
bool expressionExpected(
const std::vector<Token> &previousTokens) const override {
return (previousTokens.size() == EXPR);
}
bool matchToken(const std::vector<Token> &previousTokens,
const Token &token) const override {
struct {
const PlasticVertexPattern *m_this;
const SkD *skdp(const Token &columnToken) {
int colIdx = columnToken.getIntValue() -
1; // The first column (1) actually starts at index 0
if (!m_this->m_xsh->isColumnEmpty(colIdx)) {
TStageObject *obj =
m_this->m_xsh->getStageObject(TStageObjectId::ColumnId(colIdx));
assert(obj);
if (const SkDP &skdp = obj->getPlasticSkeletonDeformation())
return skdp.getPointer();
}
return 0;
}
} locals = {this};
const std::string &text = token.getText();
int pos = previousTokens.size();
if (!m_fixedTokens[pos].empty()) return (text == m_fixedTokens[pos]);
switch (pos) {
case COLUMN_NUMBER:
return (token.getType() == Token::Number && locals.skdp(token));
case VERTEX_NAME:
if (const SkD *skdp = locals.skdp(previousTokens[COLUMN_NUMBER])) {
const QString &vertexName = QString::fromStdString(text);
return (skdp->vertexDeformation(vertexName) != 0);
}
break;
case COMPONENT:
return std::count(m_components,
m_components + sizeof(m_components) / sizeof(Component),
text) > 0;
}
return false;
}
bool isFinished(const std::vector<Token> &previousTokens,
const Token &token) const override {
return (previousTokens.size() >= POSITIONS_COUNT);
}
bool isComplete(const std::vector<Token> &previousTokens,
const Token &token) const override {
return (previousTokens.size() >= POSITIONS_COUNT ||
previousTokens.size() == L2);
}
TSyntax::TokenType getTokenType(const std::vector<Token> &previousTokens,
const Token &token) const override {
return TSyntax::Operator;
}
void createNode(Calculator *calc, std::vector<CalculatorNode *> &stack,
const std::vector<Token> &tokens) const override {
assert(tokens.size() > COMPONENT);
std::unique_ptr<CalculatorNode> frameNode(
(tokens.size() == POSITIONS_COUNT)
? popNode(stack)
: new VariableNode(calc, CalculatorNode::FRAME));
int colIdx = tokens[COLUMN_NUMBER].getIntValue() - 1;
if (!m_xsh->isColumnEmpty(colIdx)) {
TStageObject *obj =
m_xsh->getStageObject(TStageObjectId::ColumnId(colIdx));
assert(obj);
if (const SkDP &skdp = obj->getPlasticSkeletonDeformation()) {
const QString &vertexName =
QString::fromStdString(tokens[VERTEX_NAME].getText());
if (SkVD *skvd = skdp->vertexDeformation(vertexName)) {
const Component *componentsEnd = m_components + sizeof(m_components) /
sizeof(Component),
*component = std::find(m_components, componentsEnd,
tokens[COMPONENT].getText());
if (component != componentsEnd) {
const TDoubleParamP ¶m =
skvd->m_params[component->m_paramId].getPointer();
stack.push_back(
new ParamCalculatorNode(calc, param, std::move(frameNode)));
}
}
}
}
}
private:
struct Component {
std::string m_name;
SkVD::Params m_paramId;
bool operator==(const std::string &name) const { return (m_name == name); }
};
private:
static const std::string m_fixedTokens[POSITIONS_COUNT];
static const Component m_components[5];
};
const std::string PlasticVertexPattern::m_fixedTokens[POSITIONS_COUNT] = {
"vertex", "(", "", ",", "\"", "", "\"", ")", ".", "", "(", "", ")"};
const PlasticVertexPattern::Component PlasticVertexPattern::m_components[] = {
{"ang", SkVD::ANGLE},
{"angle", SkVD::ANGLE},
{"dist", SkVD::DISTANCE},
{"distance", SkVD::DISTANCE},
{"so", SkVD::SO}};
} // namespace
//******************************************************************************
// API functions
//******************************************************************************
TSyntax::Grammar *createXsheetGrammar(TXsheet *xsh) {
Grammar *grammar = new Grammar();
grammar->addPattern(new XsheetReferencePattern(xsh));
grammar->addPattern(new FxReferencePattern(xsh));
grammar->addPattern(new PlasticVertexPattern(xsh));
return grammar;
}
bool dependsOn(TExpression &expr, TDoubleParam *possiblyDependentParam) {
ParamDependencyFinder pdf(possiblyDependentParam);
expr.accept(pdf);
return pdf.found();
}
bool dependsOn(TDoubleParam *param, TDoubleParam *possiblyDependentParam) {
ParamDependencyFinder pdf(possiblyDependentParam);
param->accept(pdf);
return pdf.found();
}
void referenceParams(TExpression &expr, QSet<int> &columnIndices,
QSet<TDoubleParam *> ¶ms) {
ParamReferenceFinder prf;
expr.accept(prf);
columnIndices = prf.columnIndices();
params = prf.refParams();
}