// TnzCore includes #include "tundo.h" // TnzLib includes #include "toonz/tobjecthandle.h" #include "plastictool.h" using namespace PlasticToolLocals; //**************************************************************************************** // Undo definitions //**************************************************************************************** namespace { class AnimateValuesUndo final : public TUndo { int m_row, m_col; //!< Xsheet coordinates int m_v; //!< Moved vertex public: SkDKey m_oldValues, m_newValues; //!< Keyframe values public: AnimateValuesUndo(int v) : m_row(::row()), m_col(::column()), m_v(v) {} // Again, not accurate. We should get in the details of SkDF... So, let's say // around 10 kB - max 10k instances in the standard undos pool. int getSize() const override { return 10 << 10; } void redo() const override { PlasticTool::TemporaryActivation tempActivate(m_row, m_col); if (m_v >= 0) l_plasticTool.setSkeletonSelection(m_v); l_suspendParamsObservation = true; // Coalesce params change notifications into one l_plasticTool.deformation()->deleteKeyframe(m_row - 1); l_plasticTool.deformation()->setKeyframe(m_newValues); l_suspendParamsObservation = false; l_plasticTool.onChange(); } void undo() const override { PlasticTool::TemporaryActivation tempActivate(m_row, m_col); if (m_v >= 0) l_plasticTool.setSkeletonSelection(m_v); l_suspendParamsObservation = true; // Coalesce params change notifications into one l_plasticTool.deformation()->deleteKeyframe( m_row - 1); // Yep. Typical frame/row shift... xD l_plasticTool.deformation()->setKeyframe(m_oldValues); l_suspendParamsObservation = false; l_plasticTool.onChange(); } }; } // namespace //**************************************************************************************** // PlasticTool functions //**************************************************************************************** void PlasticTool::mouseMove_animate(const TPointD &pos, const TMouseEvent &me) { // Track mouse position m_pos = pos; // Needs to be done now - ensures m_pos is valid m_svHigh = m_seHigh = -1; // Reset highlighted primitives if (m_sd) { double d, highlightRadius = getPixelSize() * HIGHLIGHT_DISTANCE; // Look for nearest vertex int v = deformedSkeleton().closestVertex(pos, &d); if (v >= 0 && d < highlightRadius) m_svHigh = v; invalidate(); } } //------------------------------------------------------------------------ void PlasticTool::leftButtonDown_animate(const TPointD &pos, const TMouseEvent &me) { // Track mouse position m_pressedPos = m_pos = pos; setSkeletonSelection(m_svHigh); if (m_svSel.hasSingleObject()) { // Store original vertex position and keyframe values m_pressedVxsPos = std::vector(1, deformedSkeleton().vertex(m_svSel).P()); m_sd->getKeyframeAt(frame(), m_pressedSkDF); } invalidate(); } //------------------------------------------------------------------------ void PlasticTool::leftButtonDrag_animate(const TPointD &pos, const TMouseEvent &me) { // Track mouse position m_pos = pos; if (m_sd && m_svSel.hasSingleObject() && m_svSel > 0) // Avoid move if vertex is root { l_suspendParamsObservation = true; // Automatic params notification happen // twice (1 x param) - dealing with it manually double frame = ::frame(); // First, retrieve selected vertex's deformation SkVD *vd = m_sd->vertexDeformation(::skeletonId(), m_svSel); assert(vd); // Move selected branch if (m_keepDistance.getValue()) { ::setKeyframe(vd->m_params[SkVD::ANGLE], frame); // Set a keyframe for it. It must be done // to set the correct function interpolation // type and other stuff. m_sd->updateAngle(*skeleton(), deformedSkeleton(), frame, m_svSel, pos); } else { ::setKeyframe(vd->m_params[SkVD::ANGLE], frame); // Same here. NOTE: Not setting a frame on ::setKeyframe(vd->m_params[SkVD::DISTANCE], frame); // vd directly due to SkVD::SO m_sd->updatePosition(*skeleton(), deformedSkeleton(), frame, m_svSel, pos); } l_suspendParamsObservation = false; // onChange(); // Due to // a nasty Function Editor dependency, // it's better to call the following directly m_deformedSkeleton.invalidate(); invalidate(); } } //------------------------------------------------------------------------ void PlasticTool::leftButtonUp_animate(const TPointD &pos, const TMouseEvent &me) { // Track mouse position m_pos = pos; if (m_svSel.hasSingleObject() && m_dragged) { // Set a keyframe to each skeleton vertex, if that was requested if (m_globalKey.getValue()) ::setKeyframe(m_sd, ::frame()); // Already invokes keyframes rebuild else stageObject()->updateKeyframes(); // Otherwise, must be explicit // Add a corresponding undo AnimateValuesUndo *undo = new AnimateValuesUndo(m_svSel); undo->m_oldValues = m_pressedSkDF; m_sd->getKeyframeAt(frame(), undo->m_newValues); TUndoManager::manager()->add(undo); // This is needed to refresh the xsheet (there may be new keyframes) TTool::getApplication()->getCurrentObject()->notifyObjectIdChanged(false); } // In case one of the vertices is attached (as a hook) to an external // position, // we need to update the whole skeleton according to the updated vertex. updateMatrix(); invalidate(); } //------------------------------------------------------------------------ void PlasticTool::addContextMenuActions_animate(QMenu *menu) { bool ret = true; if (m_sd.getPointer() == nullptr) return; if (!m_svSel.isEmpty()) { QAction *setKey = menu->addAction(tr("Set Key")); ret = ret && connect(setKey, SIGNAL(triggered()), &l_plasticTool, SLOT(setKey_undo())); QAction *setRestKey = menu->addAction(tr("Set Rest Key")); ret = ret && connect(setRestKey, SIGNAL(triggered()), &l_plasticTool, SLOT(setRestKey_undo())); } QAction *setGlobalKey = menu->addAction(tr("Set Global Key")); ret = ret && connect(setGlobalKey, SIGNAL(triggered()), &l_plasticTool, SLOT(setGlobalKey_undo())); QAction *setGlobalRestKey = menu->addAction(tr("Set Global Rest Key")); ret = ret && connect(setGlobalRestKey, SIGNAL(triggered()), &l_plasticTool, SLOT(setGlobalRestKey_undo())); menu->addSeparator(); assert(ret); } //------------------------------------------------------------------------ void PlasticTool::keyFunc_undo(void (PlasticTool::*keyFunc)()) { assert(m_svSel.objects().size() <= 1); double frame = ::frame(); AnimateValuesUndo *undo = new AnimateValuesUndo(m_svSel); m_sd->getKeyframeAt(frame, undo->m_oldValues); (this->*keyFunc)(); m_sd->getKeyframeAt(frame, undo->m_newValues); TUndoManager::manager()->add(undo); } //------------------------------------------------------------------------ void PlasticTool::draw_animate() { double pixelSize = getPixelSize(); PlasticSkeleton &deformedSkeleton = this->deformedSkeleton(); // Draw deformed skeleton if (m_sd) { drawOnionSkinSkeletons_animate(pixelSize); drawSkeleton(deformedSkeleton, pixelSize); drawSelections(m_sd, deformedSkeleton, pixelSize); drawAngleLimits(m_sd, m_skelId, m_svSel, pixelSize); } drawHighlights(m_sd, &deformedSkeleton, pixelSize); }