diff --git a/toonz/sources/common/tvectorimage/tcomputeregions.cpp b/toonz/sources/common/tvectorimage/tcomputeregions.cpp index 8235ce5..f9ed60a 100644 --- a/toonz/sources/common/tvectorimage/tcomputeregions.cpp +++ b/toonz/sources/common/tvectorimage/tcomputeregions.cpp @@ -970,8 +970,10 @@ void markDeadIntersections(VIList<Intersection> &intList, Intersection *p) { if (!p1) return; if (p->m_numInter == 1) { - while (p1 && !!p1->m_nextIntersection) p1 = p1->next(); - assert(p1); + while (p1 && !!p1->m_nextIntersection) { + p1 = p1->next(); + } + // assert(p1); if (!p1) return; Intersection *nextInt = p1->m_nextIntersection; diff --git a/toonz/sources/include/tools/tool.h b/toonz/sources/include/tools/tool.h index fb95237..a2f66ef 100644 --- a/toonz/sources/include/tools/tool.h +++ b/toonz/sources/include/tools/tool.h @@ -638,6 +638,10 @@ public: virtual double projectToZ(const TPoint &delta) = 0; virtual TPointD getDpiScale() const = 0; + virtual int getVGuideCount() = 0; + virtual int getHGuideCount() = 0; + virtual double getHGuide(int index) = 0; + virtual double getVGuide(int index) = 0; virtual void resetInputMethod() = 0; // Intended to call QWidget->resetInputContext() diff --git a/toonz/sources/include/tools/tooloptions.h b/toonz/sources/include/tools/tooloptions.h index 92ca7a3..c14981c 100644 --- a/toonz/sources/include/tools/tooloptions.h +++ b/toonz/sources/include/tools/tooloptions.h @@ -475,6 +475,8 @@ class BrushToolOptionsBox final : public ToolOptionsBox { ToolOptionPopupButton *m_joinStyleCombo; ToolOptionIntSlider *m_miterField; ToolOptionCombo *m_presetCombo; + ToolOptionCheckbox *m_snapCheckbox; + ToolOptionCombo *m_snapSensitivityCombo; QPushButton *m_addPresetButton; QPushButton *m_removePresetButton; diff --git a/toonz/sources/include/toonz/preferences.h b/toonz/sources/include/toonz/preferences.h index fadefd0..5df3065 100644 --- a/toonz/sources/include/toonz/preferences.h +++ b/toonz/sources/include/toonz/preferences.h @@ -76,6 +76,8 @@ public: header) */ }; + enum SnappingTarge { SnapStrokes, SnapGuides, SnapAll }; + public: static Preferences *instance(); @@ -321,6 +323,9 @@ public: return m_useNumpadForSwitchingStyles; } + void setVectorSnappingTarget(int target); + int getVectorSnappingTarget() { return m_vectorSnappingTarget; } + // Xsheet tab void setXsheetStep(int step); //!< Sets the step used for the <I>next/prev @@ -501,7 +506,7 @@ private: int m_currentLanguage, m_currentStyleSheet, m_undoMemorySize, // in megabytes m_dragCellsBehaviour, m_lineTestFpsCapture, m_defLevelType, m_xsheetStep, - m_shmmax, m_shmseg, m_shmall, m_shmmni; + m_shmmax, m_shmseg, m_shmall, m_shmmni, m_vectorSnappingTarget; bool m_autoExposeEnabled, m_autoCreateEnabled, m_subsceneFolderEnabled, m_generatedMovieViewEnabled, m_xsheetAutopanEnabled, diff --git a/toonz/sources/include/toonz/strokegenerator.h b/toonz/sources/include/toonz/strokegenerator.h index 84b69dd..916c2ab 100644 --- a/toonz/sources/include/toonz/strokegenerator.h +++ b/toonz/sources/include/toonz/strokegenerator.h @@ -69,6 +69,8 @@ public: */ void add(const TThickPoint &point, double pixelSize2); + TPointD getFirstPoint(); // returns the first point + //! Filtra i punti di m_points /*! Verifica se i primi sei e gli ultimi sei punti successivi hanno una diff --git a/toonz/sources/tnztools/brushtool.cpp b/toonz/sources/tnztools/brushtool.cpp index 54c1a72..d6b4f21 100644 --- a/toonz/sources/tnztools/brushtool.cpp +++ b/toonz/sources/tnztools/brushtool.cpp @@ -27,6 +27,7 @@ #include "toonz/toonzimageutils.h" #include "toonz/palettecontroller.h" #include "toonz/stage2.h" +#include "tw/keycodes.h" #include "toonz/preferences.h" // TnzCore includes @@ -35,7 +36,8 @@ #include "tvectorimage.h" #include "tenv.h" #include "tregion.h" -#include "tstroke.h" +#include "tinbetween.h" + #include "tgl.h" #include "trop.h" @@ -58,6 +60,9 @@ TEnv::IntVar BrushBreakSharpAngles("InknpaintBrushBreakSharpAngles", 0); TEnv::IntVar RasterBrushPencilMode("InknpaintRasterBrushPencilMode", 0); TEnv::IntVar BrushPressureSensitivity("InknpaintBrushPressureSensitivity", 1); TEnv::DoubleVar RasterBrushHardness("RasterBrushHardness", 100); +TEnv::IntVar VectorBrushFrameRange("VectorBrushFrameRange", 0); +TEnv::IntVar VectorBrushSnap("VectorBrushSnap", 0); +TEnv::IntVar VectorBrushSnapSensitivity("VectorBrushSnapSensitivity", 0); //------------------------------------------------------------------- @@ -69,6 +74,18 @@ TEnv::DoubleVar RasterBrushHardness("RasterBrushHardness", 100); #define MITER_WSTR L"miter_join" #define CUSTOM_WSTR L"<custom>" +#define LINEAR_WSTR L"Linear" +#define EASEIN_WSTR L"In" +#define EASEOUT_WSTR L"Out" +#define EASEINOUT_WSTR L"In&Out" + +#define LOW_WSTR L"Low" +#define MEDIUM_WSTR L"Med" +#define HIGH_WSTR L"High" + +const double SNAPPING_LOW = 5.0; +const double SNAPPING_MEDIUM = 25.0; +const double SNAPPING_HIGH = 100.0; //------------------------------------------------------------------- // // (Da mettere in libreria) : funzioni che spezzano una stroke @@ -762,7 +779,12 @@ BrushTool::BrushTool(std::string name, int targetType) , m_enabled(false) , m_isPrompting(false) , m_firstTime(true) + , m_firstFrameRange(true) , m_presetsLoaded(false) + , m_frameRange("Range:") + , m_snap("Snap", false) + , m_snapSensitivity("Sensitivity:") + , m_targetType(targetType) , m_workingFrameId(TFrameId()) { bind(targetType); @@ -785,6 +807,24 @@ BrushTool::BrushTool(std::string name, int targetType) } m_prop[0].bind(m_pressure); + + if (targetType & TTool::Vectors) { + m_frameRange.addValue(L"Off"); + m_frameRange.addValue(LINEAR_WSTR); + m_frameRange.addValue(EASEIN_WSTR); + m_frameRange.addValue(EASEOUT_WSTR); + m_frameRange.addValue(EASEINOUT_WSTR); + m_prop[0].bind(m_frameRange); + m_frameRange.setId("FrameRange"); + m_prop[0].bind(m_snap); + m_snap.setId("Snap"); + m_snapSensitivity.addValue(LOW_WSTR); + m_snapSensitivity.addValue(MEDIUM_WSTR); + m_snapSensitivity.addValue(HIGH_WSTR); + m_prop[0].bind(m_snapSensitivity); + m_snapSensitivity.setId("SnapSensitivity"); + } + m_prop[0].bind(m_preset); m_preset.setId("BrushPreset"); m_preset.addValue(CUSTOM_WSTR); @@ -978,6 +1018,9 @@ void BrushTool::updateTranslation() { m_capStyle.setQStringName(tr("Cap")); m_joinStyle.setQStringName(tr("Join")); m_miterJoinLimit.setQStringName(tr("Miter:")); + m_frameRange.setQStringName("Range:"); + m_snap.setQStringName("Snap"); + m_snapSensitivity.setQStringName(""); } //--------------------------------------------------------------------------------------------------- @@ -1025,12 +1068,29 @@ void BrushTool::onActivate() { m_accuracy.setValue(BrushAccuracy); m_smooth.setValue(BrushSmooth); m_hardness.setValue(RasterBrushHardness); + if (m_targetType & TTool::Vectors) { + m_frameRange.setIndex(VectorBrushFrameRange); + m_snap.setValue(VectorBrushSnap); + m_snapSensitivity.setIndex(VectorBrushSnapSensitivity); + switch (VectorBrushSnapSensitivity) { + case 0: + m_minDistance2 = SNAPPING_LOW; + break; + case 1: + m_minDistance2 = SNAPPING_MEDIUM; + break; + case 2: + m_minDistance2 = SNAPPING_HIGH; + break; + } + } } if (m_targetType & TTool::ToonzImage) { m_brushPad = ToolUtils::getBrushPad(m_rasThickness.getValue().second, m_hardness.getValue() * 0.01); setWorkAndBackupImages(); } + resetFrameRange(); // TODO:app->editImageOrSpline(); } @@ -1052,6 +1112,7 @@ void BrushTool::onDeactivate() { } m_workRas = TRaster32P(); m_backupRas = TRasterCM32P(); + resetFrameRange(); } //-------------------------------------------------------------------------------------------------- @@ -1165,7 +1226,7 @@ void BrushTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) { invalidate(invalidateRect); } - } else { + } else { // vector happens here m_track.clear(); double thickness = (m_pressure.getValue() || m_isPath) @@ -1174,10 +1235,16 @@ void BrushTool::leftButtonDown(const TPointD &pos, const TMouseEvent &e) { /*--- ストロークの最初にMaxサイズの円が描かれてしまう不具合を防止する ---*/ if (m_pressure.getValue() && e.m_pressure == 255) - thickness = m_rasThickness.getValue().first; - + thickness = m_rasThickness.getValue().first; + m_currThickness = thickness; m_smoothStroke.beginStroke(m_smooth.getValue()); - addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize()); + + if ((m_targetType & TTool::Vectors) && m_foundFirstSnap) { + addTrackPoint(TThickPoint(m_firstSnapPoint, thickness), + getPixelSize() * getPixelSize()); + } else + addTrackPoint(TThickPoint(pos, thickness), + getPixelSize() * getPixelSize()); } } @@ -1296,14 +1363,26 @@ void BrushTool::leftButtonDrag(const TPointD &pos, const TMouseEvent &e) { ? computeThickness(e.m_pressure, m_thickness, m_isPath) : m_thickness.getValue().second * 0.5; + m_currThickness = thickness; + + m_mousePos = pos; + m_lastSnapPoint = pos; + m_foundLastSnap = false; + checkStrokeSnapping(false); + checkGuideSnapping(false); + m_brushPos = m_lastSnapPoint; + if (e.isShiftPressed()) { m_smoothStroke.clearPoints(); - m_track.add(TThickPoint(pos, thickness), getPixelSize() * getPixelSize()); + m_track.add(TThickPoint(m_brushPos, thickness), + getPixelSize() * getPixelSize()); m_track.removeMiddlePoints(); - } else { + } + + else if (m_dragDraw) addTrackPoint(TThickPoint(pos, thickness), getPixelSize() * getPixelSize()); - } + invalidate(); } } @@ -1369,6 +1448,18 @@ void BrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) { m_track.clear(); return; } + + if (vi && m_snap.getValue() && m_foundLastSnap) { + addTrackPoint(TThickPoint(m_lastSnapPoint, m_currThickness), + getPixelSize() * getPixelSize()); + } + m_strokeIndex1 = -1; + m_strokeIndex2 = -1; + m_w1 = -1; + m_w2 = -2; + m_foundFirstSnap = false; + m_foundLastSnap = false; + m_track.filterPoints(); double error = 30.0 / (1 + 0.5 * m_accuracy.getValue()); error *= getPixelSize(); @@ -1399,13 +1490,77 @@ void BrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) { // autoclose proprio dal fatto che // hanno 1 solo chunk. stroke->insertControlPoints(0.5); + if (m_frameRange.getIndex()) { + if (m_firstFrameId == -1) { + m_firstStroke = new TStroke(*stroke); + m_firstFrameId = getFrameId(); + TTool::Application *application = TTool::getApplication(); + if (application) { + m_col = application->getCurrentColumn()->getColumnIndex(); + m_firstFrame = application->getCurrentFrame()->getFrame(); + } + m_rangeTrack = m_track; + if (m_firstFrameRange) { + m_veryFirstCol = m_col; + m_veryFirstFrame = m_firstFrame; + m_veryFirstFrameId = m_firstFrameId; + } + } else if (m_firstFrameId == getFrameId()) { + if (m_firstStroke) { + delete m_firstStroke; + m_firstStroke = 0; + } + m_firstStroke = new TStroke(*stroke); + m_rangeTrack = m_track; + } else { + TFrameId currentId = getFrameId(); + int curCol = 0, curFrame = 0; + TTool::Application *application = TTool::getApplication(); + if (application) { + curCol = application->getCurrentColumn()->getColumnIndex(); + curFrame = application->getCurrentFrame()->getFrame(); + } + bool success = + doFrameRangeStrokes(m_firstFrameId, m_firstStroke, getFrameId(), + stroke, m_firstFrameRange); + if (e.isCtrlPressed()) { + if (application) { + if (m_firstFrameId > currentId) { + if (application->getCurrentFrame()->isEditingScene()) { + application->getCurrentColumn()->setColumnIndex(curCol); + application->getCurrentFrame()->setFrame(curFrame); + } else + application->getCurrentFrame()->setFid(currentId); + } + } + resetFrameRange(); + m_firstStroke = new TStroke(*stroke); + m_rangeTrack = m_track; + m_firstFrameId = currentId; + m_firstFrameRange = false; + } - addStrokeToImage(getApplication(), vi, stroke, m_breakAngles.getValue(), - m_isFrameCreated, m_isLevelCreated); - TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion(); - invalidate(); + if (application && !e.isCtrlPressed()) { + if (application->getCurrentFrame()->isEditingScene()) { + application->getCurrentColumn()->setColumnIndex(m_veryFirstCol); + application->getCurrentFrame()->setFrame(m_veryFirstFrame); + } else + application->getCurrentFrame()->setFid(m_veryFirstFrameId); + } + + if (!e.isCtrlPressed()) { + resetFrameRange(); + } + } + } else { + addStrokeToImage(getApplication(), vi, stroke, m_breakAngles.getValue(), + m_isFrameCreated, m_isLevelCreated); + TRectD bbox = stroke->getBBox().enlarge(2) + m_track.getModifiedRegion(); + invalidate(); + } assert(stroke); m_track.clear(); + } else if (TToonzImageP ti = image) { finishRasterBrush(pos, e.m_pressure); } @@ -1413,6 +1568,111 @@ void BrushTool::leftButtonUp(const TPointD &pos, const TMouseEvent &e) { //-------------------------------------------------------------------------------------------------- +bool BrushTool::keyDown(int key, TUINT32 b, const TPoint &point) { + if (key == TwConsts::TK_Esc) { + resetFrameRange(); + } + return true; +} + +//-------------------------------------------------------------------------------------------------- + +bool BrushTool::doFrameRangeStrokes(TFrameId firstFrameId, TStroke *firstStroke, + TFrameId lastFrameId, TStroke *lastStroke, + bool drawFirstStroke) { + TXshSimpleLevel *sl = + TTool::getApplication()->getCurrentLevel()->getLevel()->getSimpleLevel(); + TStroke *first = new TStroke(); + TStroke *last = new TStroke(); + TVectorImageP firstImage = new TVectorImage(); + TVectorImageP lastImage = new TVectorImage(); + + *first = *firstStroke; + *last = *lastStroke; + bool swapped = false; + if (firstFrameId > lastFrameId) { + tswap(firstFrameId, lastFrameId); + *first = *lastStroke; + *last = *firstStroke; + swapped = true; + } + + firstImage->addStroke(first); + lastImage->addStroke(last); + assert(firstFrameId <= lastFrameId); + + std::vector<TFrameId> allFids; + sl->getFids(allFids); + std::vector<TFrameId>::iterator i0 = allFids.begin(); + while (i0 != allFids.end() && *i0 < firstFrameId) i0++; + if (i0 == allFids.end()) return false; + std::vector<TFrameId>::iterator i1 = i0; + while (i1 != allFids.end() && *i1 <= lastFrameId) i1++; + assert(i0 < i1); + std::vector<TFrameId> fids(i0, i1); + int m = fids.size(); + assert(m > 0); + + TUndoManager::manager()->beginBlock(); + for (int i = 0; i < m; ++i) { + TFrameId fid = fids[i]; + assert(firstFrameId <= fid && fid <= lastFrameId); + + // This is an attempt to divide the tween evenly + double t = m > 1 ? (double)i / (double)(m - 1) : 0.5; + double s = t; + switch (m_frameRange.getIndex()) { + case 1: // LINEAR_WSTR + break; + case 2: // EASEIN_WSTR + s = t * t; + break; // s'(0) = 0 + case 3: // EASEOUT_WSTR + s = t * (2 - t); + break; // s'(1) = 0 + case 4: // EASEINOUT_WSTR: + s = t * t * (3 - 2 * t); + break; // s'(0) = s'(1) = 0 + } + + TTool::Application *app = TTool::getApplication(); + if (app) { + if (app->getCurrentFrame()->isEditingScene()) + app->getCurrentFrame()->setFrame(fid.getNumber() - 1); + else + app->getCurrentFrame()->setFid(fid); + } + + TVectorImageP img = sl->getFrame(fid, true); + if (t == 0) { + if (!swapped && !drawFirstStroke) { + } else + addStrokeToImage(getApplication(), img, firstImage->getStroke(0), + m_breakAngles.getValue(), m_isFrameCreated, + m_isLevelCreated); + } else if (t == 1) { + if (swapped && !drawFirstStroke) { + } else + addStrokeToImage(getApplication(), img, lastImage->getStroke(0), + m_breakAngles.getValue(), m_isFrameCreated, + m_isLevelCreated); + } else { + assert(firstImage->getStrokeCount() == 1); + assert(lastImage->getStrokeCount() == 1); + TVectorImageP vi = TInbetween(firstImage, lastImage).tween(s); + assert(vi->getStrokeCount() == 1); + addStrokeToImage(getApplication(), img, vi->getStroke(0), + m_breakAngles.getValue(), m_isFrameCreated, + m_isLevelCreated); + } + } + TUndoManager::manager()->endBlock(); + notifyImageChanged(); + return true; +} + +//-------------------------------------------------------------------------------------------------- + void BrushTool::addTrackPoint(const TThickPoint &point, double pixelSize2) { m_smoothStroke.addPoint(point); std::vector<TThickPoint> pts; @@ -1686,8 +1946,15 @@ void BrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) { max); } else { m_brushPos = pos; + + m_mousePos = pos; + m_firstSnapPoint = pos; + m_foundFirstSnap = false; + + checkStrokeSnapping(true); + checkGuideSnapping(true); + m_brushPos = m_firstSnapPoint; } - m_mousePos = pos; invalidate(); if (m_minThick == 0 && m_maxThick == 0) { @@ -1703,6 +1970,124 @@ void BrushTool::mouseMove(const TPointD &pos, const TMouseEvent &e) { //------------------------------------------------------------------------------------------------------------- +void BrushTool::checkStrokeSnapping(bool beforeMousePress) { + if (Preferences::instance()->getVectorSnappingTarget() == 1) return; + + TVectorImageP vi(getImage(false)); + if (vi && m_snap.getValue()) { + m_dragDraw = true; + double minDistance2 = m_minDistance2; + if (beforeMousePress) + m_strokeIndex1 = -1; + else + m_strokeIndex2 = -1; + int i, strokeNumber = vi->getStrokeCount(); + TStroke *stroke; + double distance2, outW; + + for (i = 0; i < strokeNumber; i++) { + stroke = vi->getStroke(i); + if (stroke->getNearestW(m_mousePos, outW, distance2) && + distance2 < minDistance2) { + minDistance2 = distance2; + beforeMousePress ? m_strokeIndex1 = i : m_strokeIndex2 = i; + if (areAlmostEqual(outW, 0.0, 1e-3)) + beforeMousePress ? m_w1 = 0.0 : m_w2 = 0.0; + else if (areAlmostEqual(outW, 1.0, 1e-3)) + beforeMousePress ? m_w1 = 1.0 : m_w2 = 1.0; + else + beforeMousePress ? m_w1 = outW : m_w2 = outW; + TThickPoint point1; + beforeMousePress ? point1 = stroke->getPoint(m_w1) + : point1 = stroke->getPoint(m_w2); + if (beforeMousePress) { + m_firstSnapPoint = TPointD(point1.x, point1.y); + m_foundFirstSnap = true; + } else { + m_lastSnapPoint = TPointD(point1.x, point1.y); + m_foundLastSnap = true; + if (distance2 < 2.0) m_dragDraw = false; + } + } + } + } +} + +//------------------------------------------------------------------------------------------------------------- + +void BrushTool::checkGuideSnapping(bool beforeMousePress) { + if (Preferences::instance()->getVectorSnappingTarget() == 0) return; + bool foundSnap; + TPointD snapPoint; + beforeMousePress ? foundSnap = m_foundFirstSnap : foundSnap = m_foundLastSnap; + beforeMousePress ? snapPoint = m_firstSnapPoint : snapPoint = m_lastSnapPoint; + if ((m_targetType & TTool::Vectors) && m_snap.getValue()) { + // check guide snapping + int vGuideCount = 0, hGuideCount = 0; + double guideDistance = sqrt(m_minDistance2); + TTool::Viewer *viewer = getViewer(); + if (viewer) { + vGuideCount = viewer->getVGuideCount(); + hGuideCount = viewer->getHGuideCount(); + } + double distanceToVGuide = -1.0, distanceToHGuide = -1.0; + double vGuide, hGuide; + bool useGuides = false; + if (vGuideCount) { + for (int j = 0; j < vGuideCount; j++) { + double guide = viewer->getVGuide(j); + double tempDistance = abs(guide - m_mousePos.y); + if (tempDistance < guideDistance && + (distanceToVGuide < 0 || tempDistance < distanceToVGuide)) { + distanceToVGuide = tempDistance; + vGuide = guide; + useGuides = true; + } + } + } + if (hGuideCount) { + for (int j = 0; j < hGuideCount; j++) { + double guide = viewer->getHGuide(j); + double tempDistance = abs(guide - m_mousePos.x); + if (tempDistance < guideDistance && + (distanceToHGuide < 0 || tempDistance < distanceToHGuide)) { + distanceToHGuide = tempDistance; + hGuide = guide; + useGuides = true; + } + } + } + if (useGuides && foundSnap) { + double currYDistance = abs(snapPoint.y - m_mousePos.y); + double currXDistance = abs(snapPoint.x - m_mousePos.x); + double hypotenuse = + sqrt(pow(currYDistance, 2.0) + pow(currXDistance, 2.0)); + if ((distanceToVGuide >= 0 && distanceToVGuide < hypotenuse) || + (distanceToHGuide >= 0 && distanceToHGuide < hypotenuse)) + useGuides = true; + else + useGuides = false; + } + if (useGuides) { + assert(distanceToHGuide >= 0 || distanceToVGuide >= 0); + if (distanceToHGuide < 0 || + (distanceToVGuide <= distanceToHGuide && distanceToVGuide >= 0)) { + snapPoint.y = vGuide; + snapPoint.x = m_mousePos.x; + + } else { + snapPoint.y = m_mousePos.y; + snapPoint.x = hGuide; + } + beforeMousePress ? m_foundFirstSnap = true : m_foundLastSnap = true; + beforeMousePress ? m_firstSnapPoint = snapPoint : m_lastSnapPoint = + snapPoint; + } + } +} + +//------------------------------------------------------------------------------------------------------------- + void BrushTool::draw() { /*--ショートカットでのツール切り替え時に赤点が描かれるのを防止する--*/ if (m_minThick == 0 && m_maxThick == 0 && @@ -1715,6 +2100,38 @@ void BrushTool::draw() { tglColor(m_isPrompting ? TPixel32::Green : m_currentColor); m_track.drawAllFragments(); + // snapping + TVectorImageP vi = img; + if ((m_targetType & TTool::Vectors) && m_snap.getValue()) { + m_pixelSize = getPixelSize(); + double thick = 6.0 * m_pixelSize; + if (m_foundFirstSnap) { + tglColor(TPixelD(0.1, 0.9, 0.1)); + tglDrawCircle(m_firstSnapPoint, thick); + } + + TThickPoint point2; + + if (m_foundLastSnap) { + tglColor(TPixelD(0.1, 0.9, 0.1)); + tglDrawCircle(m_lastSnapPoint, thick); + } + } + + // frame range + if (m_firstStroke) { + glColor3d(1.0, 0.0, 0.0); + m_rangeTrack.drawAllFragments(); + glColor3d(0.0, 0.6, 0.0); + TPointD firstPoint = m_rangeTrack.getFirstPoint(); + TPointD topLeftCorner = TPointD(firstPoint.x - 5, firstPoint.y - 5); + TPointD topRightCorner = TPointD(firstPoint.x + 5, firstPoint.y - 5); + TPointD bottomLeftCorner = TPointD(firstPoint.x - 5, firstPoint.y + 5); + TPointD bottomRightCorner = TPointD(firstPoint.x + 5, firstPoint.y + 5); + tglDrawSegment(topLeftCorner, bottomRightCorner); + tglDrawSegment(topRightCorner, bottomLeftCorner); + } + if (getApplication()->getCurrentObject()->isSpline()) return; // Draw the brush outline - change color when the Ink / Paint check is @@ -1822,6 +2239,18 @@ void BrushTool::setWorkAndBackupImages() { //------------------------------------------------------------------ +void BrushTool::resetFrameRange() { + m_rangeTrack.clear(); + m_firstFrameId = -1; + if (m_firstStroke) { + delete m_firstStroke; + m_firstStroke = 0; + } + m_firstFrameRange = true; +} + +//------------------------------------------------------------------ + bool BrushTool::onPropertyChanged(std::string propertyName) { // Set the following to true whenever a different piece of interface must // be refreshed - done once at the end. @@ -1869,6 +2298,26 @@ bool BrushTool::onPropertyChanged(std::string propertyName) { VectorJoinStyle = m_joinStyle.getIndex(); } else if (propertyName == m_miterJoinLimit.getName()) { VectorMiterValue = m_miterJoinLimit.getValue(); + } else if (propertyName == m_frameRange.getName()) { + int index = m_frameRange.getIndex(); + VectorBrushFrameRange = index; + if (index == 0) resetFrameRange(); + } else if (propertyName == m_snap.getName()) { + VectorBrushSnap = m_snap.getValue(); + } else if (propertyName == m_snapSensitivity.getName()) { + int index = m_snapSensitivity.getIndex(); + VectorBrushSnapSensitivity = index; + switch (index) { + case 0: + m_minDistance2 = SNAPPING_LOW; + break; + case 1: + m_minDistance2 = SNAPPING_MEDIUM; + break; + case 2: + m_minDistance2 = SNAPPING_HIGH; + break; + } } if (m_targetType & TTool::Vectors) { @@ -1942,6 +2391,9 @@ void BrushTool::loadPreset() { m_capStyle.setIndex(preset.m_cap); m_joinStyle.setIndex(preset.m_join); m_miterJoinLimit.setValue(preset.m_miter); + m_frameRange.setIndex(preset.m_frameRange); + m_snap.setValue(preset.m_snap); + m_snapSensitivity.setIndex(preset.m_snapSensitivity); } else { m_rasThickness.setValue(TDoublePairProperty::Value( std::max(preset.m_min, 1.0), preset.m_max)); @@ -1971,16 +2423,19 @@ void BrushTool::addPreset(QString name) { preset.m_max = m_rasThickness.getValue().second; } - preset.m_acc = m_accuracy.getValue(); - preset.m_smooth = m_smooth.getValue(); - preset.m_hardness = m_hardness.getValue(); - preset.m_selective = m_selective.getValue(); - preset.m_pencil = m_pencil.getValue(); - preset.m_breakAngles = m_breakAngles.getValue(); - preset.m_pressure = m_pressure.getValue(); - preset.m_cap = m_capStyle.getIndex(); - preset.m_join = m_joinStyle.getIndex(); - preset.m_miter = m_miterJoinLimit.getValue(); + preset.m_acc = m_accuracy.getValue(); + preset.m_smooth = m_smooth.getValue(); + preset.m_hardness = m_hardness.getValue(); + preset.m_selective = m_selective.getValue(); + preset.m_pencil = m_pencil.getValue(); + preset.m_breakAngles = m_breakAngles.getValue(); + preset.m_pressure = m_pressure.getValue(); + preset.m_cap = m_capStyle.getIndex(); + preset.m_join = m_joinStyle.getIndex(); + preset.m_miter = m_miterJoinLimit.getValue(); + preset.m_frameRange = m_frameRange.getIndex(); + preset.m_snap = m_snap.getValue(); + preset.m_snapSensitivity = m_snapSensitivity.getIndex(); // Pass the preset to the manager m_presetsManager.addPreset(preset); @@ -2038,10 +2493,12 @@ BrushData::BrushData() , m_pressure(false) , m_cap(0) , m_join(0) + , m_frameRange(0) + , m_snap(false) + , m_snapSensitivity(0) , m_miter(0) , m_modifierSize(0.0) - , m_modifierOpacity(0.0) - {} + , m_modifierOpacity(0.0) {} //---------------------------------------------------------------------------------------------------------- @@ -2060,10 +2517,12 @@ BrushData::BrushData(const std::wstring &name) , m_pressure(false) , m_cap(0) , m_join(0) + , m_frameRange(0) + , m_snap(false) + , m_snapSensitivity(0) , m_miter(0) , m_modifierSize(0.0) - , m_modifierOpacity(0.0) - {} + , m_modifierOpacity(0.0) {} //---------------------------------------------------------------------------------------------------------- @@ -2106,6 +2565,12 @@ void BrushData::saveData(TOStream &os) { os.closeChild(); os.openChild("Miter"); os << m_miter; + os.openChild("Frame_Range"); + os << (int)m_frameRange; + os.openChild("Snap"); + os << (int)m_snap; + os.openChild("SnapSensitivity"); + os << (int)m_snapSensitivity; os.closeChild(); os.openChild("Modifier_Size"); os << m_modifierSize; @@ -2148,6 +2613,12 @@ void BrushData::loadData(TIStream &is) { is >> m_join, is.matchEndTag(); else if (tagName == "Miter") is >> m_miter, is.matchEndTag(); + else if (tagName == "Frame_Range") + is >> m_frameRange, is.matchEndTag(); + else if (tagName == "Snap") + is >> m_snap, is.matchEndTag(); + else if (tagName == "SnapSensitivity") + is >> m_snapSensitivity, is.matchEndTag(); else if (tagName == "Modifier_Size") is >> m_modifierSize, is.matchEndTag(); else if (tagName == "Modifier_Opacity") diff --git a/toonz/sources/tnztools/brushtool.h b/toonz/sources/tnztools/brushtool.h index 23b18da..e24a12b 100644 --- a/toonz/sources/tnztools/brushtool.h +++ b/toonz/sources/tnztools/brushtool.h @@ -7,7 +7,7 @@ #include "tproperty.h" #include "trasterimage.h" #include "ttoonzimage.h" - +#include "tstroke.h" #include "toonz/strokegenerator.h" #include "tools/tool.h" @@ -36,8 +36,8 @@ struct BrushData final : public TPersist { std::wstring m_name; double m_min, m_max, m_acc, m_smooth, m_hardness, m_opacityMin, m_opacityMax; - bool m_selective, m_pencil, m_breakAngles, m_pressure; - int m_cap, m_join, m_miter; + bool m_selective, m_pencil, m_breakAngles, m_pressure, m_snap; + int m_cap, m_join, m_miter, m_frameRange, m_snapSensitivity; double m_modifierSize, m_modifierOpacity; BrushData(); @@ -127,6 +127,7 @@ public: void leftButtonDrag(const TPointD &pos, const TMouseEvent &e) override; void leftButtonUp(const TPointD &pos, const TMouseEvent &e) override; void mouseMove(const TPointD &pos, const TMouseEvent &e) override; + bool keyDown(int key, TUINT32 b, const TPoint &point) override; void draw() override; @@ -137,7 +138,7 @@ public: TPropertyGroup *getProperties(int targetType) override; bool onPropertyChanged(std::string propertyName) override; - + void resetFrameRange(); void onImageChanged() override; void setWorkAndBackupImages(); void updateWorkAndBackupRasters(const TRect &rect); @@ -154,6 +155,11 @@ public: void addTrackPoint(const TThickPoint &point, double pixelSize2); void flushTrackPoint(); + bool doFrameRangeStrokes(TFrameId firstFrameId, TStroke *firstStroke, + TFrameId lastFrameId, TStroke *lastStroke, + bool drawFirstStroke = true); + void checkGuideSnapping(bool beforeMousePress); + void checkStrokeSnapping(bool beforeMousePress); protected: TPropertyGroup m_prop[2]; @@ -168,24 +174,34 @@ protected: TBoolProperty m_breakAngles; TBoolProperty m_pencil; TBoolProperty m_pressure; + TBoolProperty m_snap; + TEnumProperty m_frameRange; + TEnumProperty m_snapSensitivity; TEnumProperty m_capStyle; TEnumProperty m_joinStyle; TIntProperty m_miterJoinLimit; StrokeGenerator m_track; + StrokeGenerator m_rangeTrack; RasterStrokeGenerator *m_rasterTrack; - + TStroke *m_firstStroke; TTileSetCM32 *m_tileSet; TTileSaverCM32 *m_tileSaver; - + TFrameId m_firstFrameId, m_veryFirstFrameId; TPixel32 m_currentColor; int m_styleId; double m_minThick, m_maxThick; + // for snapping and framerange + int m_strokeIndex1, m_strokeIndex2, m_col, m_firstFrame, m_veryFirstFrame, + m_veryFirstCol, m_targetType; + double m_w1, m_w2, m_pixelSize, m_currThickness, m_minDistance2; + bool m_foundFirstSnap = false, m_foundLastSnap = false, m_dragDraw = true; TRectD m_modifiedRegion; TPointD m_dpiScale, m_mousePos, //!< Current mouse position, in world coordinates. - m_brushPos; //!< World position the brush will be painted at. + m_brushPos, //!< World position the brush will be painted at. + m_firstSnapPoint, m_lastSnapPoint; BluredBrush *m_bluredBrush; QRadialGradient m_brushPad; @@ -204,7 +220,7 @@ protected: bool m_active, m_enabled, m_isPrompting, //!< Whether the tool is prompting for spline //! substitution. - m_firstTime, m_isPath, m_presetsLoaded; + m_firstTime, m_isPath, m_presetsLoaded, m_firstFrameRange; /*--- 作業中のFrameIdをクリック時に保存し、マウスリリース時(Undoの登録時)に別のフレームに diff --git a/toonz/sources/tnztools/tooloptions.cpp b/toonz/sources/tnztools/tooloptions.cpp index 5157985..00f872d 100644 --- a/toonz/sources/tnztools/tooloptions.cpp +++ b/toonz/sources/tnztools/tooloptions.cpp @@ -342,9 +342,10 @@ void ToolOptionControlBuilder::visit(TEnumProperty *p) { case COMBOBOX: default: { - QLabel *label = addLabel(p); - m_panel->addLabel(p->getName(), label); - + if (p->getQStringName() != "") { + QLabel *label = addLabel(p); + m_panel->addLabel(p->getName(), label); + } ToolOptionCombo *obj = new ToolOptionCombo(m_tool, p, m_toolHandle); control = obj; widget = obj; @@ -1570,6 +1571,8 @@ BrushToolOptionsBox::BrushToolOptionsBox(QWidget *parent, TTool *tool, , m_pencilMode(0) , m_hardnessLabel(0) , m_joinStyleCombo(0) + , m_snapCheckbox(0) + , m_snapSensitivityCombo(0) , m_miterField(0) { TPropertyGroup *props = tool->getProperties(0); assert(props->getPropertyCount() > 0); @@ -1615,7 +1618,10 @@ BrushToolOptionsBox::BrushToolOptionsBox(QWidget *parent, TTool *tool, addSeparator(); if (tool && tool->getProperties(1)) tool->getProperties(1)->accept(builder); - + m_snapCheckbox = + dynamic_cast<ToolOptionCheckbox *>(m_controls.value("Snap")); + m_snapSensitivityCombo = + dynamic_cast<ToolOptionCombo *>(m_controls.value("Sensitivity:")); m_joinStyleCombo = dynamic_cast<ToolOptionPopupButton *>(m_controls.value("Join")); m_miterField = @@ -1633,23 +1639,29 @@ void BrushToolOptionsBox::filterControls() { // show or hide widgets which modify imported brush (mypaint) bool showModifiers = false; - if (FullColorBrushTool* fullColorBrushTool = dynamic_cast<FullColorBrushTool*>(m_tool)) + if (FullColorBrushTool *fullColorBrushTool = + dynamic_cast<FullColorBrushTool *>(m_tool)) showModifiers = fullColorBrushTool->getBrushStyle(); - for (QMap<std::string, QLabel *>::iterator it = m_labels.begin(); it != m_labels.end(); it++) { + for (QMap<std::string, QLabel *>::iterator it = m_labels.begin(); + it != m_labels.end(); it++) { bool isModifier = (it.key().substr(0, 8) == "Modifier"); - bool isCommon = (it.key() == "Pressure" || it.key() == "Preset:"); - bool visible = isCommon || (isModifier == showModifiers); + bool isCommon = (it.key() == "Pressure" || it.key() == "Preset:"); + bool visible = isCommon || (isModifier == showModifiers); it.value()->setVisible(visible); } - for (QMap<std::string, ToolOptionControl *>::iterator it = m_controls.begin(); it != m_controls.end(); it++) { + for (QMap<std::string, ToolOptionControl *>::iterator it = m_controls.begin(); + it != m_controls.end(); it++) { bool isModifier = (it.key().substr(0, 8) == "Modifier"); - bool isCommon = (it.key() == "Pressure" || it.key() == "Preset:"); - bool visible = isCommon || (isModifier == showModifiers); - if (QWidget* widget = dynamic_cast<QWidget*>(it.value())) + bool isCommon = (it.key() == "Pressure" || it.key() == "Preset:"); + bool visible = isCommon || (isModifier == showModifiers); + if (QWidget *widget = dynamic_cast<QWidget *>(it.value())) widget->setVisible(visible); } + if (m_tool->getTargetType() & TTool::Vectors) { + m_snapSensitivityCombo->setHidden(!m_snapCheckbox->isChecked()); + } } //----------------------------------------------------------------------------- @@ -1664,6 +1676,8 @@ void BrushToolOptionsBox::updateStatus() { if (m_miterField) m_miterField->setEnabled(m_joinStyleCombo->currentIndex() == TStroke::OutlineOptions::MITER_JOIN); + if (m_snapCheckbox) + m_snapSensitivityCombo->setHidden(!m_snapCheckbox->isChecked()); } //----------------------------------------------------------------------------- diff --git a/toonz/sources/toonz/mainwindow.cpp b/toonz/sources/toonz/mainwindow.cpp index 37b086c..49b6bec 100644 --- a/toonz/sources/toonz/mainwindow.cpp +++ b/toonz/sources/toonz/mainwindow.cpp @@ -2116,7 +2116,8 @@ void MainWindow::defineActions() { tr("Brush hardness - Increase"), ""); createToolOptionsAction("A_DecreaseBrushHardness", tr("Brush hardness - Decrease"), ""); - + createToolOptionsAction("A_ToolOption_SnapSensitivity", tr("SnapSensitivity"), + ""); createToolOptionsAction("A_ToolOption_AutoGroup", tr("Auto Group"), ""); createToolOptionsAction("A_ToolOption_BreakSharpAngles", tr("Break sharp angles"), ""); diff --git a/toonz/sources/toonz/preferencespopup.cpp b/toonz/sources/toonz/preferencespopup.cpp index ad64942..f7d4059 100644 --- a/toonz/sources/toonz/preferencespopup.cpp +++ b/toonz/sources/toonz/preferencespopup.cpp @@ -852,6 +852,13 @@ void PreferencesPopup::onDefLevelParameterChanged() { //----------------------------------------------------------------------------- +void PreferencesPopup::onVectorSnappingTargetChanged(int index) { + m_vectorSnappingTargetCB->setCurrentIndex(index); + m_pref->setVectorSnappingTarget(index); +} + +//----------------------------------------------------------------------------- + void PreferencesPopup::rebuildFormatsList() { const Preferences &prefs = *Preferences::instance(); @@ -1196,13 +1203,14 @@ PreferencesPopup::PreferencesPopup() //--- Drawing ------------------------------ categoryList->addItem(tr("Drawing")); - m_defScanLevelType = new QComboBox(this); - m_defLevelType = new QComboBox(this); - m_defLevelWidth = new MeasuredDoubleLineEdit(0); - m_defLevelHeight = new MeasuredDoubleLineEdit(0); - m_defLevelDpi = new DoubleLineEdit(0, 66.76); - m_autocreationType = new QComboBox(this); - m_dpiLabel = new QLabel(tr("DPI:"), this); + m_defScanLevelType = new QComboBox(this); + m_defLevelType = new QComboBox(this); + m_defLevelWidth = new MeasuredDoubleLineEdit(0); + m_defLevelHeight = new MeasuredDoubleLineEdit(0); + m_defLevelDpi = new DoubleLineEdit(0, 66.76); + m_autocreationType = new QComboBox(this); + m_dpiLabel = new QLabel(tr("DPI:"), this); + m_vectorSnappingTargetCB = new QComboBox(this); CheckBox *keepOriginalCleanedUpCB = new CheckBox(tr("Keep Original Cleaned Up Drawings As Backup"), this); CheckBox *multiLayerStylePickerCB = new CheckBox( @@ -1493,6 +1501,11 @@ PreferencesPopup::PreferencesPopup() int autocreationType = m_pref->getAutocreationType(); m_autocreationType->setCurrentIndex(autocreationType); + QStringList vectorSnappingTargets; + vectorSnappingTargets << tr("Strokes") << tr("Guides") << tr("All"); + m_vectorSnappingTargetCB->addItems(vectorSnappingTargets); + m_vectorSnappingTargetCB->setCurrentIndex(m_pref->getVectorSnappingTarget()); + //--- Xsheet ------------------------------ xsheetAutopanDuringPlaybackCB->setChecked(m_pref->isXsheetAutopanEnabled()); m_cellsDragBehaviour->addItem(tr("Cells Only")); @@ -1919,6 +1932,9 @@ PreferencesPopup::PreferencesPopup() drawingTopLay->addWidget(new QLabel(tr("Autocreation:")), 4, 0, Qt::AlignRight); drawingTopLay->addWidget(m_autocreationType, 4, 1, 1, 3); + drawingTopLay->addWidget(new QLabel(tr("Vector Snapping:")), 5, 0, + Qt::AlignRight); + drawingTopLay->addWidget(m_vectorSnappingTargetCB, 5, 1, 1, 3); } drawingFrameLay->addLayout(drawingTopLay, 0); @@ -2295,6 +2311,9 @@ PreferencesPopup::PreferencesPopup() SLOT(onDefLevelTypeChanged(int))); ret = ret && connect(m_autocreationType, SIGNAL(currentIndexChanged(int)), SLOT(onAutocreationTypeChanged(int))); + ret = + ret && connect(m_vectorSnappingTargetCB, SIGNAL(currentIndexChanged(int)), + SLOT(onVectorSnappingTargetChanged(int))); ret = ret && connect(m_defLevelWidth, SIGNAL(valueChanged()), SLOT(onDefLevelParameterChanged())); ret = ret && connect(m_defLevelHeight, SIGNAL(valueChanged()), diff --git a/toonz/sources/toonz/preferencespopup.h b/toonz/sources/toonz/preferencespopup.h index ab25ffa..1a9f3bc 100644 --- a/toonz/sources/toonz/preferencespopup.h +++ b/toonz/sources/toonz/preferencespopup.h @@ -53,7 +53,8 @@ private: QComboBox *m_keyframeType, *m_cellsDragBehaviour, *m_defScanLevelType, *m_defLevelType, *m_autocreationType, *m_levelFormatNames, - *m_columnIconOm, *m_unitOm, *m_cameraUnitOm, *m_importPolicy; + *m_columnIconOm, *m_unitOm, *m_cameraUnitOm, *m_importPolicy, + *m_vectorSnappingTargetCB; DVGui::MeasuredDoubleLineEdit *m_defLevelWidth, *m_defLevelHeight; @@ -146,6 +147,7 @@ private slots: void onRegionAntialiasChanged(int); void onImportPolicyChanged(int); void onImportPolicyExternallyChanged(int policy); + void onVectorSnappingTargetChanged(int index); #ifdef LINETEST void onLineTestFpsCapture(int); diff --git a/toonz/sources/toonz/sceneviewer.cpp b/toonz/sources/toonz/sceneviewer.cpp index 7974032..bda616c 100644 --- a/toonz/sources/toonz/sceneviewer.cpp +++ b/toonz/sources/toonz/sceneviewer.cpp @@ -897,6 +897,22 @@ void SceneViewer::hideEvent(QHideEvent *) { if (m_locator && m_locator->isVisible()) m_locator->hide(); } +int SceneViewer::getVGuideCount() { + if (viewGuideToggle.getStatus()) + return m_vRuler->getGuideCount(); + else + return 0; +} +int SceneViewer::getHGuideCount() { + if (viewGuideToggle.getStatus()) + return m_hRuler->getGuideCount(); + else + return 0; +} + +double SceneViewer::getVGuide(int index) { return m_vRuler->getGuide(index); } +double SceneViewer::getHGuide(int index) { return m_hRuler->getGuide(index); } + //----------------------------------------------------------------------------- void SceneViewer::initializeGL() { diff --git a/toonz/sources/toonz/sceneviewer.h b/toonz/sources/toonz/sceneviewer.h index 111e459..65da547 100644 --- a/toonz/sources/toonz/sceneviewer.h +++ b/toonz/sources/toonz/sceneviewer.h @@ -238,6 +238,10 @@ public: void setIsLocator() { m_isLocator = true; } void setIsStyleShortcutSwitchable() { m_isStyleShortcutSwitchable = true; } + int getVGuideCount(); + int getHGuideCount(); + double getVGuide(int index); + double getHGuide(int index); public: // SceneViewer's gadget public functions diff --git a/toonz/sources/toonzlib/preferences.cpp b/toonz/sources/toonzlib/preferences.cpp index 6fbbcbf..8c0d5f4 100644 --- a/toonz/sources/toonzlib/preferences.cpp +++ b/toonz/sources/toonzlib/preferences.cpp @@ -255,6 +255,7 @@ Preferences::Preferences() , m_dragCellsBehaviour(0) , m_lineTestFpsCapture(25) , m_defLevelType(0) + , m_vectorSnappingTarget(SnapAll) , m_autocreationType(1) , m_autoExposeEnabled(true) , m_autoCreateEnabled(true) @@ -362,7 +363,7 @@ Preferences::Preferences() getValue(*m_settings, "autosavePeriod", m_autosavePeriod); getValue(*m_settings, "taskchunksize", m_chunkSize); getValue(*m_settings, "xsheetStep", m_xsheetStep); - + getValue(*m_settings, "vectorSnappingTarget", m_vectorSnappingTarget); int r = 0, g = 255, b = 0; getValue(*m_settings, "frontOnionColor.r", r); getValue(*m_settings, "frontOnionColor.g", g); @@ -1295,6 +1296,13 @@ void Preferences::setIgnoreImageDpi(bool on) { //----------------------------------------------------------------- +void Preferences::setVectorSnappingTarget(int target) { + m_vectorSnappingTarget = target; + m_settings->setValue("vectorSnappingTarget", target); +} + +//----------------------------------------------------------------- + void Preferences::setPaletteTypeOnLoadRasterImageAsColorModel(int type) { m_paletteTypeOnLoadRasterImageAsColorModel = type; m_settings->setValue("paletteTypeOnLoadRasterImageAsColorModel", type); diff --git a/toonz/sources/toonzlib/strokegenerator.cpp b/toonz/sources/toonzlib/strokegenerator.cpp index 1a9469a..f1e6b99 100644 --- a/toonz/sources/toonzlib/strokegenerator.cpp +++ b/toonz/sources/toonzlib/strokegenerator.cpp @@ -259,6 +259,10 @@ TRectD StrokeGenerator::getLastModifiedRegion() { //------------------------------------------------------------------- +TPointD StrokeGenerator::getFirstPoint() { return m_points[0]; } + +//------------------------------------------------------------------- + TStroke *StrokeGenerator::makeStroke(double error, UINT onlyLastPoints) const { if (onlyLastPoints == 0 || onlyLastPoints > m_points.size()) return TStroke::interpolate(m_points, error);