From 859c38307709ae63f3524e3b613d3c726feefcd4 Mon Sep 17 00:00:00 2001 From: Ivo Petrov <48355182+ivaylo-matov@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:13:21 +0000 Subject: [PATCH 1/9] [DYN-7923] CurveMapper node (#15863) --- src/DynamoCoreWpf/DynamoCoreWpf.csproj | 12 + .../UI/Images/lock_default_16px.png | Bin 0 -> 310 bytes .../UI/Images/lock_hover_16px.png | Bin 0 -> 271 bytes .../UI/Images/reset_default_16px.png | Bin 0 -> 408 bytes .../UI/Images/reset_hover_16px.png | Bin 0 -> 348 bytes .../UI/Images/unlock_default_16px.png | Bin 0 -> 306 bytes .../UI/Images/unlock_hover_16px.png | Bin 0 -> 270 bytes src/DynamoCoreWpf/UI/lock_default_16px.png | Bin 0 -> 310 bytes src/DynamoCoreWpf/UI/lock_hover_16px.png | Bin 0 -> 271 bytes src/DynamoCoreWpf/UI/unlock_default_16px.png | Bin 0 -> 306 bytes src/DynamoCoreWpf/UI/unlock_hover_16px.png | Bin 0 -> 270 bytes .../CoreNodeModels/CoreNodeModelsImages.resx | 358 ++++--- .../CoreNodeModels/CurveMapperNodeModel.cs | 951 ++++++++++++++++++ .../CoreNodeModels.CurveMapper.Large.png | Bin 0 -> 1665 bytes .../CoreNodeModels.CurveMapper.Small.png | Bin 0 -> 1210 bytes .../CurveMapper/CurveMapperControl.xaml | 261 +++++ .../CurveMapper/CurveMapperControl.xaml.cs | 465 +++++++++ .../CurveMapper/CurveMapperControlPoint.xaml | 80 ++ .../CurveMapperControlPoint.xaml.cs | 192 ++++ .../CurveMapper/CurveMapperNodeView.cs | 25 + .../CurveMapper/CurveRenderer.cs | 89 ++ src/Libraries/CoreNodes/Color.cs | 4 +- .../CoreNodes/CurveMapper/BezierCurve.cs | 95 ++ .../CoreNodes/CurveMapper/ControlLine.cs | 29 + .../CoreNodes/CurveMapper/CurveBase.cs | 40 + .../CurveMapper/CurveMapperGenerator.cs | 88 ++ .../CoreNodes/CurveMapper/GaussianCurve.cs | 125 +++ .../CoreNodes/CurveMapper/LinearCurve.cs | 95 ++ .../CoreNodes/CurveMapper/ParabolicCurve.cs | 112 +++ .../CoreNodes/CurveMapper/PerlinNoiseCurve.cs | 250 +++++ .../CoreNodes/CurveMapper/PowerCurve.cs | 82 ++ .../CoreNodes/CurveMapper/SineWave.cs | 78 ++ .../CoreNodes/CurveMapper/SquareRootCurve.cs | 109 ++ .../Controls/ExportWithUnitsControl.xaml | 2 +- .../Packages/LibrarieJS/layoutSpecs.json | 11 + .../CoreNodeModels.CurveMapper.Large.png | Bin 0 -> 1665 bytes .../CoreNodeModels.CurveMapper.Small.png | Bin 0 -> 1210 bytes 37 files changed, 3395 insertions(+), 158 deletions(-) create mode 100644 src/DynamoCoreWpf/UI/Images/lock_default_16px.png create mode 100644 src/DynamoCoreWpf/UI/Images/lock_hover_16px.png create mode 100644 src/DynamoCoreWpf/UI/Images/reset_default_16px.png create mode 100644 src/DynamoCoreWpf/UI/Images/reset_hover_16px.png create mode 100644 src/DynamoCoreWpf/UI/Images/unlock_default_16px.png create mode 100644 src/DynamoCoreWpf/UI/Images/unlock_hover_16px.png create mode 100644 src/DynamoCoreWpf/UI/lock_default_16px.png create mode 100644 src/DynamoCoreWpf/UI/lock_hover_16px.png create mode 100644 src/DynamoCoreWpf/UI/unlock_default_16px.png create mode 100644 src/DynamoCoreWpf/UI/unlock_hover_16px.png create mode 100644 src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs create mode 100644 src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Large.png create mode 100644 src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Small.png create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs create mode 100644 src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/ControlLine.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/CurveBase.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/SineWave.cs create mode 100644 src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs create mode 100644 src/Resources/CoreNodeModels/LargeIcons/CoreNodeModels.CurveMapper.Large.png create mode 100644 src/Resources/CoreNodeModels/SmallIcons/CoreNodeModels.CurveMapper.Small.png diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj index 5195fb32057..82dcf2abe08 100644 --- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj +++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj @@ -109,10 +109,16 @@ + + + + + + @@ -1033,6 +1039,8 @@ + + @@ -1047,6 +1055,8 @@ + + @@ -1065,6 +1075,8 @@ + + diff --git a/src/DynamoCoreWpf/UI/Images/lock_default_16px.png b/src/DynamoCoreWpf/UI/Images/lock_default_16px.png new file mode 100644 index 0000000000000000000000000000000000000000..2300856f96371160ed3d4bd7c936ad05dcc85ed2 GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRt!3HF+tk*dLq&N#aB8wRqxP?KOkzv*x37{Zj zage(c!@6@aFM%AEbVpxD28NCO+_v3sG;6Pym*f4Jngr6!UI#~iUe_X!E^<)q(A%$_3A;@{a}>%+ zJZ9VXGMv3Z*QR_rgZt!%%x7dB%F>i%wlZscXu7wN&x)-;-S&I!`6iCE#ja1!{*&4! zk|-+1y5?yPqj~nU23@mgg|H`DjUs2R&j`9v|AHK~#7F#Zt>{ zgFq171q*RZI@nYYQb9-uP6y$HIE4-(R}klfIFSHoqh9F>e{0DsjBK`7>3t0O;=)1(-dp1W9!$9`9zTc{*>i3m?;L|5xJ6bB@=T0(48$xC2ml%X!#97ODx6loCVeOW7efb4o3y>Q0a=~u^0000 CD5RwT literal 0 HcmV?d00001 diff --git a/src/DynamoCoreWpf/UI/Images/reset_hover_16px.png b/src/DynamoCoreWpf/UI/Images/reset_hover_16px.png new file mode 100644 index 0000000000000000000000000000000000000000..541ade632529915da54e18c53e1d952224cf2217 GIT binary patch literal 348 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{zp$2$B+ufvq1~RngV$4J{I`PU{&_^*4Avp znLbfDbq%dc<}ZldU1rB%E#EM=ue|+jhw!czllZfnH9a=NUX*{an^LB{Ts51n7rw literal 0 HcmV?d00001 diff --git a/src/DynamoCoreWpf/UI/Images/unlock_default_16px.png b/src/DynamoCoreWpf/UI/Images/unlock_default_16px.png new file mode 100644 index 0000000000000000000000000000000000000000..a316c6b2658a563652647faed1c635b75a0c65de GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0vp^0zfRt!3HF+tk*dLq&N#aB8wRqxP?KOkzv*x37{Zj zage(c!@6@aFM%AEbVpxD28NCO+Fl<8ek$_EyyrjTG`~(VWJ-B+D0%)?zT(6l%io`Qj~0HARNT$?o3U)3 zQwLYJXedLDq16idI;99NDQ20u9++X8eh NgQu&X%Q~loCIHs7Ve_v3sG;6Pym*f4Jngr6!UI#~iUe_X!E^<)q(A%$_3A;@{a}>%+ zJZ9VXGMv3Z*QR_rgZt!%%x7dB%F>i%wlZscXu7wN&x)-;-S&I!`6iCE#ja1!{*&4! zk|-+1y5?yPqj~nU23@mgg|H`DjUs2R&j`9v|AFl<8ek$_EyyrjTG`~(VWJ-B+D0%)?zT(6l%io`Qj~0HARNT$?o3U)3 zQwLYJXedLDq16idI;99NDQ20u9++X8eh NgQu&X%Q~loCIHs7Ve iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAKBFJREFUeF7t3X/QZXV9H/AIhiCIRhwiVoo6OEPIDGk0zIQOyQwx7chMsSGTKRmm + DgAACw4BQL7hQQAAKBFJREFUeF7t3X/QZXV9H/AIhiCIRhwiVoo6OEPIDGk0zIQOyQwx7chMsSGTKRmm ncFI0hmaaltFFKK1YikxiUS0ahE1oVNBQ5TqGDNqBkrJPrDLLrvA/mIVRHZAsggCsgICe/r+bJ9Drh/e +33uOffce8553/cfr9H98Hzv+X7Pfc77eZ57vt/v+amqqszMbAnRopmZ6aNFMzPTR4tmZqaPFs3MTB8t mpmZPlo0MzN9tGhmZvpo0czM9NGimZnpo0UzM9NHi2Zmpo8WzcxMHy2amZk+WjQzM320aGZm+mjRzMz0 @@ -298,7 +298,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADK1JREFUeF7tnU/MXFUZxiGQEkhoDGyICYREWBhZgnEjRHEBGxfoyoTgCiXBFTGi + DgAACw4BQL7hQQAADK1JREFUeF7tnU/MXFUZxiGQEkhoDGyICYREWBhZgnEjRHEBGxfoyoTgCiXBFTGi G6K2C0PEfwujCSAYFmIimkgk8i8xof0qtqb9PkpbwYC0iFGLERWK1PF5JnOb93vnuXfuufe8c6dzz+KX dp655z1n3vf9zpx7z585ZzKZFEaMFAvjQYqF8SDFwniQYmE8SLEwHqRoOXr06GSZ+PoLsUjRooIUia+/ EIsULSpIkfj6C7FI0aKCFImvvxCLFC3RAYq2X2hGipboAEXbLzQjRUt0gKLtF5qRoiU6QNH2C81I0RId @@ -358,7 +358,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAGK1JREFUeF7t3DGuZdtjlHGTQA5DQMwCMRHEKBwiRoIYiZ16CCD9YyKYABmPc0wH + DgAACw4BQL7hQQAAGK1JREFUeF7t3DGuZdtjlHGTQA5DQMwCMRHEKBwiRoIYiZ16CCD9YyKYABmPc0wH paPvVVf3u6f7rnW/4JeUXpXPbe21tuRt+e/++OMPSdIXhKEk6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLu h6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4Y SpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek6X4YSpLuh6Ek @@ -469,7 +469,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABAJJREFUeF7t2TGOU0EQBNAlgRyOgLgF4iKInJwQcRLESSDlCCD5CHABMj7dq7GY + DgAACw4BQL7hQQAABAJJREFUeF7t2TGOU0EQBNAlgRyOgLgF4iKInJwQcRLESSDlCCD5CHABMj7dq7GY na7vtvEfrN9VwQu22h5W1SMxiLtlWYQYDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUH DIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIUHDIVHCN59XRb5q++m ohCgEpj13VQUAlQCs76bikKASmDWd1NRCLIC2OfVhCArgH1eTQiyAtjn1YQgK4B9Xk0IsgLY59WEICuA @@ -492,7 +492,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAD7ZJREFUeF7t3THIrtl61vHIwRCCIgSCQRBsAim0srFIlSa1lVW6FGJtZZnGdKex + DgAACw4BQL7hQQAAD7ZJREFUeF7t3THIrtl61vHIwRCCIgSCQRBsAim0srFIlSa1lVW6FGJtZZnGdKex skujjRYiNlYWVhaCjXAKRUECgiAWIoo6vjsM8mXlf3K+PPPseea7vt/Ar7ngfdcN11r3W8yePT/3zTff APAJZQjAvgwB2JchAPsyBGBfhgDsyxCAfRkCsC9DAPZlCMC+DAHYlyEA+zIEYF+GAOzLEIB9GQKwL0MA 9mUIwL4MAdiXIQD7MgRgX4YA7MsQgH0ZArAvQwD2ZQjAvgwB2JchAPsyBGBfhgDsyxCAfRkCsC9DAPZl @@ -565,7 +565,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABKNJREFUeF7t0i+ubEUUxeFHXoIhGAwoJAkjQMMA0BgsEyBINIpgcMwCBsAEMMiL + DgAACw4BQL7hQQAABKNJREFUeF7t0i+ubEUUxeFHXoIhGAwoJAkjQMMA0BgsEyBINIpgcMwCBsAEMMiL QhEUCASBAJfaSYta6/wo3r9037NriU+snV6Vrtrn0f39fWwMh7EPHMY+cBj7wGHsA4exDxzGPnAY+8Bh 7AOHsQ8cxj5wGPvAYewDh7EPHMY+cBj7OAzu7u7uH7jP/D/HszsM4MEfonwEL8hhAI/9UOUjeAEOA3jo hywfwXM6DOCRH7p8BM/hMIAHPoN8BM/oMIDHPYt8BM/gMICHPZN8BE/pMIBHPZt8BE/hMIAHPaN8BE/o @@ -591,7 +591,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAD7ZJREFUeF7t3THIpdtZBeDIxRCCIgQCQRBsAim0srFIlSa1lVW6cBFrK8s0xiqN + DgAACw4BQL7hQQAAD7ZJREFUeF7t3THIpdtZBeDIxRCCIgQCQRBsAim0srFIlSa1lVW6cBFrK8s0xiqN lV0abbQQsbGysFIQbAQLRUEEQRALEYM6fn8Y5OTNurkne/Y/e846T/E0C87ZL6y931PcuTOfefPmDQBP KIYA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQ L4YA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQL4YA9IshAP1iCEC/GALQ @@ -664,7 +664,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABKNJREFUeF7t0i+ubEUUxeFHXoIhGBQKScII0DAANAZ7J0CQaILA4ZgFDIABgHkW + DgAACw4BQL7hQQAABKNJREFUeF7t0i+ubEUUxeFHXoIhGBQKScII0DAANAZ7J0CQaILA4ZgFDIABgHkW hUSBQBBegEvtpEWtdX4U71+679m1xCfWTq9KV+3z6P7+PjaGw9gHDmMfOIx94DD2gcPYBw5jHziMfeAw 9oHD2AcOYx84jH3gMPaBw9gHDmMfOIx9HAZf/vD0/oH7wv9zvLjDAB78IcpH8IocBvDYD1U+glfgMICH fsjyEbykwwAe+aHLR/ASDgN44DPIR/CCDgN43LPIR/ACDgN42DPJR/CcDgN41LPJR/AcDgN40DPKR/CM @@ -690,7 +690,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAEmtJREFUeF7t1b21LslyHFDAA2o0mRqpwSqKUJ4DdOAZMEzwBwSSgeya6Z6punW2 + DgAACw4BQL7hQQAAEmtJREFUeF7t1b21LslyHFDAA2o0mRqpwSqKUJ4DdOAZMEzwBwSSgeya6Z6punW2 sJU4Z60uIeOLf/jtt98A+IFiCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA /WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA /WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA/WIIwP1iCMD9YgjA @@ -775,7 +775,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABVZJREFUeF7t0sGNHEcQRFF6QZN5lH3rAB1YA0Y6iEAh8FXiZ/fkzqDi8C4BVCKR + DgAACw4BQL7hQQAABVZJREFUeF7t0sGNHEcQRFF6QZN5lH3rAB1YA0Y6iEAh8FXiZ/fkzqDi8C4BVCKR Ud8ej0cdDMM6B4Z1DgzrHBgaHx8fj5PlPT4/P39k9sowNOgoJ8l7/PMBHu/0CTA06CgnyXv8+wHe5hNg aNBRTpL3WD7AW3wCDA06yknyHvEBXv4TYGjQUU6S94AP8NKfAEODjnKSvAeU/8tLfgIMDTrKSfIeUPzq 5T4BhgYd5SR5Dyg9vdQnwNCgo5wk7wGFk5f5BBgadJST5D2g7P/yEp8AQ4OOcpK8BxS98+WfAEODjnKS @@ -804,7 +804,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAELVJREFUeF7t1UGWJctVRFEx2T8VFh2aDIRpaBDMg04hAQLhHOl61Y+qmxm5G7tj + DgAACw4BQL7hQQAAELVJREFUeF7t1UGWJctVRFEx2T8VFh2aDIRpaBDMg04hAQLhHOl61Y+qmxm5G7tj 763l3jAL/8O3b98A+IIyBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE 4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE 4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE4P0yBOD9MgTg/TIE @@ -881,7 +881,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABVZJREFUeF7t0sGtXDcQRFFF+1MRnI7TUBDKw5uRFhZAFK5pXb03/WfAWpxNAWw0 + DgAACw4BQL7hQQAABVZJREFUeF7t0sGtXDcQRFFF+1MRnI7TUBDKw5uRFhZAFK5pXb03/WfAWpxNAWw0 uvjl8XjUwTCsc2BY58CwzoGh8de3fx4ny3v8/f3xNbNXhqFBRzlJ3uPnB3i80yfA0KCjnCTv8e8HeJtP gKFBRzlJ3mP5AG/xCTA06CgnyXvEB3j5T4ChQUc5Sd4DPsBLfwIMDTrKSfIeUP4vL/kJMDToKCfJe0Dx q5f7BBgadJST5D2g9PRSnwBDg45ykrwHFE5e5hNgaNBRTpL3gLL/y0t8AgwNOspJ8h5Q9M6nfwIMDTrK @@ -910,7 +910,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAE7tJREFUeF7t2cGNJctxhWFqyxU9oC90QL6QNskB+qAttX4rOkATCDRzpAcxGfwn + DgAACw4BQL7hQQAAE7tJREFUeF7t2cGNJctxhWFqyxU9oC90QL6QNskB+qAttX4rOkATCDRzpAcxGfwn eqa7Myoj7r/4Ngc4nQFU30ig6jdvb2+SpBeEoSRpPgwlSfNhKEmaD0NJ0nwYSpLmw1CSNB+GkqT5MJQk zYehJGk+DCVJ82EoSZoPQ0nSfBhKkubDUJI0H4aSpPkwlCTNh6EkaT4MJUnzYShJmg9DSdJ8GEqS5sNQ kjQfhpKk+TCUJM2HoSRpPgwlSfNhKEmaD0NJ0nwYSpLmw1CSNB+GkqT5MJQkzYehJGk+DCVJ82EoSZoP @@ -1000,7 +1000,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAA55JREFUeF7t28FxFFEMhGEnQUwERBDOhxwo7tz2RCjDCmrg+fm3vdqVdufRffgu + DgAACw4BQL7hQQAAA55JREFUeF7t28FxFFEMhGEnQUwERBDOhxwo7tz2RCjDCmrg+fm3vdqVdufRffgu Xa9UNWpd52nbNhOGoenA0HRgaDowNB0Ymg4MTQeGpgND04Gh6cDQdGBoOjA0HRiaDgxNB4am4+l0Om2m wwcgzgcgzgcg7sMDmB9keR6/u9S953kh8CZj9XleCLzJWH2eFwJvMlaf54XAm4zV53kh8CZj9XleCLzJ WH2eFwJvMlaf54XAm4zV53kh8CZj9XleCLzJWH2eFwJvMlaf54XAm4zV53kh8CZj9XleCLzJWH2eFwJv @@ -1021,7 +1021,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAFNxJREFUeF7t3T+srVtZhXExxIRKKrGCWjsrWhtpbbS20FoToTAxIZGGikRDRaWN + DgAACw4BQL7hQQAAFNxJREFUeF7t3T+srVtZhXExxIRKKrGCWjsrWhtpbbS20FoToTAxIZGGikRDRaWN haEwVNigBUH+R0Au3IMmiCSEoA0hRAjidq6bs3QxeO671nfOt8+399hP8UvMSIbMeeb8XuXCPOsX7u7u JElPEIaSpH4YSpL6YShJ6oehJKkfhpKkfhhKkvphKEnqh6EkqR+GkqR+GEqS+mEoSeqHoSSpH4aSpH4Y SpL6YShJ6oehJKkfhpKkfhhKkvphKEnqh6EkqR+GkqR+GEqS+mEoSeqHoSSpH4aSpH4YSpL6YShJ6oeh @@ -1116,7 +1116,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABq9JREFUeF7t3D9vHFUUBfBEimhCkS5p0iZt+lDQuaSAni8AFQV8ACMaJPgCUEON + DgAACw4BQL7hQQAABq9JREFUeF7t3D9vHFUUBfBEimhCkS5p0iZt+lDQuaSAni8AFQV8ACMaJPgCUEON QDRIpIgISDThn0wVEJCSAglhIbHcJzzocDle75md4zzPu8WvOas9b9/ctXb8ZO+lzWZTBkbDMg4alnHQ sIyDhmUcNCzjoCE6OjranKe8/tqwPTvl9TMaIlbqlNdfG7Znp7x+RkPESp3y+mvD9uyU189oiFipU15/ bdienfL6GQ2RWqhy9/fGvV+1n4ZILVS5+3vj3q/aT0OkFqrc/b1x71ftpyFSC1Xu/t6496v20xCphSp3 @@ -1151,7 +1151,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAD/dJREFUeF7t1EGKJEkQQ9G585xxDlGnqVkFJOKjTIdsHNR/8TYCgRnmEf/8/v5K + DgAACw4BQL7hQQAAD/dJREFUeF7t1EGKJEkQQ9G585xxDlGnqVkFJOKjTIdsHNR/8TYCgRnmEf/8/v5K kv5CGEqS9mEoSdqHoSRpH4aSpH0YSpL2YShJ2oehJGkfhpKkfRhKkvZhKEnah6EkaR+GkqR9GEqS9mEo SdqHoSRpH4aSpH0YSpL2YShJ2oehJGkfhpKkfRhKkvZhKEnah6EkaR+GkqR9GEqS9mEoSdqHoSRpH4aS pH0YSpL2YShJ2oehJGkfhpKkfRhKkvZhKEnah6EkaR+GkqR9GEqS9mEoSdqHoSRpH4aSpH0YSpL2YShJ @@ -1225,7 +1225,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABDNJREFUeF7t3DGqZEEMQ9G/51njLOKvpicV5lIgaA1FWcFJFMjP5bTpn8/nU4th + DgAACw4BQL7hQQAABDNJREFUeF7t3DGqZEEMQ9G/51njLOKvpicV5lIgaA1FWcFJFMjP5bTpn8/nU4th WHtgWHtgWHtgWHtgWHtgqH5/fz//05z/Gto5ac6fMFRUmjTnv4Z2TprzJwwVlSbN+a+hnZPm/AlDRaVJ c/5raOekOX/CULmFrnT/bdL7uv0YKrfQle6/TXpftx9D5Ra60v23Se/r9mOo3EJXuv826X3dfgyVW+hK 998mva/bj6FyC13p/tuk93X7MVRuoSvdf5v0vm4/hsotdKX7b5Pe1+3HULmFf/5+PpWjb03ce2Go3EL6 @@ -1249,7 +1249,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAEAlJREFUeF7t3bGrbOW5x/FcNIJHIWrIPRix9C84BiFdClstrLRIqlwEi9OInRjT + DgAACw4BQL7hQQAAEAlJREFUeF7t3bGrbOW5x/FcNIJHIWrIPRix9C84BiFdClstrLRIqlwEi9OInRjT RmxttBaLG9EbMBcLi7TmhqTTIqKFICKxuLkxmOh9FpwNw5vfWWudmT17zzzzKT7NF973nDPreWfr7Jk1 3/n2228BOEExAtBfjAD0FyMA/cUIQH8xAtBfjAD0FyMA/cUIQH8xAtBfjAD0FyMA/cUIQH8xAtBfjAD0 FyMA/cUIQH8xAtBfjAD0FyMA/cUIQH8xAtBfjAD0FyMA/cUIQH8xAtBfjAD0FyMA/cUIQH8xAtBfjAD0 @@ -1323,7 +1323,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABJZJREFUeF7t3b+KXXUUxfFE7BQMaCHYivoCPoDoAwjJUxisBTvT5DHS25kukCa2 + DgAACw4BQL7hQQAABJZJREFUeF7t3b+KXXUUxfFE7BQMaCHYivoCPoDoAwjJUxisBTvT5DHS25kukCa2 ikSJwqCVdVobm+v6yQz87r5fk+C5d985Z63iU8w6e+acve8wc+/5e2O324UxDMMHhuEDw/CBYfjAMHxg GD4wDB8Yhg8MwweG4QPD8IFh+MAwfGAYPjAMHxiGDwzDB4bhA8PwgWH4wDB8YBg+MAwfGIYPDMMHhuED w/CBYfjAMHxgGD4wDB8Yhg8MwweGnS4uLnYr91yeyUO5L3fkXer1OsKwk4ZFQ92Cp3JPPqC+rwsMO00D @@ -1349,7 +1349,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAE1pJREFUeF7t3VmodWd5B/BqEoc4JeIQjUTRQNA4YVJjHUERq6gXidCaIvRGktah + DgAACw4BQL7hQQAAE1pJREFUeF7t3VmodWd5B/BqEoc4JeIQjUTRQNA4YVJjHUERq6gXidCaIvRGktah CA43juAAKrEigjhUpNa2xlrigANYMIKgabWOwTligzgGZ63a9HnhpOxu//nO3vucfc5ez/p98Lv5X7zr fOt9nrX3XtP7R9dffz0AMxRDAPqLIQD9xRCA/mIIQH8xBKC/GALQXwwB6C+GAPQXQwD6iyEA/cUQgP5i CEB/MQSgvxgC0F8MAegvhgD0F0MA+oshAP3FEID+YghAfzEEoL8YAtBfDAHoL4YA9BdDAPqLIQD9xRCA @@ -1438,7 +1438,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABZNJREFUeF7t3TuoHGUcBfDEBzFYJKYQgqgxxhdY+ADRwsJOFMEnBLXwURgCklgo + DgAACw4BQL7hQQAABZNJREFUeF7t3TuoHGUcBfDEBzFYJKYQgqgxxhdY+ADRwsJOFMEnBLXwURgCklgo RNCAjSAItioWEUUUERQbCxGbpFDEwuZiERUfCIpKfCuu50928b/fPYvK7sw3850T+BX38K075891d3bu 7MyGyWRiwmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmho OmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmhoOmi4Smtra8/DZIS+g/j3 @@ -1517,7 +1517,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAHVNJREFUeF7t3WvIr21e1nFNnTFtso2ShIJRmmWCRJRoGPTCapoyUpRA8YVTmVEg + DgAACw4BQL7hQQAAHVNJREFUeF7t3WvIr21e1nFNnTFtso2ShIJRmmWCRJRoGPTCapoyUpRA8YVTmVEg ZgxtbENFVmSbF9kGKjLTbDNtNafoRZgSUVGhPWMb2tNmMtvQjnw6r1rV2a/vc6y1/tc1v+dev+s78Hlz zBz/dcx13v9zred+7vteH/D6669Lkm4IQ0nSfBhKkubDUJI0H4aSpPkwlCTNh6EkaT4MJUnzYShJmg9D SdJ8GEqS5sNQkjQfhpKk+TCUJM2HoSRpPgwlSfNhKEmaD0NJ0nwYSpLmw1CSNB+GkqT5MJQkzYehJGk+ @@ -1648,7 +1648,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABhZJREFUeF7t2juIXVUUBuBEowHBGIU0EhFNIz6a+CSioo2QQsRKCxXEImKhID4R + DgAACw4BQL7hQQAABhZJREFUeF7t2juIXVUUBuBEowHBGIU0EhFNIz6a+CSioo2QQsRKCxXEImKhID4R 7NRCUYJItInWIiIWdr5FEUQUQabwGdRopYKIj3j9F9wd9uz8495sWOvcdfYqPsis85//zJ11MnPmsWmx WISB0WEYBx2GcdBhGAcdhnHQYRgHHYZx0GEYx9F/rK2tLcI44gYYXNwAg4sbYHDVGyDNe0Ufz7Wy6qsG ekUfz7Wy6qsGekUfz7Wy6qsGekUfz7Wy6qsGekUfz7Wy6qsGekUfz7Wy6qsGekUfz7Wy6qsGekUfz7Wy @@ -1680,7 +1680,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAIyBJREFUeF7t3Q+s/Xd91/Fv/zBg66QNqEXYygTi3FikstBMsrDNsTF0IouSBcNY + DgAACw4BQL7hQQAAIyBJREFUeF7t3Q+s/Xd91/Fv/zBg66QNqEXYygTi3FikstBMsrDNsTF0IouSBcNY cLnwZdNGwVAsjRqZZC1gJCH8yUwJZN0WcNrJwpIZYGYDFyZEqHZAFUsTy3CEalml0Pbn++s5Fz+8+7rv e8893+/n8/28z3PJI2Hvc1/nvL/n8z3v76+fe+45w4ULFwAAB0gWAQD5ySIAID9ZBADkJ4sAgPxkEQCQ nywCAPKTRQBAfrIIAMhPFgEA+ckiACA/WQQA5CeLAID8ZBEAkJ8sAgDyk0UAQH6yCADITxYBAPnJIgAg @@ -1836,7 +1836,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACs1JREFUeF7t3VuoHGkRB/DE+4pZL5AIKsuKKCZR8QKCqA9eYhBxfdLgorIoNGRf + DgAACw4BQL7hQQAACs1JREFUeF7t3VuoHGkRB/DE+4pZL5AIKsuKKCZR8QKCqA9eYhBxfdLgorIoNGRf vKyi7KooRiKR9UVQFN9W8YKLFzCoEA262UVdRVQkrEIUworXeFuNiTL+a26n+qt/f93V093Tc6YefuGk T3X1VNV3umfmzMzZMzm+ZxWPg1fC++DzcA4uwl/gPzCBv8If4efwDfg4vAWeDixnvckkdIQ3OO8AvBO+ D/8FGXJbF+Bj8Bxgx+JIIaEd3mBOfmI/B4uf7K59D44AO3YZKSS0wxtc9lj4NKz6097U1+BJwG7LDCkk @@ -1888,7 +1888,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAIPdJREFUeF7t3Vmsrlta1XEQqEKwxAYiMZBgFGyQhBijBAwmXqCWpRghEBMMF5SK + DgAACw4BQL7hQQAAIPdJREFUeF7t3Vmsrlta1XEQqEKwxAYiMZBgFGyQhBijBAwmXqCWpRghEBMMF5SK GE0IYio22ESNqBGbC7FJ1IhIY1P2Smm8MAgxRo0a8BQ2sY9NidjELnKcr271cXz/M85+5znrOXM/e1Ty u3lOjbnGeed83732d9b3rQ969dVXIyLiJYTDiIiYD4cRETEfDiMiYj4cRkTEfDiMiIj5cBgREfPhMCIi 5sNhRETMh8OIiJgPhxERMR8OIyJiPhxGRMR8OIyIiPlwGBER8+EwIiLmw2FERMyHw4iImA+HERExHw4j @@ -2035,7 +2035,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABn9JREFUeF7t2juoXUUUBuBEowHBGIU0EhFNIz6a+CSioo2QQsRKCxXEImKhID4R + DgAACw4BQL7hQQAABn9JREFUeF7t2juoXUUUBuBEowHBGIU0EhFNIz6a+CSioo2QQsRKCxXEImKhID4R 7NRCUYJItInWIiIWdr5FEUQUQVL4DGq0UkHERzz+C84Ocyd/XDPLcWXOnlV8kLv2v/+5c+bk3nPuvesW i0UYGB2GcdBhGAcdhnHQYRgHHYZx0GEYBx2GcRz6x759+xZhHPEEGFw8AQYXT4DBqU+AaW4VfTxXyqtP DVhFH8+V8upTA1bRx3OlvPrUgFX08Vwprz41YBV9PFfKq08NWEUfz5Xy6lMDVtHHc6W8+tSAVfTxXCmv @@ -2069,7 +2069,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAFAdJREFUeF7t3LGRZNlxhWEYAZnBCDhAnQJMgBXUGUGROmEZjViZBtCE5Usgl7PT + DgAACw4BQL7hQQAAFAdJREFUeF7t3LGRZNlxhWEYAZnBCDhAnQJMgBXUGUGROmEZjViZBtCE5Usgl7PT OG8mu7v6Vd37PuFTTsdU1G3hT2n6D7/++isANxRHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2 F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2 F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2F0cA9hdHAPYXRwD2 @@ -2161,7 +2161,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABfBJREFUeF7t2j9uJVUQhfGJIGUJLIHMyyEdIgISdjDLmmCECFiBN8OjPulcyfQc + DgAACw4BQL7hQQAABfBJREFUeF7t2j9uJVUQhfGJIGUJLIHMyyEdIgISdjDLmmCECFiBN8OjPulcyfQc v79dfn1vVfAFPmOrW/Mr2bLkT6fTqSucHbs62bGrkx27OtmxW6c//jq9uH1kx26NAv9z9G/0xf072bGb vzf4J2WP4Luhmz+DT3z83Y+D/31wTa+vr6cjtX2/6gXye/if3ed/N1zKITyz7ftVDmRhX4VPdjyXQ3hm 2/erGsjCvhqf7Hguh/DMtu9XMZCFfRM+2fFc8Z/+9c7+jixi9E/kvuZi7h0rBbKwb8YnO2YUWD9HDp9+ @@ -2192,7 +2192,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAEWRJREFUeF7t2lez3lXZx3E90XN9CY7vwvGNOJ7GXmlSQhUE6UWKISBKC723IEiz + DgAACw4BQL7hQQAAEWRJREFUeF7t2lez3lXZx3E90XN9CY7vwvGNOJ7GXmlSQhUE6UWKISBKC723IEiz 10fADlYUbI8VK+v551Gcm8sLdsre91rXyufgc/LNTmbf85tceyYrr2qtAXAQSiMA80sjAPNLIwDzSyMA 80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA 80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA80sjAPNLIwDzSyMA @@ -2272,7 +2272,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABf5JREFUeF7t11tzjWcYxvE64ZyPYHwL44sYp4tSpVLbSCm131PbCBkZGRnbxkSR + DgAACw4BQL7hQQAABf5JREFUeF7t11tzjWcYxvE64ZyPYHwL44sYp4tSpVLbSCm131PbCBkZGRnbxkSR 2G+GblRrkmEY1Q0jGNVqqaHuvqtkZrlckbUkufS+3+fgd/LPu965Zp41s568Z2ZJjtGY5AeNSX7QmOQH jUl+0JjkB41JftCY5AeNClVnrDUzlP0t0aFRITt8yzzOVGcGsmeS/kejwssvQJf2zHD2XNK/aFSAL0DR 80xtZjB7PukfNCqUHDzqzIxkn0n6Ho0KcOhMuiQK0KgAh92ddEnsZzQqlBxyOdIlsZ/QqAAHXI6KLolV @@ -2304,7 +2304,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAFKpJREFUeF7t1EGqBUmSQ9Faa2+m15irySam4qLBj0bx3OsOzkQgEBjYf/79919J + DgAACw4BQL7hQQAAFKpJREFUeF7t1EGqBUmSQ9Faa2+m15irySam4qLBj0bx3OsOzkQgEBjYf/79919J 0n8hDCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ 98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/ DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQknQ/DCVJ98NQ @@ -2398,7 +2398,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAAwhJREFUeF7t0sGRHUEIBNG1Vc7IxrXm644ykGoOYhB5eJcKIKKhvz6fjw7DUHdg + DgAACw4BQL7hQQAAAwhJREFUeF7t0sGRHUEIBNG1Vc7IxrXm644ykGoOYhB5eJcKIKKhvz6fjw7DUHdg qDsw1B0Y6g4MdQeGugND3YGh7sBQd2CoOzDUHRjqDgx1B4a6A0Pd8fX9/f3RHX6A4/wAx/kBjvvjB6gF Kedx3d/61/NcCNQkts9zIVCT2D7PhUBNYvs8FwI1ie3zXAjUJLbPcyFQk9g+z4VATWL7PBcCNYnt81wI 1CS2z3MhUJPYPs+FQE1i+zwXAjWJ7fNcCNQkts9zIVCT2D7PhUBNYvs8FwI1ie3zXAjUJLbPcyFQk9g+ @@ -2442,7 +2442,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAI/BJREFUeF7t3Xm4dlVZx/HgfQGRUeB1BDVQnEoQUZFQSANRyaFAUVAuyMIZtKQM + DgAACw4BQL7hQQAAI/BJREFUeF7t3Xm4dlVZx/HgfQGRUeB1BDVQnEoQUZFQSANRyaFAUVAuyMIZtKQM B5ywTEQMQgnBAUUFUcmCKLEUB5ySFFRIAQExzFASFSf63XHei83q95yz197Ps4e1v17X55/b97nXvZ69 WOecvdde69duvvlmAMAE2SAAoHw2CAAonw0CAMpngwCA8tkgAKB8NggAKJ8NAgDKZ4MAgPLZIACgfDYI ACifDQIAymeDAIDy2SAAoHw2CAAonw0CAMpngwCA8tkgAKB8NggAKJ8NAgDKZ4MAgPLZIACgfDYIACif @@ -2601,7 +2601,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADTNJREFUeF7t3XvwdVMZB3D3VOYNuYSEKEoyRvQHb1KJRBGKCtPFJb1DqSFFF5Tb + DgAACw4BQL7hQQAADTNJREFUeF7t3XvwdVMZB3D3VOYNuYSEKEoyRvQHb1KJRBGKCtPFJb1DqSFFF5Tb xEwXpXShZrqMxlDTbZIRqWimSW6vklCT6E43XX59v9NZ5pmn79rXtffZZ9Zq5jPTPO96nv0c6/x+v7P2 XnufVZaWloqMyWCRDxks8iGDRT5ksMiHDBb5kMEiHzJY5EMGi3zIYJEPGSzyIYNFPmSwyIcMFvmQwSIf MljkQwaLfMhgkQ8ZLPIhg0U+ZHDqVq5cuQUcD1+EW+AvsDTzL7gHroQ3wvqqRvE/MjhVmMzl8E34D4QJ @@ -2663,7 +2663,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAJVpJREFUeF7t3QnUNVV15vHIPAnKJIiMxgEFjCgCLYJGEBFoFQSiGEk7QFAconYY + DgAACw4BQL7hQQAAJVpJREFUeF7t3QnUNVV15vHIPAnKJIiMxgEFjCgCLYJGEBFoFQSiGEk7QFAconYY Ihqhl6C0BtGAShAVh2iMQCRGEUWICo0DC1ohzhJBBVEhOEUT6WfH+7WVyvO+b1XdujXs+rvWb2Vl8959 dn117rn3Vp0653fuvvtuAMAE2SAAID8bBADkZ4MAgPxsEACQnw0CAPKzQQBAfjYIAMjPBgEA+dkgACA/ GwQA5GeDAID8bBAAkJ8NAgDys0EAQH42CADIzwYBAPnZIAAgPxsEAORngwCA/GwQAJCfDQIA8rNBAEB+ @@ -2828,7 +2828,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADXJJREFUeF7t3XfsNEUdx3GqFEF6RyFSBESQoKAgRU0UxYYizUgRAqImgBohICLB + DgAACw4BQL7hQQAADXJJREFUeF7t3XfsNEUdx3GqFEF6RyFSBESQoKAgRU0UxYYizUgRAqImgBohICLB WAhFECwhBgUTEhQIIlIEIVHBjiVB0KigKFbsYn98f+Ktjt/ns3t7d7N397udP17Jk+/Nd27mmfvt7ezO zq2wbNmyosdssOgPGyz6wwaL/rDBoj9ssOgPGyz6wwaL/rDBoj9ssOgPGyz6wwaL/rDBoj9ssOgPGyz6 wwaL/rDBoj9ssOgPGyz6wwaL/rDBeXX//fevj6NwOb6OR7As8TN8Fmdga1dH8f9scN4wmE/Cx/BXpAPe @@ -2891,7 +2891,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAC0FJREFUeF7t3b+OXOUdxnGgxVIokdxaVChSJFKSgiuwZOcCaOOK5ApCYy6CKyAN + DgAACw4BQL7hQQAAC0FJREFUeF7t3b+OXOUdxnGgxVIokdxaVChSJFKSgiuwZOcCaOOK5ApCYy6CKyAN ce3GUtIiClMg6nQuaChosnnf9Tiszz47Z/afd/2cT/GR0Ne773nPzuiHPXvmzDtHR0cAbFCMAPSLEYB+ MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA9IsRgH4xAtAvRgD6xQhAvxgB6BcjAP1iBKBfjAD0ixGAfjEC 0C9GAPrFCEC/GAHoFyMA/WIEoF+MAPSLEYB+MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA9IsRgH4xAtAv @@ -2945,7 +2945,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAAzlJREFUeF7t20FOFFEURmEcEIayByeMxWUwZ8IeMHGAO2EVOHMJjhkaFuAOGJho + DgAACw4BQL7hQQAAAzlJREFUeF7t20FOFFEURmEcEIayByeMxWUwZ8IeMHGAO2EVOHMJjhkaFuAOGJho bO8lZSyeJ9K2kC76/CRfAodXr+lcoKuLZm+1WoUYxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNj eGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGB4Y wwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGL+9+7TaLyflslyX27Ka9Pvd+nO9 @@ -2965,7 +2965,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADZ9JREFUeF7t3T+O5Md9xmFJqQhYoQCmhCLBgAE5lAOdgADpAyg1I9knsBLqEDqB + DgAACw4BQL7hQQAADZ9JREFUeF7t3T+O5Md9xmFJqQhYoQCmhCLBgAE5lAOdgADpAyg1I9knsBLqEDqB nEiMmRCQU0EBFSwUO2OgRIESr6uWQ2tZ+251z0wtZ/n2EzwA8dn5/qp6elDc7ek/33n+/DkANyhGAPrF CEC/GAHoFyMA/WIEoF+MAPSLEYB+MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA9IsRgH4xAtAvRgD6xQhA vxgB6BcjAP1iBKBfjAD0ixGAfjEC0C9GAPrFCEC/GAHoFyMA/WIEoF+MAPSLEYB+MQLQL0YA+sUIQL8Y @@ -3029,7 +3029,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAA2VJREFUeF7t2z9OVGEUhnEsCKXswYZaXAY9DXvAxAJ3wiqwcwnWlIYFuAMKE43j + DgAACw4BQL7hQQAAA2VJREFUeF7t2z9OVGEUhnEsCKXswYZaXAY9DXvAxAJ3wiqwcwnWlIYFuAMKE43j OWSMl48nwh2ZcOF5TX6JPHz3ADn8mRmGndVqFWIYwwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zh gTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGB4YwwNjeGAM D4zhgTE8MIYHxvDAGB4YwwNjeGAMD4zhgTE8MIYHxvDAGH99+LLaLUflvFyW67Ja6/9369f1mV2asWQY @@ -3050,7 +3050,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADvpJREFUeF7t2r3R7cpxBVBaDIApMA1mwjBkMhSGQlshyHghKAJ5vAJUR6qp0ea8 + DgAACw4BQL7hQQAADvpJREFUeF7t2r3R7cpxBVBaDIApMA1mwjBkMhSGQlshyHghKAJ5vAJUR6qp0ea8 +wN8PWgsYzm7qqv6HOwZa/7w7ds3AF4ohgD0F0MA+oshAP3FEID+YghAfzEEoL8YAtBfDAHoL4YA9BdD APqLIQD9xRCA/mIIQH8xBKC/GALQXwwB6C+GAPQXQwD6iyEA/cUQgP5iCEB/MQSgvxgC0F8MAegvhgD0 F0MA+oshAP3FEID+YghAfzEEoL8YAtBfDAHoL4YA9BdDAPqLIQD9xRCA/mIIQH8xBKC/GALQXwwB6C+G @@ -3120,7 +3120,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABCpJREFUeF7t3bFxHDsQhGFZCkApKA1lojBkKhSFIvuF8AyG8CKQpxNQe69qa+rn + DgAACw4BQL7hQQAABCpJREFUeF7t3bFxHDsQhGFZCkApKA1lojBkKhSFIvuF8AyG8CKQpxNQe69qa+rn cHkHCIXpNj6nyVlieteBxQ+3282EYWg6MDQdGJoODE0HhqYDQ9OBoenA0HRgaDowNB0Ymg4MTQeGpgND 04Gh6cDQdGBoOjA0HRiaDgxNB4amA8OKXl5ebu8R56vCsCJ6yZk4XxWGFdFLzsT5qjCsiF5yJs5XhWFF 9JIzcb4qDCuil5yJ81VhWBG95EycrwrDiuglZ+J8VRhWRC85E+erwrAiesmZOF8VhhXRS87E+aowrIhe @@ -3144,7 +3144,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAFXxJREFUeF7t3MGNLMt2nWFyIs1JEwR6IcgRQlZwKMgSgZaQU5kgAdcEygHOeFlF + DgAACw4BQL7hQQAAFXxJREFUeF7t3MGNLMt2nWFyIs1JEwR6IcgRQlZwKMgSgZaQU5kgAdcEygHOeFlF tFgLwf/td849mR1Rq/7BN1nAAqIzdu5RZf/F77//Lkn6QBhKkvphKEnqh6EkqR+GkqR+GEqS+mEoSeqH oSSpH4aSpH4YSpL6YShJ6oehJKkfhpKkfhhKkvphKEnqh6EkqR+GkqR+GEqS+mEoSeqHoSSpH4aSpH4Y SpL6YShJ6oehJKkfhpKkfhhKkvphKEnqh6EkqR+GkqR+GEqS+mEoSeqHoSSpH4aSpH4YSpL6YShJ6oeh @@ -3242,7 +3242,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABYRJREFUeF7t3b/VFFcMxmEiCqAF2qATynBIKZRC7BIcUIIrcOb1yLucc4/Ob+68 + DgAACw4BQL7hQQAABYRJREFUeF7t3b/VFFcMxmEiCqAF2qATynBIKZRC7BIcUIIrcOb1yLucc4/Ob+68 334r7h8peJLXSDOSNmCc8OF2u5XEMCx5YFjywLDkgWHJA8OSB4bmjz9vt9+pfXYGtINI7bNbGBpqEql9 dga0g0jts1sYGmoSqX12BrSDSO2zWxgaahKpfXYGtINI7bNbGBq1QZmTej8MjdqgzEm9H4ZGbVDmpN4P Q6M2KHNS74ehURuUOan3w9CoDcqc1PthaNQGZU7q/TA0aoMyJ/V+GBq1QZmTej8MjdqgzEm9H4ZGbVDm @@ -3272,7 +3272,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACt5JREFUeF7t282NLeUVhWGYmLkJwbpZWCRiEQVDi0gsIjFTQjASIZgEmNHezY84 + DgAACw4BQL7hQQAACt5JREFUeF7t282NLeUVhWGYmLkJwbpZWCRiEQVDi0gsIjFTQjASIZgEmNHezY84 lFffqu4+P1WrnsEjoZd7S/o40may9ydPT08AnFCMAPSLEYB+MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA 9IsRgH4xAtAvRgD6xQhAvxgB6BcjAP1iBKBfjAD0ixGAfjEC0C9GAPrFCEC/GAHoFyMA/WIEoF+MAPSL EYB+MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA9IsRgH4xAtAvRgD6xQhAvxgB6BcjAP1iBKBfjAD0ixGA @@ -3324,7 +3324,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAAwZJREFUeF7t3SGOFFEURuEJQSHQGHaAZA9oVoEmLAI9S0ChWcRIFjACxQIQOELS + DgAACw4BQL7hQQAAAwZJREFUeF7t3SGOFFEURuEJQSHQGHaAZA9oVoEmLAI9S0ChWcRIFjACxQIQOELS 3E6mk6rbZ3p66FfJm/xHfKIOeXXFVVWZLq52u52CYVQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqBUTkw KgdG5cCoHBiVA6NyYFQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqB UTkwKgdG5cCoOXy62X0ou0F+l9d9xupCc6mFPSs3dwu81EeacRQ0l1rcm/Jnscj/8b08p/sfBc2nlvd5 @@ -3343,7 +3343,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACslJREFUeF7t20GubHMXxmE69BmCmIWYiBiFphiJGAldQyAxBCag53zr+oiyvefs + DgAACw4BQL7hQQAACslJREFUeF7t20GubHMXxmE69BmCmIWYiBiFphiJGAldQyAxBCag53zr+oiyvefs XVV71zn11tN4Evm5dyd/lSydtd57enoC4AHFCEC/GAHoFyMA/WIEoF+MAPSLEYB+MQLQL0YA+sUIQL8Y AegXIwD9YgSgX4wA9IsRgH4xAtAvRgD6xQhAvxgB6BcjAP1iBKBfjAD0ixGAfjEC0C9GAPrFCEC/GAHo FyMA/WIEoF+MAPSLEYB+MQLQL0YA+sUIQL8YAegXIwD9YgSgX4wA9IsRgH4xAtAvRgD6xQhAvxgB6Bcj @@ -3395,7 +3395,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAAwRJREFUeF7t3btxFFEQRmFZCkApKA0yURiYCoVQsAkBQyEQAR5LD49it3X0rMut + DgAACw4BQL7hQQAAAwRJREFUeF7t3btxFFEQRmFZCkApKA0yURiYCoVQsAkBQyEQAR5LD49it3X0rMut O/Mf4zP2FHfaaJyZ2h1dnU4nBcOoHBiVA6NyYFQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqBUTkwKgdG 5cCoHBiVA6NyYFQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqBUTkwKgdG5cCoHBiVA6NyYFQOjMqBMdnH L6fP5TTIZ5qxEozJamm35fvZEt9ru8YtzVgJxnS1uPuzRb7XPV17NRjT1fKuy9ezZb7Vdvaarr0ajPr1 @@ -3414,7 +3414,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAI/VJREFUeF7t3QusPW1V33G5vNwEQUFAwUtERAUEqUIVpRotEi8RJBHE4gWxEoJo + DgAACw4BQL7hQQAAI/VJREFUeF7t3QusPW1V33G5vNwEQUFAwUtERAUEqUIVpRotEi8RJBHE4gWxEoJo QNRGW1q1sfGKSFpTW9s0xfsVqdYIRSOgqEFJBUUUxCoa72DVipa3a9Hzd5bDb695Zp/Zl7X2d5JPIOuc tWae5/2vefbZe2b2O918880AgAskgwCA/mQQANCfDAIA+pNBAEB/MggA6E8GAQD9ySAAoD8ZBAD0J4MA gP5kEADQnwwCAPqTQQBAfzIIAOhPBgEA/ckgAKA/GQQA9CeDAID+ZBAA0J8MAgD6k0EAQH8yCADoTwYB @@ -3573,7 +3573,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAC1tJREFUeF7t3WmodlUVB3BnzTBMCYMKStOywT4YSigkQVBZGQ1QiGWImkVZvDRQ + DgAACw4BQL7hQQAAC1tJREFUeF7t3WmodlUVB3BnzTBMCYMKStOywT4YSigkQVBZGQ1QiGWImkVZvDRQ hEUF0vAlIhssoXkAragsGymjDBoszN4+ZBGENtI8e/v/X+552c/a/33WeZ6zWWff55wDP17v2mvvtc++ +z7jOXXIzs7OYsZkcDEfMriYDxlczEcW2PetnZ0ppXMhlRMpncs2ygJqESKlcyGVEymdyzbKAmoRIqVz IZUTKZ3LNsoCahEipXMhlRMpncs2ygLRC+DV89pri643tSwQvQBePa+9tuh6U8sC0Qvg1fPaa4uuN7Us @@ -3628,7 +3628,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAH2xJREFUeF7t3QmsPFlVx3FnhpmBkWVkGUYG0QiIMCA4KqgIalQgAhEkYVgcRBYl + DgAACw4BQL7hQQAAH2xJREFUeF7t3QmsPFlVx3FnhpmBkWVkGUYG0QiIMCA4KqgIalQgAhEkYVgcRBYl BJCAiMYFFwwGN0SiRNxiBNxRQJAIImER1IATAUWURRERNwZcURl/B9+fXItf36pb91a/vre/lXwyk/Pe PXW7//fU6dddVf0JV199NQDgCNkgAGB8NggAGJ8NAgDGZ4MAgPHZIABgfDYIABifDQIAxmeDAIDx2SAA YHw2CAAYnw0CAMZngwCA8dkgAGB8NggAGJ8NAgDGZ4MAgPHZIABgfDYIABifDQIAxmeDAIDx2SAAYHw2 @@ -3768,7 +3768,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACipJREFUeF7t3WuobVUVB/Dr2xTDlFCooDQ1e+gHRQmFJAgsK6MHFKIZomZhJhct + DgAACw4BQL7hQQAACipJREFUeF7t3WuobVUVB/Dr2xTDlFCooDQ1e+gHRQmFJAgsK6MHFKIZomZhJhct irCoQCq/SGQPS+j9AHtgWWYaZZRBDwuz24csgtDKovfb0/9/2es29xj/ufbae81z9xx7rgk/zj1jjznP HuPMsx9rr6U7tra2Jg2TwUk7ZHDSDhmctMMFdn5ra2vyf2lvNpELqCa0LO3NJnIB1YSWpb3ZRC6gmtCy tDebyAVaa4DVWv0u0FoDrNbqd4HWGmC1Vr8LtNYAq7X6XaC1Blit1e8CrTXAaq1+F2itAVZr9btAaw2w @@ -3817,7 +3817,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAO1pJREFUeF7t3Xm4LUV5LvA4MQkcBziKMukF5cogccCgRtFEUEQE0ahRRBTCowZR + DgAACw4BQL7hQQAAO1pJREFUeF7t3Xm4LUV5LvA4MQkcBziKMukF5cogccCgRtFEUEQE0ahRRBTCowZR cEAQieIQFSSJBhXUKOIACBqu4ENA1BC8cUCcchHDIIrR62yMOJF433fvPtdOnXetqurVa63uqvePn/h8 Z6+vq76q7r12D9W/99vf/tbMzCokg2ZmVj4ZNDOz8smgmZmVTwbNzKx8MmhmZuWTQTMzK58MmplZ+WTQ zMzKJ4NmZlY+GTQzs/LJoJmZlU8GzcysfDJoZmblk0EzMyufDJqZWflk0MzMyieDZmZWPhk0M7PyyaCZ @@ -4076,7 +4076,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAEi9JREFUeF7tnWnsHWUVxkGoAillS0XZopY1DQWTAoEPIAE+1EDAYKRACCA0DYgf + DgAACw4BQL7hQQAAEi9JREFUeF7tnWnsHWUVxkGoAillS0XZopY1DQWTAoEPIAE+1EDAYKRACCA0DYgf QNmkUSlbIGKCmLAlGjYBiyxiWBIJokhMlCACgYCBqIAogoiCC2p9fnWGvByeuf+ZubPc2/80+aXtmfOe 87zvvXdm3m1mndWrVw/MYqxxYPZgjQOzB2scmD1Y48DswRoHZg/WOO184aertxT7i5PFpWKVeFg8K14V b4vVAWwcwwdfylCWGMTa0uWadqxxmtAHs57YU5wubhcviPjhNgWxyUGuPcR6TtM0YY2Tjhp+E3GUuFm8 @@ -4160,7 +4160,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAGo1JREFUeF7t3QmwLVV5xXHhMQVUBgEfKghiZBAFRcFoGXFIJCTikBhwSAhqqVEM + DgAACw4BQL7hQQAAGo1JREFUeF7t3QmwLVV5xXHhMQVUBgEfKghiZBAFRcFoGXFIJCTikBhwSAhqqVEM alQohzjggLHUGBEiTjFGSTnEWZQETBzjFIyKxtlonEoRLTWIQ8j68s6t169Z996e9j3dX/+t+lUqi3d2 79697/nOOd29+1pXX301AGCGbAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+G AID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgAyM+GAID8bAgA @@ -4279,7 +4279,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACYdJREFUeF7t3XnMddcUx/HOczoYOvzRamlDDUkJUlUVtKSoxpyGamuqBElRhFBD + DgAACw4BQL7hQQAACYdJREFUeF7t3XnMddcUx/HOczoYOvzRamlDDUkJUlUVtKSoxpyGamuqBElRhFBD Iw0hxkhoNIJozULSmoVEianGyKuiMdNSNc8e35/cI7vHb9/p7HPfe5+1/vj88a6799rr3Gffe8+wz3l3 2draSoHZYIrDBlMcNpjisMEUhw2mOGwwxWGDKQ4bTHHYYIrDBlMcNpjisMEUhw2mOGwwxWGDKQ4bTHHY YIrDBlMcNpjisMEUhw2mOGwwxWGDKQ4bTHHYYIrDBlMcNpjisMEUhw2mOGwwxWGDKQ4bTHHYYIrDBlMc @@ -4326,7 +4326,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAH7xJREFUeF7t3Qv4fulc7/EwZpgZc9jYGRqnsJXJYWrIXHKYjmjGsDOunYgd0qWj + DgAACw4BQL7hQQAAH7xJREFUeF7t3Qv4fulc7/EwZpgZc9jYGRqnsJXJYWrIXHKYjmjGsDOunYgd0qWj QbEV6SDhstnkGHZi2lR2ajcoodjYpEKKSlEiEkJINfvz0e9/eaz5/Oe31nOv53nu9X3erut14fv/re+6 72fda93rcK97fckll1wCANhDMQgAqC8GAQD1xSAAoL4YBADUF4MAgPpiEABQXwwCAOqLQQBAfTEIAKgv BgEA9cUgAKC+GAQA1BeDAID6YhAAUF8MAgDqi0EAQH0xCACoLwYBAPXFIACgvhgEANQXgwCA+mIQAFBf @@ -4467,7 +4467,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAC6ZJREFUeF7t3WmsLEUZxnH2TSIgCCKKrGqUiIrKEoVPxmhA1LhDUDASI5goCGhc + DgAACw4BQL7hQQAAC6ZJREFUeF7t3WmsLEUZxnH2TSIgCCKKrGqUiIrKEoVPxmhA1LhDUDASI5goCGhc EBTQEAMogoqARlGjgIDKYiIuiPuCuF4RF1zABVdcEPX4PDdTN3Wq/9UzPaen722rPvyC9+1636rTXTPT M91drre0tFQVDINVOTBYlQODVTkwWJUDg1U5MFiVA4NVOTBYlQODVTkwWJUDg1U5MFiVA4NVOTBYlQOD VTkwWJUDg1U5MFiVA4NVOTBYlQODVTkwWJUDg1U5MFiVA4NVOTBYlQODVTkwWJUDg1U5MDgGq1atWso4 @@ -4523,7 +4523,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAGW5JREFUeF7t3U+oZVeVx3FT0ZBAiIZKJ5Bk0IEgNhUIQUjiqMgsDgKBiCaTgCBt + DgAACw4BQL7hQQAAGW5JREFUeF7t3U+oZVeVx3FT0ZBAiIZKJ5Bk0IEgNhUIQUjiqMgsDgKBiCaTgCBt BSRpaBAMSoiQQXAUMY5UVIQISgtRBMGACk2SQYN0i3ZB2zpw7J+Bpo2t1WvRtziXxe+tevfVu+/t/Tvf wQfkV3ede/beZ68X37v3nHdcunQJALBCMgQA+JMhAMCfDAEA/mQIAPAnQwCAPxkCAPzJEADgT4YAAH8y BAD4kyEAwJ8MAQD+ZAgA8CdDAIA/GQIA/MkQAOBPhgAAfzIEAPiTIQDAnwwBAP5kCADwJ0MAgD8ZAgD8 @@ -4638,7 +4638,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABmxJREFUeF7t3b2vFVUUBXAiCQUQ0BowkUijNhTSYChoMKHAmhgaEqiNjQl/gHZW + DgAACw4BQL7hQQAABmxJREFUeF7t3b2vFVUUBXAiCQUQ0BowkUijNhTSYChoMKHAmhgaEqiNjQl/gHZW /gUQafyoJKGyMsRWKwptNDHBysSPwo/rWslMMve4hHXvmzOzZ84ufglv3bl733P2836/8dBms0kNk2Fq hwxTO2SY2iHD1A4ZpnbIkB4/fryZUtl/7dQe1FT278mQVJGayv5rp/agprJ/T4akitRU9l87tQc1lf17 MiRVpKay/9qpPaip7N+TIbkFUkzu/GRIboEUkzs/GZJbIMXkzk+G5BZIMbnzkyG5BVJM7vxkSG6BFJM7 @@ -4671,7 +4671,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAJglJREFUeF7t3XvQXVWZ5/EWW0dth24HS2qUqh67bNspqLGtnvJSNVM085c9VU4j + DgAACw4BQL7hQQAAJglJREFUeF7t3XvQXVWZ5/EWW0dth24HS2qUqh67bNspqLGtnvJSNVM085c9VU4j EBJuIdzDJSDNqC1CIwygaE9VBwjhlhAgmNCg0gG5KOGWhLwJgYSES4BwDaEd2uKi0yIgnHmel5PhZfF7 3/ec8551zrPW/v7xKa2Hs/fZa+3nWfvNPnuv9XutVgsA0EAyCAConwwCAOongwCA+skgAKB+MggAqJ8M AgDqJ4MAgPrJIACgfjIIAKifDAIA6ieDAID6ySAAoH4yCAConwwCAOongwCA+skgAKB+MggAqJ8MAgDq @@ -4839,7 +4839,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAC3xJREFUeF7t3GuMXVUVB3ACSiIQqMZoIg8jERNQE5soXzBEGyMf+FBKKcgE2uKU + DgAACw4BQL7hQQAAC3xJREFUeF7t3GuMXVUVB3ACSiIQqMZoIg8jERNQE5soXzBEGyMf+FBKKcgE2uKU PqCiFaXFBmmNSBONWtrSVvsQZtoBxAINLVZs+p7SB50+AOujRe3LQBrTaGu0j+P6T+/Vc9f6z8xd5557 ex77wy+Zu+65Z+2915lz9nnce14URUGJ0WBQHjQYlAcNBuVhAj9aezgK8kPXz8sEWJIgu3T9vEyAJQmy S9fPywRYkiC7dP28TIAlCbJL18/LBFiSILt0/bxMgCUJskvXz8sEWJIgu3T9vEyAJQmyS9fPywRYkiC7 @@ -5011,7 +5011,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAF7JJREFUeF7t3fuzXXV9xnH/iyoiOloBnbG3mU6VUtppbZlpcax1dFqd4q2lo9Xp + DgAACw4BQL7hQQAAF7JJREFUeF7t3fuzXXV9xnH/iyoiOloBnbG3mU6VUtppbZlpcax1dFqd4q2lo9Xp RVu1trRUaa1OCwkmECoESyQw4W4g3G/hGsAEAiQgRAI597PPPufkD/h2fzY9Ap8+JNnrrH3W2s9+//Aa Zx5w75N8hudz9trr8qZSCgBgDMkQAOBPhgAAfzIEAPiTIQDAnwwBAP5kCADwJ0MAgD8ZAgD8yRAA4E+G AAB/MgQA+JMhAMCfDAEA/mQIAPAnQwCAPxkCAPzJEADgT4YAAH8yBAD4kyEAwJ8MAQD+ZAgA8CdDAIA/ @@ -5118,7 +5118,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAABWxJREFUeF7t3DuOJFUQheHZBTtgBeyAHbACdsAOWAHCw2iNhwcWFsLBQ8LAwkF4 + DgAACw4BQL7hQQAABWxJREFUeF7t3DuOJFUQheHZBTtgBeyAHbACdsAOWAHCw2iNhwcWFsLBQ8LAwkF4 ODC8hmcvIFGM6qLQ4e983koydI/xOacro1GeqKysqh6eTdNkA8PQxoGhjQNDGweGNg4MbRwY2jgwtHFg mD08PExWl/apMMxoqNWhfSoMMxpqdWifCsOMhlod2qfCMKOhVof2qTDMaKjVoX0qDDMaanVonwrDjIZa HdqnwjCjoVaH9qkwzGio1aF9KgwzGmp1aJ8Kw4yGWh3ap8Iwo6FWh/apMMxoqNWhfSoMMxpqdWifCsOM @@ -5147,7 +5147,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAJydJREFUeF7t3WmYVNW97/FDRjxJnjzxJlGPEb0az3CJ57kniejJ9d4GTFCP6W7m + DgAACw4BQL7hQQAAJydJREFUeF7t3WmYVNW97/FDRjxJnjzxJlGPEb0az3CJ57kniejJ9d4GTFCP6W7m LhRRURRHwBlUEDQOJ9EMIoizQYKAgMFElIhKioamu2xQAQeUQQWJMYAC2jS1q+5/QVo2m393V3fXsNda 3xefN79H6e6qWr+991qr9v6HbDYLAPCQGgIA3KeGAAD3qSEAwH1qCABwnxoCANynhgAA96khAMB9aggA cJ8aAgDcp4YAAPepIQDAfWoIAHCfGgIA3KeGAAD3qSEAwH1qCABwnxoCANynhgAA96khAMB9aggAcJ8a @@ -5320,7 +5320,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAADdpJREFUeF7t3W1wVNUZB3CZAT44DF/aWui0A5OZQjtTmPaDM+1MOwuxYrHNJiQk + DgAACw4BQL7hQQAADdpJREFUeF7t3W1wVNUZB3CZAT44DF/aWui0A5OZQjtTmPaDM+1MOwuxYrHNJiQk WVB5EySjEgShDGgTiahjLU5jsSpY30VFklgdsUQRdYEEshIFEaog+Ia0IylT3wi5d7f/B7p69+yzuS97 7r3n3OTD78M+kOzu/7l77zn33rM5L5PJDBrA2OKggYMtDho42OKggYMtai8ZGwuXwnXQDG3QCYehBwzI CKhG/0b/h/4v/Qz9LP0O+l1j2efSHFvUSjI2Ai6BNfAinAKxubLQ76bnoOei5xzBviaNsEXlJWMlsAy2 @@ -5385,7 +5385,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAKuhJREFUeF7t3QvQbWV93/GAcPSAHJRouRgnJ2USLiYNgkisCFISwxzRthkJ0LQB + DgAACw4BQL7hQQAAKuhJREFUeF7t3QvQbWV93/GAcPSAHJRouRgnJ2USLiYNgkisCFISwxzRthkJ0LQB SYWpdAa5WCcjkqDpNBLbGq3JgAUtmDheaBOkMNQKTqytEVM19a7RoJlo5BJBRSVc+vt51hn2Wfzed621 99q35/k685lJ/rznvy7P///s9117rWf9yCOPPAIAqFAMAgDKF4MAgPLFIACgfDEIAChfDAIAyheDAIDy xSAAoHwxCAAoXwwCAMoXgwCA8sUgAKB8MQgAKF8MAgDKF4MAgPLFIACgfDEIAChfDAIAyheDAIDyxSAA @@ -5574,7 +5574,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAD3pJREFUeF7tnVnMJFUZhtnRUZC4sCmCwBCJATUgQshoRAMiSKIJqAkBcRnGiwkC + DgAACw4BQL7hQQAAD3pJREFUeF7tnVnMJFUZhtnRUZC4sCmCwBCJATUgQshoRAMiSKIJqAkBcRnGiwkC GoaoIyGjgRAjqMEL45AQL9SwxS0BRVmCokHREIQxaERUcAG8UFlEft8HTyWnv//t/qura+u/6+K56K+/ s71fd1WdU2fZbmlpaWCBscaBxcEaBxYHaxxYHKxx3tm2bdv+4kTxUXGFuE7cKR4Qj4lnxFIAG9/hgy9p SEse5LW/K2vescZ5QoFZI04QF4ubxOMiBrcuHhWUQVmUucbVaZ6wxr4j4Q8U54qbxVPCBasNKJs6UJcD @@ -5646,7 +5646,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAAGX1JREFUeF7t3XmovVtdx3FnvKldE6m0HBocyAoCNbS8ppTRIIKaipY2GIiZKRUS + DgAACw4BQL7hQQAAGX1JREFUeF7t3XmovVtdx3FnvKldE6m0HBocyAoCNbS8ppTRIIKaipY2GIiZKRUS zQMJRipJWJGZRRgNBmmYpaIVaXNm4hhpJU2mpaZmw+274LdrP5/7Od89nP08+/l9fP/x+sPPb3/WWudZ 2/2cu84++9zo+uuvBwB8DLIhACCfDQEA+WwIAMhnQwBAPhsCAPLZEACQz4YAgHw2BADksyEAIJ8NAQD5 bAgAyGdDAEA+GwIA8tkQAJDPhgCAfDYEAOSzIQAgnw0BAPlsCADIZ0MAQD4bAgDy2RAAkM+GAIB8NgQA @@ -5761,7 +5761,7 @@ iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EAAACxABrSO9dQAACCFJREFUeF7t21moVWUUB3DHMkrMkgbEwrIgGohogLBBrIcwMIiiIiiQegmSsLco + DgAACw4BQL7hQQAACCFJREFUeF7t21moVWUUB3DHMkrMkgbEwrIgGohogLBBrIcwMIiiIiiQegmSsLco HwujhKh8ix4Kg0KhCQqiiSYajIjKaBKjolmabLz91+FsWd/yv/faZ/CwuWsJP+pb3/p/53Offe/e99zt jKmpqRQYLaY4aDHFQYspDlpMcdBiioMWUxy0mOKgxRQHLaY4aDHFQYspDlpMcdBiioMWUxy0mOKgxRQH LaY4aDHFQYspDlpMcdBiioMWUxy0mOKgxRQHLaY4aDHFQYspDlpMcdBiioMWUxy0mOKgxRQHLaY4aDHF @@ -5802,7 +5802,7 @@ iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vgAADr4B6kKxwAAABmhJREFUeF7tnFmMFEUYx9cDDyDeikrECxHFF8ErMTGoMNPVXT2uD2M0PhkJhBgf + vAAADrwBlbxySQAABmhJREFUeF7tnFmMFEUYx9cDDyDeikrECxHFF8ErMTGoMNPVXT2uD2M0PhkJhBgf TFRUYIEYFW/lUIknKEbREEUlwC7bxy4ryIqwEGVBBR5Qg0YTTbwIO/5r/IYMPbWzs9PTs7PT3y/5pZed Pqr+9FZXddd0A8MwDMMwDMMwDMMwccGZOPGEHen0ce0p86qNQpxEv2aqwXopR3qWmOFZxvueFBksF7da ook+ZqKkzbIuQuhdvhQHsuGTriX+wvJjWo2JgvWmeT5C3pYffKHmp7Q6U0lUe++YxjSc6b/rgz/C1SvS @@ -5835,7 +5835,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vgAADr4B6kKxwAAAAd1JREFUWEftlTtLw2AUhnvxAt2sqIOr4ij4AxxENGm+1oIUBx1cnN0ULziJWtRd + vAAADrwBlbxySQAAAd1JREFUWEftlTtLw2AUhnvxAt2sqIOr4ij4AxxENGm+1oIUBx1cnN0ULziJWtRd xd1JEMFBvDS1Hbx0kqKLk3/ASW2hNL6nnkhIQ40mjSB94KGSL995QmxSX4N/x4UQwbSQh1RF7uZD3pFP JPyI78MSfIWDvOQN2Xg8gOgz1Ng3FXeDl70BwVnDBZDvcISX60taRNoQuzbEdQtQ4dPqw9Vn/MYQNVuE Y3y6u6SFFMbwWnHdYkrIEm9zBwyl+K0h8p3HvPVnqCLSj2d74DImN/Eh3PZK/M4UsPIE0qNZwIxp3m4f @@ -5849,7 +5849,7 @@ iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vgAADr4B6kKxwAAABiBJREFUeF7tnF1oHUUUx9Na6zdo/SraItZPREFJUaNtMrtJc2kVpErwQQX1wSdR + vAAADrwBlbxySQAABiBJREFUeF7tnF1oHUUUx9Na6zdo/SraItZPREFJUaNtMrtJc2kVpErwQQX1wSdR VLC0fnAL9qFok+zMvYaoDyIKNdU+iA1iC6WKQmuV1qTZnb2poQa1KklQUz+q7fXM5tzbTeghH72ZTMr5 wWE39+zO/s/8d2c2D7NVDMMwDMMwDMMwDMMwDMMwDMMwDMMws4CBgYGXBgcHi2OiDdOzlllTFyXUk3ob xAYv0GsbZOFOPHzWcDoYUPRlfAS2wxA/ekH0NWzX+UqvbMz3LsbTneU0MCA6BJ1eNEbAPm5TEehdvoof @@ -5881,7 +5881,7 @@ iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO - vgAADr4B6kKxwAAAAXpJREFUWEftlTFLw0AYhiO66CKORV10qKCbm0ObxCJWXATFQZ39Aa4iKIibkMRS + vAAADrwBlbxySQAAAXpJREFUWEftlTFLw0AYhiO66CKORV10qKCbm0ObxCJWXATFQZ39Aa4iKIibkMRS EBER/AWCjuKkYLeC3qVaFwen+hd8Gr6AlVpTjB00Dzyk35e768sl5IyEhISvqNVqRTyyXD2ecyvd0m6b cB0po8MkhRUCvOEzXtiuv2J6OpU5qPbIsG8J15EyOkyq4j1/XDIdnbFcf4rf23iFZTzGJdPVQzKlKeE6 UkbnQ4Cy5egJaRszp69dlqP66U/jDpZQYRHz2YJqeFyxB2gG91PsxAJj73Be2gEdCRDC2C0e1ZqUAZ0O @@ -5893,7 +5893,7 @@ iVBORw0KGgoAAAANSUhEUgAAAYAAAAGACAYAAACkx7W/AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL - EQAACxEBf2RfkQAAMOFJREFUeF7t3TusXFW2LuBji2MJEO4rboLoxBJBnyYiuNyI44bUokMuEIEIIEQy + DwAACw8BkvkDpQAAMOFJREFUeF7t3TusXFW2LuBji2MJEO4rboLoxBJBnyYiuNyI44bUokMuEIEIIEQy QScIiZCYDAlOCzJOk7QsJGjaD2z8xg+MX9sv/KCD0w8dWjrqh4Tq/v+qmuXa03/Vrqpdj1Vj/MEnWWOX q1atOeZYq9aac65/6XQ6ZmaWkAyamVl8MmhmZvHJoJmZxSeDZmYWnwyamVl8MmhmZvHJoJmZxSeDZmYW nwyamVl8MmhmZvHJoJmZxSeDZmYWnwyamVl8MmhmZvHJoJmZxSeDZmYWnwyamVl8MmhmZvHJoJmZxSeD @@ -6107,83 +6107,131 @@ - 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 - + \ No newline at end of file diff --git a/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs new file mode 100644 index 00000000000..ad048467c62 --- /dev/null +++ b/src/Libraries/CoreNodeModels/CurveMapperNodeModel.cs @@ -0,0 +1,951 @@ +using DSCore.CurveMapper; +using Dynamo.Graph.Nodes; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace CoreNodeModels +{ + [IsDesignScriptCompatible] + [NodeName("Curve Mapper")] + [NodeCategory("Math.Graph.Create")] + [NodeDescription("Generates a list of values mapped from a curve.")] // TODO: Add to resources + [NodeSearchTags("graph;curve;mapper;math")] + public class CurveMapperNodeModel : NodeModel + { + private double minLimitX = 0; + private double maxLimitX = 1; + private double minLimitY = 0; + private double maxLimitY = 1; + private int pointsCount = 10; + + private List outputValuesY; + private List outputValuesX; + private List renderValuesY; + private List renderValuesX; + + private readonly IntNode minLimitXDefaultValue = new IntNode(0); + private readonly IntNode maxLimitXDefaultValue = new IntNode(1); + private readonly IntNode minLimitYDefaultValue = new IntNode(0); + private readonly IntNode maxLimitYDefaultValue = new IntNode(1); + private readonly IntNode pointsCountDefaultValue = new IntNode(10); + + private ControlPointData DefaultLinearCurvePoint1 => new ControlPointData(0, DynamicCanvasSize); + private ControlPointData DefaultLinearCurvePoint2 => new ControlPointData(DynamicCanvasSize, 0); + private ControlPointData DefaultBezierCurvePoint1 => new ControlPointData(0, DynamicCanvasSize); + private ControlPointData DefaultBezierCurvePoint2 => new ControlPointData(DynamicCanvasSize, DynamicCanvasSize); + private ControlPointData DefaultBezierCurvePoint3 => new ControlPointData(DynamicCanvasSize * 0.2, DynamicCanvasSize * 0.2); + private ControlPointData DefaultBezierCurvePoint4 => new ControlPointData(DynamicCanvasSize * 0.8, DynamicCanvasSize * 0.2); + private ControlPointData DefaultSineWavePoint1 => new ControlPointData(DynamicCanvasSize * 0.25, 0); + private ControlPointData DefaultSineWavePoint2 => new ControlPointData(DynamicCanvasSize * 0.75, DynamicCanvasSize); + private ControlPointData DefaultCosineWavePoint1 => new ControlPointData(0, 0); + private ControlPointData DefaultCosineWavePoint2 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize); + private ControlPointData DefaultParabolicCurvePoint1 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize * 0.1); + private ControlPointData DefaultParabolicCurvePoint2 => new ControlPointData(DynamicCanvasSize, DynamicCanvasSize); + private ControlPointData DefaultPerlinNoiseCurvePoint1 => new ControlPointData(DynamicCanvasSize * 0.5, 0); + private ControlPointData DefaultPerlinNoiseCurvePoint2 => new ControlPointData(0, DynamicCanvasSize); + private ControlPointData DefaultPerlinNoiseCurvePoint3 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize * 0.5); + private ControlPointData DefaultPowerCurvePoint1 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize * 0.5); + private ControlPointData DefaultSquareRootCurvePoint1 => new ControlPointData(0, DynamicCanvasSize); + private ControlPointData DefaultSquareRootCurvePoint2 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize * 0.5); + private ControlPointData DefaultGaussianCurvePoint1 => new ControlPointData(0, DynamicCanvasSize * 0.8); + private ControlPointData DefaultGaussianCurvePoint2 => new ControlPointData(DynamicCanvasSize * 0.5, DynamicCanvasSize * 0.5, "GaussianCurveControlPointData2"); + private ControlPointData DefaultGaussianCurvePoint3 => new ControlPointData(DynamicCanvasSize * 0.4, DynamicCanvasSize, "GaussianCurveControlPointData3"); + private ControlPointData DefaultGaussianCurvePoint4 => new ControlPointData(DynamicCanvasSize * 0.6, DynamicCanvasSize, "GaussianCurveControlPointData4"); + + + private GraphTypes selectedGraphType; + private const double defaultCanvasSize = 240; + private double dynamicCanvasSize = defaultCanvasSize; + private bool isLocked; + + #region Curves & Point Data + + /// Point data for the 1st control point of the linear curve. + [JsonProperty] + public ControlPointData LinearCurveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the linear curve. + [JsonProperty] + public ControlPointData LinearCurveControlPointData2 { get; private set; } + private LinearCurve linearCurve; + + /// Point data for the 1st control point of the sine wave. + [JsonProperty] + public ControlPointData BezierCurveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the sine wave. + [JsonProperty] + public ControlPointData BezierCurveControlPointData2 { get; private set; } + /// Point data for the 3rd control point of the sine wave. + [JsonProperty] + public ControlPointData BezierCurveControlPointData3 { get; private set; } + /// Point data for the 4th control point of the sine wave. + [JsonProperty] + public ControlPointData BezierCurveControlPointData4 { get; private set; } + private BezierCurve bezierCurve; + + /// Point data for the 1st control point of the sine wave. + [JsonProperty] + public ControlPointData SineWaveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the sine wave. + [JsonProperty] + public ControlPointData SineWaveControlPointData2 { get; private set; } + private SineWave sineWave; + + /// Point data for the 1st control point of the cosine wave. + [JsonProperty] + public ControlPointData CosineWaveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the cosine wave. + [JsonProperty] + public ControlPointData CosineWaveControlPointData2 { get; private set; } + private SineWave cosineWave; + + /// Point data for the 1st control point of the parabolic curve. + [JsonProperty] + public ControlPointData ParabolicCurveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the parabolic curve. + [JsonProperty] + public ControlPointData ParabolicCurveControlPointData2 { get; private set; } + private ParabolicCurve parabolicCurve; + + /// Point data for the 1st control point of the perlin noise curve. + [JsonProperty] + public ControlPointData PerlinNoiseControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the perlin noise curve. + [JsonProperty] + public ControlPointData PerlinNoiseControlPointData2 { get; private set; } + /// Point data for the 3rd control point of the perlin noise curve. + [JsonProperty] + public ControlPointData PerlinNoiseControlPointData3 { get; private set; } + private PerlinNoiseCurve perlinNoiseCurve; + + /// Point data for the power curve control point. + [JsonProperty] + public ControlPointData PowerCurveControlPointData1 { get; private set; } + private PowerCurve powerCurve; + + /// Point data for the 1st control point of the square root curve. + [JsonProperty] + public ControlPointData SquareRootCurveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the square root curve. + [JsonProperty] + public ControlPointData SquareRootCurveControlPointData2 { get; private set; } + private SquareRootCurve squareRootCurve; + + /// Point data for the 1st control point of the Gaussian curve. + [JsonProperty] + public ControlPointData GaussianCurveControlPointData1 { get; private set; } + /// Point data for the 2nd control point of the Gaussian curve. + [JsonProperty] + public ControlPointData GaussianCurveControlPointData2 { get; private set; } + /// Point data for the 3rd control point of the Gaussian curve. + [JsonProperty] + public ControlPointData GaussianCurveControlPointData3 { get; private set; } + /// Point data for the 4th control point of the Gaussian curve. + [JsonProperty] + public ControlPointData GaussianCurveControlPointData4 { get; private set; } + private GaussianCurve gaussianCurve; + + #endregion + + #region Inputs + + /// Gets or sets the minimum X limit for the curve. + [JsonProperty] + public double MinLimitX + { + get => minLimitX; + set + { + if (minLimitX != value) + { + minLimitX = value; + this.RaisePropertyChanged(nameof(MinLimitX)); + this.RaisePropertyChanged(nameof(MidValueX)); + OnNodeModified(); + } + } + } + + /// Gets or sets the maximum X limit for the curve. + [JsonProperty] + public double MaxLimitX + { + get => maxLimitX; + set + { + if (maxLimitX != value) + { + maxLimitX = value; + this.RaisePropertyChanged(nameof(MaxLimitX)); + this.RaisePropertyChanged(nameof(MidValueX)); + OnNodeModified(); + } + } + } + + /// Gets or sets the minimum Y limit for the curve. + [JsonProperty] + public double MinLimitY + { + get => minLimitY; + set + { + if (minLimitY != value) + { + minLimitY = value; + this.RaisePropertyChanged(nameof(MinLimitY)); + this.RaisePropertyChanged(nameof(MidValueY)); + OnNodeModified(); + } + } + } + + /// Gets or sets the maximum Y limit for the curve. + [JsonProperty] + public double MaxLimitY + { + get => maxLimitY; + set + { + if (maxLimitY != value) + { + maxLimitY = value; + this.RaisePropertyChanged(nameof(MaxLimitY)); + this.RaisePropertyChanged(nameof(MidValueY)); + OnNodeModified(); + } + } + } + + /// Gets or sets the number of points used to compute the curve. + [JsonProperty] + public int PointsCount + { + get => pointsCount; + set + { + if (pointsCount != value) + { + pointsCount = value; + this.RaisePropertyChanged(nameof(PointsCount)); + OnNodeModified(); + } + } + } + + /// Gets the midpoint value of the X range. + [JsonIgnore] + public double MidValueX => (MaxLimitX + MinLimitX) * 0.5; + + /// Gets the midpoint value of the Y range. + [JsonIgnore] + public double MidValueY => (MaxLimitY + MinLimitY) * 0.5; + + #endregion + + #region Output + + /// Gets or sets the Y values used for rendering the curve. + [JsonIgnore] + public List RenderValuesY + { + get => renderValuesY; + set + { + renderValuesY = value; + OnNodeModified(); + } + } + /// Gets or sets the X values used for rendering the curve. + [JsonIgnore] + public List RenderValuesX + { + get => renderValuesX; + set + { + renderValuesX = value; + OnNodeModified(); + } + } + + #endregion + + /// Gets or sets the dynamic size of the canvas, scaling control points accordingly. + [JsonProperty] + public double DynamicCanvasSize + { + get => dynamicCanvasSize; + set + { + if (dynamicCanvasSize != value) + { + double oldSize = dynamicCanvasSize; + dynamicCanvasSize = System.Math.Max(value, defaultCanvasSize); + + ScaleAllControlPoints(oldSize, dynamicCanvasSize); + RaisePropertyChanged(nameof(DynamicCanvasSize)); + OnNodeModified(); + GenerateRenderValues(); + } + } + } + + /// Gets a list of graph type descriptions for UI selection. + [JsonIgnore] + public List GraphTypesList => Enum.GetValues(typeof(GraphTypes)) + .Cast() + .Select(value => GetEnumDescription(value)) + .ToList(); + + /// Gets or sets the selected graph type as a description for UI binding. + [JsonIgnore] + public string SelectedGraphTypeDescription + { + get => GetEnumDescription(SelectedGraphType); + set + { + SelectedGraphType = Enum.GetValues(typeof(GraphTypes)) + .Cast() + .FirstOrDefault(e => GetEnumDescription(e) == value); + + RaisePropertyChanged(nameof(SelectedGraphType)); + RaisePropertyChanged(nameof(SelectedGraphTypeDescription)); + } + } + + /// Gets or sets the currently selected graph type. + [JsonConverter(typeof(StringEnumConverter))] + public GraphTypes SelectedGraphType + { + get => selectedGraphType; + set + { + selectedGraphType = value; + GenerateRenderValues(); + RaisePropertyChanged(nameof(SelectedGraphType)); + OnNodeModified(); + } + } + + /// Gets or sets a value indicating whether the control points are locked. + [JsonProperty] + public bool IsLocked + { + get => isLocked; + set + { + if (isLocked != value) + { + isLocked = value; + RaisePropertyChanged(nameof(IsLocked)); + } + } + } + + #region Constructors + + public CurveMapperNodeModel() + { + if (InPorts.Count == 0) + { + InPorts.Add(new PortModel(PortType.Input, this, new PortData("x-MinLimit", + "Minimum value for the X-axis domain.", + minLimitXDefaultValue))); + InPorts.Add(new PortModel(PortType.Input, this, new PortData("x-MaxLimit", + "Maximum value for the X-axis domain.", + maxLimitXDefaultValue))); + InPorts.Add(new PortModel(PortType.Input, this, new PortData("y-MinLimit", + "Minimum value for the Y-axis domain.", + minLimitYDefaultValue))); + InPorts.Add(new PortModel(PortType.Input, this, new PortData("y-MaxLimit", + "Maximum value for the Y-axis domain.", + maxLimitYDefaultValue))); + InPorts.Add(new PortModel(PortType.Input, this, new PortData("count", + "Number of values to generate.", + pointsCountDefaultValue))); + } + if (OutPorts.Count == 0) + { + OutPorts.Add(new PortModel(PortType.Output, this, new PortData("y-Values", "Values derived from the curve."))); + OutPorts.Add(new PortModel(PortType.Output, this, new PortData("x-Values", "Values derived from the curve."))); + } + + RegisterAllPorts(); + + foreach (var port in InPorts) + { + port.Connectors.CollectionChanged += Connectors_CollectionChanged; + } + + SelectedGraphType = GraphTypes.Empty; + ArgumentLacing = LacingStrategy.Disabled; + + InitiateControlPointData(); + GenerateRenderValues(); + } + + [JsonConstructor] + public CurveMapperNodeModel(IEnumerable inPorts, IEnumerable outPorts, + double dynamicCanvasSize = defaultCanvasSize) : base(inPorts, outPorts) + { + foreach (var port in InPorts) + { + port.Connectors.CollectionChanged += Connectors_CollectionChanged; + } + + DynamicCanvasSize = dynamicCanvasSize; + ArgumentLacing = LacingStrategy.Disabled; + } + + #endregion + + #region Event Handers + + private void Connectors_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + OnNodeModified(); + } + + #endregion + + #region Public Methods + + /// + /// Generates and updates the X and Y render values based on the selected graph type and control points, or displays a warning if the curve is invalid. + /// + public void GenerateRenderValues() + { + ClearErrorsAndWarnings(); + + if (SelectedGraphType == GraphTypes.Empty) + { + RenderValuesX = RenderValuesY = null; + return; + } + if (!IsValidCurve()) + { + ClearErrorsAndWarnings(); + Warning("The provided original values cannot be redistributed using the curve equation.", isPersistent: true); // TODO: add to resources + + RenderValuesX = RenderValuesY = null; + return; + } + object curve = null; + + switch (SelectedGraphType) + { + case GraphTypes.LinearCurve: + curve = new LinearCurve( + LinearCurveControlPointData1.X, (DynamicCanvasSize - LinearCurveControlPointData1.Y), + LinearCurveControlPointData2.X, (DynamicCanvasSize - LinearCurveControlPointData2.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.BezierCurve: + curve = new BezierCurve( + BezierCurveControlPointData1.X, (DynamicCanvasSize - BezierCurveControlPointData1.Y), + BezierCurveControlPointData2.X, (DynamicCanvasSize - BezierCurveControlPointData2.Y), + BezierCurveControlPointData3.X, (DynamicCanvasSize - BezierCurveControlPointData3.Y), + BezierCurveControlPointData4.X, (DynamicCanvasSize - BezierCurveControlPointData4.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.SineWave: + curve = new SineWave( + SineWaveControlPointData1.X, (DynamicCanvasSize - SineWaveControlPointData1.Y), + SineWaveControlPointData2.X, (DynamicCanvasSize - SineWaveControlPointData2.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.CosineWave: + curve = new SineWave( + CosineWaveControlPointData1.X, (DynamicCanvasSize - CosineWaveControlPointData1.Y), + CosineWaveControlPointData2.X, (DynamicCanvasSize - CosineWaveControlPointData2.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.ParabolicCurve: + curve = new ParabolicCurve( + ParabolicCurveControlPointData1.X, (DynamicCanvasSize - ParabolicCurveControlPointData1.Y), + ParabolicCurveControlPointData2.X, (DynamicCanvasSize - ParabolicCurveControlPointData2.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.PerlinNoiseCurve: + curve = new PerlinNoiseCurve( + PerlinNoiseControlPointData1.X, (DynamicCanvasSize - PerlinNoiseControlPointData1.Y), + PerlinNoiseControlPointData2.X, (DynamicCanvasSize - PerlinNoiseControlPointData2.Y), + PerlinNoiseControlPointData3.X, (DynamicCanvasSize - PerlinNoiseControlPointData3.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.PowerCurve: + curve = new PowerCurve( + PowerCurveControlPointData1.X, (DynamicCanvasSize - PowerCurveControlPointData1.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.SquareRootCurve: + curve = new SquareRootCurve( + SquareRootCurveControlPointData1.X, (DynamicCanvasSize - SquareRootCurveControlPointData1.Y), + SquareRootCurveControlPointData2.X, (DynamicCanvasSize - SquareRootCurveControlPointData2.Y), + DynamicCanvasSize + ); + break; + case GraphTypes.GaussianCurve: + curve = new GaussianCurve( + GaussianCurveControlPointData1.X, (DynamicCanvasSize - GaussianCurveControlPointData1.Y), + GaussianCurveControlPointData2.X, (DynamicCanvasSize - GaussianCurveControlPointData2.Y), + GaussianCurveControlPointData3.X, (DynamicCanvasSize - GaussianCurveControlPointData3.Y), + GaussianCurveControlPointData4.X, (DynamicCanvasSize - GaussianCurveControlPointData4.Y), + DynamicCanvasSize + ); + break; + } + + if (curve is not null) + { + dynamic dynamicCurve = curve; + RenderValuesX = dynamicCurve.GetCurveXValues(PointsCount, true); + RenderValuesY = dynamicCurve.GetCurveYValues(PointsCount, true); + } + } + + /// + /// Resets the curves to their original state + /// + public void ResetControlPointData() + { + var propertyNames = new List(); + + if (SelectedGraphType == GraphTypes.LinearCurve) + { + LinearCurveControlPointData1 = DefaultLinearCurvePoint1; + LinearCurveControlPointData2 = DefaultLinearCurvePoint2; + propertyNames = new List() { nameof(LinearCurveControlPointData1), nameof(LinearCurveControlPointData2) }; + } + else if (SelectedGraphType == GraphTypes.BezierCurve) + { + BezierCurveControlPointData1 = DefaultBezierCurvePoint1; + BezierCurveControlPointData2 = DefaultBezierCurvePoint2; + BezierCurveControlPointData3 = DefaultBezierCurvePoint3; + BezierCurveControlPointData4 = DefaultBezierCurvePoint4; + propertyNames = new List() + { + nameof(BezierCurveControlPointData1), nameof(BezierCurveControlPointData2), + nameof(BezierCurveControlPointData3), nameof(BezierCurveControlPointData4) + }; + } + else if (SelectedGraphType == GraphTypes.SineWave) + { + SineWaveControlPointData1 = DefaultSineWavePoint1; + SineWaveControlPointData2 = DefaultSineWavePoint2; + propertyNames = new List() { nameof(SineWaveControlPointData1), nameof(SineWaveControlPointData2) }; + } + else if (SelectedGraphType == GraphTypes.CosineWave) + { + CosineWaveControlPointData1 = DefaultCosineWavePoint1; + CosineWaveControlPointData2 = DefaultCosineWavePoint2; + propertyNames = new List() { nameof(CosineWaveControlPointData1), nameof(CosineWaveControlPointData2) }; + } + else if (SelectedGraphType == GraphTypes.ParabolicCurve) + { + ParabolicCurveControlPointData1 = DefaultParabolicCurvePoint1; + ParabolicCurveControlPointData2 = DefaultParabolicCurvePoint2; + propertyNames = new List() { nameof(ParabolicCurveControlPointData1), nameof(ParabolicCurveControlPointData2) }; + } + else if (SelectedGraphType == GraphTypes.PerlinNoiseCurve) + { + PerlinNoiseControlPointData1 = DefaultPerlinNoiseCurvePoint1; + PerlinNoiseControlPointData2 = DefaultPerlinNoiseCurvePoint2; + PerlinNoiseControlPointData3 = DefaultPerlinNoiseCurvePoint3; + propertyNames = new List() + { + nameof(PerlinNoiseControlPointData1), nameof(PerlinNoiseControlPointData2), + nameof(PerlinNoiseControlPointData3) + }; + } + else if (SelectedGraphType == GraphTypes.PowerCurve) + { + PowerCurveControlPointData1 = DefaultPowerCurvePoint1; + propertyNames = new List() { nameof(PowerCurveControlPointData1) }; + } + else if (SelectedGraphType == GraphTypes.SquareRootCurve) + { + SquareRootCurveControlPointData1 = DefaultSquareRootCurvePoint1; + SquareRootCurveControlPointData2 = DefaultSquareRootCurvePoint2; + propertyNames = new List() { nameof(SquareRootCurveControlPointData1), nameof(SquareRootCurveControlPointData2) }; + } + else if (SelectedGraphType == GraphTypes.GaussianCurve) + { + GaussianCurveControlPointData1 = DefaultGaussianCurvePoint1; + GaussianCurveControlPointData2 = DefaultGaussianCurvePoint2; + GaussianCurveControlPointData3 = DefaultGaussianCurvePoint3; + GaussianCurveControlPointData4 = DefaultGaussianCurvePoint4; + propertyNames = new List() + { + nameof(GaussianCurveControlPointData1), nameof(GaussianCurveControlPointData2), + nameof(GaussianCurveControlPointData3), nameof(GaussianCurveControlPointData4) + }; + } + + foreach (var propertyName in propertyNames) + { + RaisePropertyChanged(propertyName); + } + + GenerateRenderValues(); + } + + /// + /// Updates Gaussian control points positions while maintaining relative spacing and canvas boundaries. + /// + public void UpdateGaussianCurveControlPoints(double deltaX, string tag) + { + switch (tag) + { + case "GaussianCurveControlPointData2": + GaussianCurveControlPointData3 = new ControlPointData( + GaussianCurveControlPointData3.X + deltaX, + GaussianCurveControlPointData3.Y, + "GaussianCurveControlPointData3"); + GaussianCurveControlPointData4 = new ControlPointData( + GaussianCurveControlPointData4.X + deltaX, + GaussianCurveControlPointData4.Y, + "GaussianCurveControlPointData4"); + break; + + case "GaussianCurveControlPointData3": + GaussianCurveControlPointData3 = new ControlPointData( + GaussianCurveControlPointData3.X + deltaX, + GaussianCurveControlPointData3.Y, + "GaussianCurveControlPointData3"); + GaussianCurveControlPointData4 = new ControlPointData( + GaussianCurveControlPointData4.X - deltaX, + GaussianCurveControlPointData4.Y, + "GaussianCurveControlPointData4"); + break; + + case "GaussianCurveControlPointData4": + GaussianCurveControlPointData4 = new ControlPointData( + GaussianCurveControlPointData4.X + deltaX, + GaussianCurveControlPointData4.Y, + "GaussianCurveControlPointData4"); + GaussianCurveControlPointData3 = new ControlPointData( + GaussianCurveControlPointData3.X - deltaX, + GaussianCurveControlPointData3.Y, + "GaussianCurveControlPointData3"); + + break; + } + + RaisePropertyChanged(nameof(GaussianCurveControlPointData3)); + RaisePropertyChanged(nameof(GaussianCurveControlPointData4)); + } + + #endregion + + #region Private Methods + + private bool IsValidInput() + { + return PointsCount >= 2 + && MinLimitX != MaxLimitX + && MinLimitY != MaxLimitY; + } + + private bool IsValidCurve() + { + // Dictionary mapping graph types to control point validation logic + var controlPointChecks = new Dictionary> + { + { GraphTypes.LinearCurve, () => LinearCurveControlPointData1.X != LinearCurveControlPointData2.X }, + { GraphTypes.SineWave, () => SineWaveControlPointData1.X != SineWaveControlPointData2.X }, + { GraphTypes.CosineWave, () => CosineWaveControlPointData1.X != CosineWaveControlPointData2.X }, + { GraphTypes.ParabolicCurve, () => ParabolicCurveControlPointData1.X != ParabolicCurveControlPointData2.X }, + { GraphTypes.PowerCurve, () => PowerCurveControlPointData1.X > 0 && + PowerCurveControlPointData1.Y > 0 && + PowerCurveControlPointData1.X < DynamicCanvasSize && + PowerCurveControlPointData1.Y < DynamicCanvasSize } + }; + + return controlPointChecks.TryGetValue(SelectedGraphType, out var validator) ? validator() : true; + } + + private void InitiateControlPointData() + { + // Linear Curve + LinearCurveControlPointData1 = DefaultLinearCurvePoint1; + LinearCurveControlPointData2 = DefaultLinearCurvePoint2; + // Bezier curve + BezierCurveControlPointData1 = DefaultBezierCurvePoint1; + BezierCurveControlPointData2 = DefaultBezierCurvePoint2; + BezierCurveControlPointData3 = DefaultBezierCurvePoint3; + BezierCurveControlPointData4 = DefaultBezierCurvePoint4; + // Sine wave + SineWaveControlPointData1 = DefaultSineWavePoint1; + SineWaveControlPointData2 = DefaultSineWavePoint2; + // Cosine wave + CosineWaveControlPointData1 = DefaultCosineWavePoint1; + CosineWaveControlPointData2 = DefaultCosineWavePoint2; + // Parabolic curve + ParabolicCurveControlPointData1 = DefaultParabolicCurvePoint1; + ParabolicCurveControlPointData2 = DefaultParabolicCurvePoint2; + // Perlin noise curve + PerlinNoiseControlPointData1 = DefaultPerlinNoiseCurvePoint1; + PerlinNoiseControlPointData2 = DefaultPerlinNoiseCurvePoint2; + PerlinNoiseControlPointData3 = DefaultPerlinNoiseCurvePoint3; + // Power curve + PowerCurveControlPointData1 = DefaultPowerCurvePoint1; + // Square root curve + SquareRootCurveControlPointData1 = DefaultSquareRootCurvePoint1; + SquareRootCurveControlPointData2 = DefaultSquareRootCurvePoint2; + // Gaussian curve + GaussianCurveControlPointData1 = DefaultGaussianCurvePoint1; + GaussianCurveControlPointData2 = DefaultGaussianCurvePoint2; + GaussianCurveControlPointData3 = DefaultGaussianCurvePoint3; + GaussianCurveControlPointData4 = DefaultGaussianCurvePoint4; + } + + private void ScaleAllControlPoints(double oldSize, double newSize) + { + var controlPoints = new List + { + LinearCurveControlPointData1, LinearCurveControlPointData2, + BezierCurveControlPointData1, BezierCurveControlPointData2, + BezierCurveControlPointData3, BezierCurveControlPointData4, + SineWaveControlPointData1, SineWaveControlPointData2, + CosineWaveControlPointData1, CosineWaveControlPointData2, + ParabolicCurveControlPointData1, ParabolicCurveControlPointData2, + PerlinNoiseControlPointData1, PerlinNoiseControlPointData2, PerlinNoiseControlPointData3, + PowerCurveControlPointData1, + SquareRootCurveControlPointData1, SquareRootCurveControlPointData2, + GaussianCurveControlPointData1, GaussianCurveControlPointData2, + GaussianCurveControlPointData3, GaussianCurveControlPointData4 + }.Where(p => p != null).ToList(); + + foreach (var point in controlPoints) + { + point?.ScaleToNewCanvasSize(oldSize, newSize); + } + } + + private string GetEnumDescription(Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)); + return attribute != null ? attribute.Description : value.ToString(); + } + + #endregion + + #region AST Methods + + protected override void OnBuilt() + { + base.OnBuilt(); + VMDataBridge.DataBridge.Instance.RegisterCallback(GUID.ToString(), DataBridgeCallback); + } + + private void DataBridgeCallback(object data) + { + ClearErrorsAndWarnings(); + + // Ignore invalid inputs & grab input data + if (!(data is ArrayList inputs) || inputs.Count < 5) return; + + var minValueX = double.TryParse(inputs[0]?.ToString(), out var minX) ? minX : MinLimitX; + var maxValueX = double.TryParse(inputs[1]?.ToString(), out var maxX) ? maxX : MaxLimitX; + var minValueY = double.TryParse(inputs[2]?.ToString(), out var minY) ? minY : MinLimitY; + var maxValueY = double.TryParse(inputs[3]?.ToString(), out var maxY) ? maxY : MaxLimitY; + var listValue = int.TryParse(inputs[4]?.ToString(), out var parsedCount) ? parsedCount : PointsCount; + + // Check port connectivity + if (InPorts[0].IsConnected) MinLimitX = minValueX; + if (InPorts[1].IsConnected) MaxLimitX = maxValueX; + if (InPorts[2].IsConnected) MinLimitY = minValueY; + if (InPorts[3].IsConnected) MaxLimitY = maxValueY; + if (InPorts[4].IsConnected) PointsCount = listValue; + + // Notify property changes to update UI + foreach (var propertyName in new[] { nameof(MinLimitX), nameof(MaxLimitX), nameof(MinLimitY), nameof(MaxLimitY), nameof(PointsCount) }) + { + RaisePropertyChanged(propertyName); + } + + if (!IsValidInput()) + { + Warning("The provided original values cannot be redistributed using the curve equation.", isPersistent: true); // TODO: add to resources + } + } + + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + // Return null outputs if GraphType is Empty + if (SelectedGraphType == GraphTypes.Empty || !IsValidCurve()) + { + return new[] + { + AstFactory.BuildAssignment( GetAstIdentifierForOutputIndex(0), AstFactory.BuildNullNode()), + AstFactory.BuildAssignment( GetAstIdentifierForOutputIndex(1), AstFactory.BuildNullNode()) + }; + } + + // Map GraphType to corresponding control points + var controlPointMap = new Dictionary>() + { + [GraphTypes.LinearCurve] = new() { + (LinearCurveControlPointData1.X, LinearCurveControlPointData1.Y), + (LinearCurveControlPointData2.X, LinearCurveControlPointData2.Y) + }, + [GraphTypes.BezierCurve] = new() { + (BezierCurveControlPointData1.X, BezierCurveControlPointData1.Y), + (BezierCurveControlPointData2.X, BezierCurveControlPointData2.Y), + (BezierCurveControlPointData3.X, BezierCurveControlPointData3.Y), + (BezierCurveControlPointData4.X, BezierCurveControlPointData4.Y) + }, + [GraphTypes.SineWave] = new() { + (SineWaveControlPointData1.X, SineWaveControlPointData1.Y), + (SineWaveControlPointData2.X, SineWaveControlPointData2.Y) + }, + [GraphTypes.CosineWave] = new() { + (CosineWaveControlPointData1.X, CosineWaveControlPointData1.Y), + (CosineWaveControlPointData2.X, CosineWaveControlPointData2.Y) + }, + [GraphTypes.ParabolicCurve] = new() { + (ParabolicCurveControlPointData1.X, ParabolicCurveControlPointData1.Y), + (ParabolicCurveControlPointData2.X, ParabolicCurveControlPointData2.Y) + }, + [GraphTypes.PerlinNoiseCurve] = new() { + (PerlinNoiseControlPointData1.X, PerlinNoiseControlPointData1.Y), + (PerlinNoiseControlPointData2.X, PerlinNoiseControlPointData2.Y), + (PerlinNoiseControlPointData3.X, PerlinNoiseControlPointData3.Y) + }, + [GraphTypes.PowerCurve] = new() { + (PowerCurveControlPointData1.X, + PowerCurveControlPointData1.Y) + }, + [GraphTypes.SquareRootCurve] = new() { + (SquareRootCurveControlPointData1.X, SquareRootCurveControlPointData1.Y), + (SquareRootCurveControlPointData2.X, SquareRootCurveControlPointData2.Y) + }, + [GraphTypes.GaussianCurve] = new() { + (GaussianCurveControlPointData1.X, GaussianCurveControlPointData1.Y), + (GaussianCurveControlPointData2.X, GaussianCurveControlPointData2.Y), + (GaussianCurveControlPointData3.X, GaussianCurveControlPointData3.Y), + (GaussianCurveControlPointData4.X, GaussianCurveControlPointData4.Y) + } + }; + + // Build controlPointsList dynamically + var controlPointsList = AstFactory.BuildExprList( + controlPointMap[SelectedGraphType] + .SelectMany(cp => new AssociativeNode[] + { + AstFactory.BuildDoubleNode(cp.X), + AstFactory.BuildDoubleNode(DynamicCanvasSize - cp.Y) + }) + .Cast() + .ToList() + ); + + // Handle input values with fall-back defaults + var inputValues = new List // TODO: Try to rationalize + { + InPorts[0].IsConnected ? inputAstNodes[0] : minLimitXDefaultValue, + InPorts[1].IsConnected ? inputAstNodes[1] : maxLimitXDefaultValue, + InPorts[2].IsConnected ? inputAstNodes[2] : minLimitYDefaultValue, + InPorts[3].IsConnected ? inputAstNodes[3] : maxLimitYDefaultValue, + InPorts[4].IsConnected ? inputAstNodes[4] : pointsCountDefaultValue + }; + + var curveInputs = new List {controlPointsList, AstFactory.BuildDoubleNode(DynamicCanvasSize)}; + curveInputs.AddRange(inputValues); + curveInputs.Add(AstFactory.BuildStringNode(SelectedGraphType.ToString())); + + AssociativeNode buildResultNode = + AstFactory.BuildFunctionCall( + new Func, double, double, double, double, double, int, string, List>>( + CurveMapperGenerator.CalculateValues), + curveInputs + ); + + // DataBridge call + var dataBridgeCall = AstFactory.BuildAssignment( + AstFactory.BuildIdentifier(AstIdentifierBase + "_dataBridge"), + VMDataBridge.DataBridge.GenerateBridgeDataAst(GUID.ToString(), AstFactory.BuildExprList(inputValues)) + ); + + // Assign outputs + var xValuesAssignment = AstFactory.BuildAssignment( + GetAstIdentifierForOutputIndex(0), + AstFactory.BuildIndexExpression(buildResultNode, AstFactory.BuildIntNode(0)) + ); + + var yValuesAssignment = AstFactory.BuildAssignment( + GetAstIdentifierForOutputIndex(1), + AstFactory.BuildIndexExpression(buildResultNode, AstFactory.BuildIntNode(1)) + ); + + return new[] { xValuesAssignment, yValuesAssignment, dataBridgeCall }; + } + + #endregion + } + + /// + /// Represents the different types of graph curves available in the Curve Mapper. + /// + //[TypeConverter(typeof(EnumDescriptionTypeConverter))] + public enum GraphTypes + { + [Description("Select type")] + Empty, + [Description("Linear Curve")] + LinearCurve, + [Description("Bezier Curve")] + BezierCurve, + [Description("Sine Wave")] + SineWave, + [Description("Cosine Wave")] + CosineWave, + [Description("Parabolic Curve")] + ParabolicCurve, + [Description("Perlin Noise")] + PerlinNoiseCurve, + [Description("Power Curve")] + PowerCurve, + [Description("Square Root Curve")] + SquareRootCurve, + [Description("Gaussian Curve")] + GaussianCurve + } + + /// + /// Represents a control point with X and Y coordinates, supporting scaling to a new canvas size. + /// + public class ControlPointData + { + public double X { get; set; } + public double Y { get; set; } + public string Tag { get; set; } + + public ControlPointData(double x, double y, string tag = "") + { + X = x; + Y = y; + Tag = tag; + } + + public void ScaleToNewCanvasSize(double oldCanvasSize, double newCanvasSize) + { + X = (X / oldCanvasSize) * newCanvasSize; + Y = newCanvasSize - ((oldCanvasSize - Y) / oldCanvasSize) * newCanvasSize; + } + } +} diff --git a/src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Large.png b/src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..002f5f84cbab25ce65a78d37c4e7b91c9fc352e7 GIT binary patch literal 1665 zcmZ{lS6mbJ7RUct5Cei52bYE&5D9_Mq6{?&pv165*=QivNWp4Dlx0fDz+Ns=Vni85 zK_wP3AXtr9qL84nia;nblxZ)^G7^m}fpBS`Zy(O@Jp9h*_wal8p3J}i%0`qi3IMQ? zdg#E>_09WhkUH!7f_S180G&hB10KOCH)e_sf7kE5wUK0VHqt8AJR!u$)=sR)%-y}? zipyXl(c?AxrH+#H|Ex)oSWZyj0 zZ$~^>GFqIcK0f>6Le=b{hb0-A{l;r%U+3!#qqx|JPDnhG4z@kQMmXsZ;A;15F-w$n z4BZHlMT~=H3Tqx`KtFu%?`c0V8WgJBZWuznIj5qpf#H5^coCyO@=QONfTkSn9@K=x z$fu8`9ZXMPTc-`*o$YilLciMy2ABkNb6U=}B4=UULSoU>9|O?iwXn3M`=1xIX1%pN z&z)jilJx16;Zz`6#RGREjB|^L5@B!Naw#m(_YIgH-2TB={Ulw26gCJ7n&t`>MBbyD zYivQMDmu6Af-&Wo!!j)ci<1?=Io6aojEVgRt_dJPCVheoM()i`E3y!i#Lwb~xafx= z2Hi;pBXeJ_VO`jVcPCD?pW5OGP1#C2sp(pf#r0C;<(AJeWu>_vzh|M}Mo#-0$8Bdi zDO^Hu>gQo{TnS}5mNZEUamlc+P)0#`j#vyrNC`O8Q`tOs!4e=Z8WNmGUA z9o9r2-Ibv`$@PhcH2=rwo=vabF0Vo^=%NQct)p8a9YFmA{0Lz^x_V!|zyl2x(Fys3 zOYh&j4G&y4JfZcKPU-Ezo%RHR%fHd%HPK(63VodeQePD;RjfLCsT-VP;1B4H6+Lui z@(h1o|HUc>U-m3GeZYLpIEXLxZ=y@^|Sc+X0Ga_&E^yRcW ztb}65LE)SK#ywS!N?xJJu8QffCiX|;>W~5c{HoYl=$%L_fCUhi&vcAXfenqEIq<-$ zTYB1F8PeYqJ-D55sZR<3IZ7#2v+k?v>XwrADBhg4t-DYcZdz8)LVe~57hqx5u8|2Z zxGQtsK|*%%!b1Iq7)1Fu>cGl9Ob9Qo@>%+yQxW+mer(yR^^;5d-*K*-v4R6X|IzkK zYdzAN;(y&c>)7Gr>rW2(D-lx6|3j!PFV73|x0QlNv>H-#w)G7Snbz!+t7>?U so*G)5cU?^kll^t{j|BhQaK5$%%CB}U;#K=c*Ix^u9t=28=}8y-4|qQJvH$=8 literal 0 HcmV?d00001 diff --git a/src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Small.png b/src/Libraries/CoreNodeModels/Resources/CoreNodeModels.CurveMapper.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f4ff114fe3679dbfc41037bd4477c6514d872c GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7e6l0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#N0|Vop0G|+7pn{t>Z|=GLr1(xOeONt(!L=-nzEo;)|2l?k_&~CjQ|6(xd;{PyeYo@vrFUf1oZP$~_EaK)7Yc z{#Bj$50`}qLdeaRp6|T$wC4Dqf+Hwe;A$S+x_0Hp?WPky3t@)CrGW0ab@N8Y$?upZ z-@bWc#+i?p;y}GXT?ei_!jyV?`wFrZ_itU#!Qmu`1AuYBppPUAC4`Y(IyUXN<%=fne zIqW5#zOL*qnS}Wal?afw|7d;XNt3{ zW8A@%rr;2+prN93h-bRk(SSo5*X$i-);Tx@c}c}Ko^gq^Ok1&MPt&4Fo48hSxrLgR z@~&X=)y++ljkT=}VP$J`J0@UzMs1nenKNn6#5CLtqM{;icNjJ2ZrFO6#p>d{gMy)r zlQ(y3M>Njf&3=M^-m+_2`&oI5rv@@IbL z<_Z+uU??u?KP!NV+v(Y*O+_KBJ!h;$r-oiV^Fd?fO4slMG2!-P`HFDj(jliH)Epd$~ zNl7e8wMs5Z1yT$~21cg321dGumLY~FRwfo!Mn>8O237_JPIZ6gplHa=PsvQH#I3NoH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs new file mode 100644 index 00000000000..edb68c9d89f --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs @@ -0,0 +1,465 @@ +using CoreNodeModels; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace Dynamo.Wpf.CurveMapper +{ + /// + /// Interaction logic for CurveMapperControl.xaml + /// + public partial class CurveMapperControl : UserControl, INotifyPropertyChanged + { + private readonly CurveMapperNodeModel curveMapperNodeModel; + private CurveMapperControlPoint linearCurveControlPoint1; + private CurveMapperControlPoint linearCurveControlPoint2; + private CurveMapperControlPoint bezierCurveControlPoint1; + private CurveMapperControlPoint bezierCurveControlPoint2; + private CurveMapperControlPoint bezierCurveControlPoint3; + private CurveMapperControlPoint bezierCurveControlPoint4; + private CurveMapperControlPoint sineWaveControlPoint1; + private CurveMapperControlPoint sineWaveControlPoint2; + private CurveMapperControlPoint cosineWaveControlPoint1; + private CurveMapperControlPoint cosineWaveControlPoint2; + private CurveMapperControlPoint parabolicCurveControlPoint1; + private CurveMapperControlPoint parabolicCurveControlPoint2; + private CurveMapperControlPoint perlinNoiseCurveControlPoint1; + private CurveMapperControlPoint perlinNoiseCurveControlPoint2; + private CurveMapperControlPoint perlinNoiseCurveControlPoint3; + private CurveMapperControlPoint powerCurveControlPoint1; + private CurveMapperControlPoint squareRootCurveControlPoint1; + private CurveMapperControlPoint squareRootCurveControlPoint2; + private CurveMapperControlPoint gaussianCurveControlPoint1; + private CurveMapperControlPoint gaussianCurveControlPoint2; + private CurveMapperControlPoint gaussianCurveControlPoint3; + private CurveMapperControlPoint gaussianCurveControlPoint4; + + private readonly Dictionary controlPointProperties = + new Dictionary + { + { nameof(bezierCurveControlPoint1), (true, true) }, + { nameof(bezierCurveControlPoint2), (true, true) }, + { nameof(perlinNoiseCurveControlPoint1), (true, false) }, + { nameof(perlinNoiseCurveControlPoint2), (true, true) }, + { nameof(gaussianCurveControlPoint1), (true, true) }, + { nameof(gaussianCurveControlPoint2), (true, false) }, + { nameof(gaussianCurveControlPoint3), (true, false) }, + { nameof(gaussianCurveControlPoint4), (true, false) } + }; + + private const double offsetValue = 6; + private const int gridSize = 10; + private const int minCanvasSize = 240; + private const int controlLabelsWidth = 70; + private const int controlLabelsHeight = 100; + + /// + /// Occurs when a property value changes, notifying bound UI elements of updates. + /// + public event PropertyChangedEventHandler PropertyChanged; + + public CurveMapperControl(CurveMapperNodeModel model, double canvasSize) + { + InitializeComponent(); + this.curveMapperNodeModel = model; + DataContext = model; + + Width = canvasSize + controlLabelsWidth; + Height = canvasSize + controlLabelsHeight; + + + model.PropertyChanged += NodeModel_PropertyChanged; + this.Unloaded += Unload; + + DrawGrid(); + + // Dictionary to map UI control points to their corresponding data + var controlPointsMap = BuildControlPointsDictionary(); + RecreateControlPoints(controlPointsMap); + + RenderCurve(); + + ToggleControlPointsLock(); + UpdateLockButton(); + } + + private void RenderCurve() + { + // Remove existing curves (without affecting control points) + Dispatcher.Invoke(() => + { + for (int i = GraphCanvas.Children.Count - 1; i >= 0; i--) + { + if (GraphCanvas.Children[i] is Path) + { + GraphCanvas.Children.RemoveAt(i); + } + } + + // Determine rendering behavior based on graph type + bool isGaussian = curveMapperNodeModel.SelectedGraphType == GraphTypes.GaussianCurve; + bool isPerlin = curveMapperNodeModel.SelectedGraphType == GraphTypes.PerlinNoiseCurve; + + // Only render the curve on valid selection + if (curveMapperNodeModel.SelectedGraphType != GraphTypes.Empty) + { + var paths = CurveRenderer.RenderCurve( + curveMapperNodeModel.RenderValuesX, + curveMapperNodeModel.RenderValuesY, + curveMapperNodeModel.DynamicCanvasSize, + false, isGaussian, isPerlin + ); + + if (paths != null) + { + paths.ForEach(path => GraphCanvas.Children.Add(path)); + } + } + + // Render control lines for Bezier curve + if (curveMapperNodeModel.SelectedGraphType == GraphTypes.BezierCurve) + { + var controlLine1 = CurveRenderer.RenderCurve( + new List { + curveMapperNodeModel.BezierCurveControlPointData1.X, + curveMapperNodeModel.BezierCurveControlPointData3.X + }, + new List { + curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData1.Y, + curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData3.Y + }, + curveMapperNodeModel.DynamicCanvasSize, + true + ); + var controlLine2 = CurveRenderer.RenderCurve( + new List { + curveMapperNodeModel.BezierCurveControlPointData2.X, + curveMapperNodeModel.BezierCurveControlPointData4.X + }, + new List { + curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData2.Y, + curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData4.Y + }, + curveMapperNodeModel.DynamicCanvasSize, + true + ); + + GraphCanvas.Children.Add(controlLine1.FirstOrDefault()); + GraphCanvas.Children.Add(controlLine2.FirstOrDefault()); + } + }); + } + + private void ResetButton_Click(object sender, RoutedEventArgs e) + { + if (curveMapperNodeModel.IsLocked) return; + + curveMapperNodeModel.ResetControlPointData(); + + // Dictionary to map UI control points to their corresponding data + var controlPointsResetMap = BuildControlPointsDictionary(); + RecreateControlPoints(controlPointsResetMap); + + curveMapperNodeModel.GenerateRenderValues(); + RenderCurve(); + } + + private void LockButton_Click(object sender, RoutedEventArgs e) + { + var button = sender as Button; + if (button != null) + { + curveMapperNodeModel.IsLocked = !curveMapperNodeModel.IsLocked; + UpdateLockButton(); + ToggleControlPointsLock(); + } + } + + private void NodeModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(curveMapperNodeModel.DynamicCanvasSize)) + { + double newSize = curveMapperNodeModel.DynamicCanvasSize; + + // Dictionary to map UI control points to their corresponding data + var controlPointsMap = BuildControlPointsDictionary(); + + // Dynamically retrieve control points from controlPointsMap + foreach (var (pointNames, dataPoints) in controlPointsMap.Values) + { + for (int i = 0; i < pointNames.Count; i++) + { + var pointField = GetType().GetField(pointNames[i], System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var controlPoint = pointField?.GetValue(this) as UIElement; + + if (controlPoint != null && dataPoints[i] != null) + { + UpdateControlPointPosition(controlPoint, dataPoints[i], newSize); + } + } + } + + RenderCurve(); + } + + if (e.PropertyName == nameof(curveMapperNodeModel.SelectedGraphType)) + { + var controlPointsMap = BuildControlPointsDictionary(); + RecreateControlPoints(controlPointsMap); + + curveMapperNodeModel.GenerateRenderValues(); + RenderCurve(); + + ToggleControlPointsLock(); + } + + if (e.PropertyName == nameof(curveMapperNodeModel.RenderValuesX) || + e.PropertyName == nameof(curveMapperNodeModel.RenderValuesY)) + { + RenderCurve(); + } + + if (e.PropertyName == nameof(curveMapperNodeModel.GaussianCurveControlPointData3)) + { + UpdateGaussianControlPoint(gaussianCurveControlPoint3, curveMapperNodeModel.GaussianCurveControlPointData3); + } + if (e.PropertyName == nameof(curveMapperNodeModel.GaussianCurveControlPointData4)) + { + UpdateGaussianControlPoint(gaussianCurveControlPoint4, curveMapperNodeModel.GaussianCurveControlPointData4); + } + } + + private void Unload(object sender, RoutedEventArgs e) + { + this.curveMapperNodeModel.PropertyChanged -= NodeModel_PropertyChanged; + Unloaded -= Unload; + } + + private void RecreateControlPoints(Dictionary pointNames, List dataPoints)> controlPointsMap) + { + // Remove existing control points + var existingControlPoints = GraphCanvas.Children.OfType().ToList(); + foreach (var cp in existingControlPoints) + { + GraphCanvas.Children.Remove(cp); + } + + // Recreate control points for the selected graph + var selectedType = curveMapperNodeModel.SelectedGraphType; + if (controlPointsMap.ContainsKey(selectedType)) + { + var (pointNames, dataPoints) = controlPointsMap[selectedType]; + Type controlType = this.GetType(); + + for (int i = 0; i < pointNames.Count; i++) + { + // Get the field dynamically & remove the old control point + var pointField = controlType.GetField(pointNames[i], System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var oldPoint = pointField?.GetValue(this) as CurveMapperControlPoint; + if (oldPoint != null) GraphCanvas.Children.Remove(oldPoint); + + // Determine if this control point should be orthogonal or vertical + (bool isOrthogonal, bool isVertical) = controlPointProperties.TryGetValue(pointNames[i], out var props) + ? props + : (false, false); + + var newPoint = new CurveMapperControlPoint(dataPoints[i], curveMapperNodeModel.DynamicCanvasSize, curveMapperNodeModel, RenderCurve, isOrthogonal, isVertical); + pointField?.SetValue(this, newPoint); + GraphCanvas.Children.Add(newPoint); + + // Ensure correct visibility on graph load + double newX = dataPoints[i].X; + double newY = dataPoints[i].Y; + double canvasSize = curveMapperNodeModel.DynamicCanvasSize; + newPoint.Visibility = (newX < 0 || newX > canvasSize || newY < 0 || newY > canvasSize) + ? Visibility.Hidden + : Visibility.Visible; + + Canvas.SetLeft(newPoint, dataPoints[i].X - offsetValue); + Canvas.SetTop(newPoint, dataPoints[i].Y - offsetValue); + } + } + } + + private void UpdateControlPointPosition(UIElement controlPoint, ControlPointData dataPoint, double newSize) + { + if (controlPoint != null && dataPoint != null) + { + double newX = (dataPoint.X / newSize) * newSize; + double newY = ((curveMapperNodeModel.DynamicCanvasSize - dataPoint.Y) / newSize) * newSize; + + Canvas.SetLeft(controlPoint, newX - offsetValue); + Canvas.SetTop(controlPoint, newSize - newY - offsetValue); + } + } + + private void UpdateGaussianControlPoint(UIElement controlPoint, ControlPointData dataPoint) + { + if (controlPoint != null && dataPoint != null) + { + double newX = dataPoint.X; + double newY = dataPoint.Y; + double canvasSize = curveMapperNodeModel.DynamicCanvasSize; + + // Update position + Canvas.SetLeft(controlPoint, newX - offsetValue); + Canvas.SetTop(controlPoint, newY - offsetValue); + + // Hide if out of bounds + controlPoint.Visibility = (newX < 0 || newX > canvasSize || newY < 0 || newY > canvasSize) + ? Visibility.Hidden + : Visibility.Visible; + } + } + + + private void DrawGrid() + { + // Remove current grid lines + var gridLines = GraphCanvas.Children + .OfType() + .Where(child => (child as FrameworkElement)?.Tag?.ToString() == "GridLine") + .ToList(); + + foreach (var line in gridLines) + { + GraphCanvas.Children.Remove(line); + } + + // Draw grid lines + double xPixelsPerStep = curveMapperNodeModel.DynamicCanvasSize / gridSize; + double yPixelsPerStep = curveMapperNodeModel.DynamicCanvasSize / gridSize; + + for (int i = 0; i <= gridSize; i++) + { + double xPos = i * xPixelsPerStep; + DrawLine(xPos, 0, xPos, curveMapperNodeModel.DynamicCanvasSize); + } + + for (int i = 0; i <= gridSize; i++) + { + double yPos = i * yPixelsPerStep; + DrawLine(0, yPos, curveMapperNodeModel.DynamicCanvasSize, yPos); + } + } + + private void DrawLine(double x1, double y1, double x2, double y2) + { + var line = new System.Windows.Shapes.Line + { + X1 = x1, + Y1 = y1, + X2 = x2, + Y2 = y2, + Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5e5e5e")), + StrokeThickness = 0.6, + Tag = "GridLine" + }; + Canvas.SetZIndex(line, 0); + GraphCanvas.Children.Add(line); + } + + /// + /// Dictionary mapping UI control points to their corresponding data. + /// Although it seems redundant to recreate this dictionary, reusing a property does not work + /// because control point data references are updated dynamically. + /// + private Dictionary pointNames, List dataPoints)> BuildControlPointsDictionary() + { + var controlPointsResetMap = new Dictionary pointNames, List dataPoints)> + { + { GraphTypes.LinearCurve, (new List { nameof(linearCurveControlPoint1), nameof(linearCurveControlPoint2) }, + new List { curveMapperNodeModel.LinearCurveControlPointData1, curveMapperNodeModel.LinearCurveControlPointData2 }) }, + + { GraphTypes.BezierCurve, (new List { nameof(bezierCurveControlPoint1), nameof(bezierCurveControlPoint2), nameof(bezierCurveControlPoint3), nameof(bezierCurveControlPoint4) }, + new List { curveMapperNodeModel.BezierCurveControlPointData1, curveMapperNodeModel.BezierCurveControlPointData2, + curveMapperNodeModel.BezierCurveControlPointData3, curveMapperNodeModel.BezierCurveControlPointData4 }) }, + + { GraphTypes.SineWave, (new List { nameof(sineWaveControlPoint1), nameof(sineWaveControlPoint2) }, + new List { curveMapperNodeModel.SineWaveControlPointData1, curveMapperNodeModel.SineWaveControlPointData2 }) }, + + { GraphTypes.CosineWave, (new List { nameof(cosineWaveControlPoint1), nameof(cosineWaveControlPoint2) }, + new List { curveMapperNodeModel.CosineWaveControlPointData1, curveMapperNodeModel.CosineWaveControlPointData2 }) }, + + { GraphTypes.ParabolicCurve, (new List { nameof(parabolicCurveControlPoint1), nameof(parabolicCurveControlPoint2) }, + new List { curveMapperNodeModel.ParabolicCurveControlPointData1, curveMapperNodeModel.ParabolicCurveControlPointData2 }) }, + + { GraphTypes.PerlinNoiseCurve, (new List { nameof(perlinNoiseCurveControlPoint1), nameof(perlinNoiseCurveControlPoint2), nameof(perlinNoiseCurveControlPoint3) }, + new List { curveMapperNodeModel.PerlinNoiseControlPointData1, curveMapperNodeModel.PerlinNoiseControlPointData2, + curveMapperNodeModel.PerlinNoiseControlPointData3 }) }, + + { GraphTypes.PowerCurve, (new List { nameof(powerCurveControlPoint1) }, + new List { curveMapperNodeModel.PowerCurveControlPointData1 }) }, + + { GraphTypes.SquareRootCurve, (new List { nameof(squareRootCurveControlPoint1), nameof(squareRootCurveControlPoint2) }, + new List { curveMapperNodeModel.SquareRootCurveControlPointData1, curveMapperNodeModel.SquareRootCurveControlPointData2 }) }, + + { GraphTypes.GaussianCurve, (new List { nameof(gaussianCurveControlPoint1), nameof(gaussianCurveControlPoint2), nameof(gaussianCurveControlPoint3), nameof(gaussianCurveControlPoint4) }, + new List { curveMapperNodeModel.GaussianCurveControlPointData1, curveMapperNodeModel.GaussianCurveControlPointData2, + curveMapperNodeModel.GaussianCurveControlPointData3, curveMapperNodeModel.GaussianCurveControlPointData4}) } + }; + + return controlPointsResetMap; + } + + private void ToggleControlPointsLock() + { + foreach (var child in GraphCanvas.Children) + { + if (child is CurveMapperControlPoint controlPoint) + { + controlPoint.IsMoveEnabled = !curveMapperNodeModel.IsLocked; + } + } + } + + private void UpdateLockButton() + { + if (LockButton != null) + { + LockButton.Tag = curveMapperNodeModel.IsLocked ? "Locked" : "Unlocked"; + if (LockButton.ToolTip is ToolTip lockTooltip) + { + lockTooltip.Content = curveMapperNodeModel.IsLocked + ? "Curve cannot be modified. Click to unlock." + : "Click to lock the curve."; + } + ResetButton.Tag = curveMapperNodeModel.IsLocked ? "Locked" : "Unlocked"; + if (ResetButton.ToolTip is ToolTip resetTooltip) + { + resetTooltip.Content = curveMapperNodeModel.IsLocked + ? "The curve has been locked and cannot be reset. Please unlock the curve first." + : "Reset the curve."; + } + } + } + + private void ThumbResizeThumbOnDragDeltaHandler(object sender, DragDeltaEventArgs e) + { + var sizeChange = Math.Min(e.VerticalChange, e.HorizontalChange); + var yAdjust = ActualHeight + sizeChange; + var xAdjust = ActualWidth + sizeChange; + + // Ensure the mainGrid doesn't resize below its minimum size + yAdjust = Math.Max(yAdjust, minCanvasSize + controlLabelsHeight); + xAdjust = Math.Max(xAdjust, minCanvasSize + controlLabelsWidth); + + Width = xAdjust; + Height = yAdjust; + + // Adjust the size of the GraphCanvas dynamically + curveMapperNodeModel.DynamicCanvasSize = Math.Max(xAdjust - controlLabelsWidth, minCanvasSize); + DrawGrid(); + + // Reposition control points based on the new size + NodeModel_PropertyChanged(this, new PropertyChangedEventArgs(nameof(curveMapperNodeModel.DynamicCanvasSize))); + curveMapperNodeModel.GenerateRenderValues(); + } + } +} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml new file mode 100644 index 00000000000..ad93c10608b --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + Drag to modify the curve. + + + + + + + diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs new file mode 100644 index 00000000000..def8d9a6956 --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs @@ -0,0 +1,192 @@ +using CoreNodeModels; +using Newtonsoft.Json; +using System; +using System.ComponentModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace Dynamo.Wpf.CurveMapper +{ + /// + /// Interaction logic for CurveMapperControlPoint.xaml + /// + public partial class CurveMapperControlPoint : Thumb, INotifyPropertyChanged + { + private CurveMapperNodeModel curveMapperNodeModel; + private const double offsetValue = 6; + private bool isMoveEnabled = true; + private bool isOrthogonal; + private bool isVertical; + private ControlPointData controlPointData; + + /// + /// Represents the dynamic canvas size for the control point. + /// + public double CanvasSize + { + get => (double)GetValue(DynamicCanvasSizeProperty); + set => SetValue(DynamicCanvasSizeProperty, value); + } + + /// + /// Gets the scaled coordinates of the control point, normalized based on Min/Max limits. + /// + [JsonIgnore] + public string ScaledCoordinates + { + get + { + double scaledX = curveMapperNodeModel.MinLimitX + + (controlPointData.X / CanvasSize) * + (curveMapperNodeModel.MaxLimitX - curveMapperNodeModel.MinLimitX); + + double scaledY = curveMapperNodeModel.MinLimitY + + (1 - (controlPointData.Y / CanvasSize)) * + (curveMapperNodeModel.MaxLimitY - curveMapperNodeModel.MinLimitY); + + return $"Coordinates: ({scaledX:F2}, {scaledY:F2})"; + } + } + + /// + /// Gets or sets a value indicating whether the control point can be moved by the user. + /// + [JsonIgnore] + public bool IsMoveEnabled + { + get => isMoveEnabled; + set + { + if (isMoveEnabled != value) + { + isMoveEnabled = value; + UpdateCursor(); + } + } + } + + /// + /// Identifies the DynamicCanvasSize dependency property, which represents the dynamic canvas size + /// used for scaling and rendering the control point. + /// + public static readonly DependencyProperty DynamicCanvasSizeProperty = RegisterProperty(nameof(DynamicCanvasSizeProperty), 240.0); + + /// + /// Gets or sets the action invoked when the control point is moved. + /// + public Action OnControlPointMoved { get; set; } + + public event PropertyChangedEventHandler PropertyChanged; + + public CurveMapperControlPoint( + ControlPointData controlPoint, + double canvasSize, + CurveMapperNodeModel model, + Action updateCurve, + bool isOrthogonal = false, bool isVertical = false + ) + { + InitializeComponent(); + DataContext = this; + + controlPointData = controlPoint; + CanvasSize = canvasSize; + curveMapperNodeModel = model; + OnControlPointMoved = updateCurve; + this.isOrthogonal = isOrthogonal; + this.isVertical = isVertical; + + Canvas.SetLeft(this, controlPoint.X - offsetValue); + Canvas.SetTop(this, controlPoint.Y - offsetValue); + Canvas.SetZIndex(this, 25); + + UpdateCursor(); + + curveMapperNodeModel.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(curveMapperNodeModel.MinLimitX) || + e.PropertyName == nameof(curveMapperNodeModel.MaxLimitX) || + e.PropertyName == nameof(curveMapperNodeModel.MinLimitY) || + e.PropertyName == nameof(curveMapperNodeModel.MaxLimitY)) + { + RaisePropertyChanged(nameof(ScaledCoordinates)); + } + }; + } + + private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) + { + if (!IsMoveEnabled) return; + + double newCanvasSize = curveMapperNodeModel.DynamicCanvasSize; + + // Calculate horizontal and vertical deltas within canvas boundaries + // Requires for Gaussian control points. + var deltaL = Canvas.GetLeft(this) + offsetValue; + var deltaR = newCanvasSize - Canvas.GetLeft(this) - offsetValue; + var deltaX = e.HorizontalChange > 0 ? + Math.Min(e.HorizontalChange, deltaR) : + Math.Max(e.HorizontalChange, -deltaL); + + var deltaT = Canvas.GetTop(this) + offsetValue; + var deltaB = newCanvasSize - Canvas.GetTop(this) - offsetValue; + var deltaY = e.VerticalChange > 0 ? + Math.Min(e.VerticalChange, deltaB) : + Math.Max(e.VerticalChange, -deltaT); + + // Calculate new positions for X and Y based on drag changes + double newX = Canvas.GetLeft(this) + (isOrthogonal && isVertical ? 0.0 : deltaX) + offsetValue; + double newY = Canvas.GetTop(this) + (isOrthogonal && !isVertical ? 0.0 : deltaY) + offsetValue; + + controlPointData.X = newX; + controlPointData.Y = newY; + Canvas.SetLeft(this, newX - offsetValue); + Canvas.SetTop(this, newY - offsetValue); + + // Handle Gaussian curve control points + string tag = controlPointData.Tag; + + if (tag.Contains("GaussianCurveControlPointData")) + { + curveMapperNodeModel.UpdateGaussianCurveControlPoints(deltaX, tag); + } + + // Refresh scaled coordinates in tooltip + RaisePropertyChanged(nameof(ScaledCoordinates)); + + // Notify the mode and UI to update the curve + curveMapperNodeModel.GenerateRenderValues(); + OnControlPointMoved?.Invoke(); + } + + private void UpdateCursor() + { + this.Cursor = IsMoveEnabled ? Cursors.Hand : Cursors.Arrow; + } + + public void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + + /// + /// Helper method to register dependency properties with a common property changed callback. + /// + /// + private static DependencyProperty RegisterProperty(string name, double defaultValue) + { + return DependencyProperty.Register(name, typeof(double), typeof(CurveMapperControlPoint), + new PropertyMetadata(defaultValue, OnPropertyUpdated)); + } + + /// + /// Common property changed handler to raise updates for ScaledCoordinates. + /// + private static void OnPropertyUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + } + } +} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs new file mode 100644 index 00000000000..d92f7bce274 --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs @@ -0,0 +1,25 @@ +using CoreNodeModels; +using Dynamo.Controls; + +namespace Dynamo.Wpf.CurveMapper +{ + public class CurveMapperNodeView : INodeViewCustomization + { + private CurveMapperNodeModel curveMapperNodeModel; + private CurveMapperControl curveMapperControl; + + public void CustomizeView(CurveMapperNodeModel model, NodeView nodeView) + { + curveMapperNodeModel = model; + curveMapperControl = new CurveMapperControl(model, model.DynamicCanvasSize); + curveMapperControl.DataContext = model; + curveMapperNodeModel = model; + + nodeView.inputGrid.Children.Add(curveMapperControl); + } + + public void Dispose() + { + } + } +} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs new file mode 100644 index 00000000000..f5b48082b1a --- /dev/null +++ b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Media; +using System.Windows.Shapes; + +namespace Dynamo.Wpf.CurveMapper +{ + public class CurveRenderer + { + /// + /// Renders a curve as a Path object based on given X and Y values, adjusting for the inverted Y-axis in WPF. + /// + public static List RenderCurve( + List xValues, List yValues, double canvasSize, + bool isControlLine = false, bool isGaussian = false, bool isPerlin = false) + { + if (xValues == null || yValues == null || xValues.Count != yValues.Count || xValues.Count < 2) + return null; + + List paths = new List(); + PathGeometry currentGeometry = new PathGeometry(); + PathFigure currentFigure = null; + int hitCount = (isPerlin && (yValues[0] == 0 || yValues[0] == canvasSize)) ? 1 : 0; + + for (int i = 0; i < xValues.Count; i++) + { + Point currentPoint = new Point(xValues[i], canvasSize - yValues[i]); + bool isBoundaryGaussian = isGaussian && yValues[i] == canvasSize; + bool isBoundaryPerlin = isPerlin && (yValues[i] == 0 || yValues[i] == canvasSize); + + + if (isBoundaryGaussian || isBoundaryPerlin) // Odd occurrences: Close the path and reset + { + hitCount++; + if (hitCount % 2 == 1) + { + if (currentFigure != null) + { + currentFigure.Segments.Add(new LineSegment(currentPoint, true)); + currentGeometry.Figures.Add(currentFigure); + paths.Add(CreatePathFromGeometry(currentGeometry, isControlLine)); + } + currentGeometry = new PathGeometry(); + currentFigure = null; + } + else + { + currentFigure = new PathFigure { StartPoint = currentPoint }; + currentGeometry.Figures.Add(currentFigure); + } + } + else // Regular points: Add to the current path normally + { + if (currentFigure == null) + { + currentFigure = new PathFigure { StartPoint = currentPoint }; + currentGeometry.Figures.Add(currentFigure); + } + else + { + currentFigure.Segments.Add(new LineSegment(currentPoint, true)); + } + } + } + + if (currentFigure != null) + { + paths.Add(CreatePathFromGeometry(currentGeometry, isControlLine)); + } + + return paths; + } + + /// + /// Creates a styled Path object from a given PathGeometry. + /// + private static Path CreatePathFromGeometry(PathGeometry geometry, bool isControlLine) + { + return new Path + { + Data = geometry, + Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B385F2")), + StrokeThickness = isControlLine ? 1 : 3, + StrokeDashArray = isControlLine ? new DoubleCollection { 4, 4 } : null + }; + } + } + +} diff --git a/src/Libraries/CoreNodes/Color.cs b/src/Libraries/CoreNodes/Color.cs index a42d43d7e9c..70c46fd418b 100644 --- a/src/Libraries/CoreNodes/Color.cs +++ b/src/Libraries/CoreNodes/Color.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -584,4 +584,4 @@ public Color GetColorAtParameter(UV parameter) return color; } } -} \ No newline at end of file +} diff --git a/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs b/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs new file mode 100644 index 00000000000..67a46e1b399 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a Bezier curve in the CurveMapper. + /// A Bezier curve is defined by four control points and provides smooth interpolation. + /// + public class BezierCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + private double ControlPoint3X; + private double ControlPoint3Y; + private double ControlPoint4X; + private double ControlPoint4Y; + + private Dictionary xToYMap = new Dictionary(); + private double tFactor; + + public BezierCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, + double cp3X, double cp3Y, double cp4X, double cp4Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + ControlPoint3X = cp3X; + ControlPoint3Y = cp3Y; + ControlPoint4X = cp4X; + ControlPoint4Y = cp4Y; + + tFactor = 1.0 / canvasSize; + } + + private void GetValueAtT(double t, out double x, out double y) + { + x = Math.Pow(1 - t, 3) * ControlPoint1X + + 3 * Math.Pow(1 - t, 2) * t * ControlPoint3X + + 3 * (1 - t) * t * t * ControlPoint4X + + t * t * t * ControlPoint2X; + + y = Math.Pow(1 - t, 3) * ControlPoint1Y + + 3 * Math.Pow(1 - t, 2) * t * ControlPoint3Y + + 3 * (1 - t) * t * t * ControlPoint4Y + + t * t * t * ControlPoint2Y; + } + + /// + /// Gets interpolated Y values based on the assigned parameters and limits. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + { + var renderValuesX = new List(); + var renderValuesY = new List(); + var valuesX = new List(); + var valuesY = new List(); + + // Generate fine-grained samples to ensure better interpolation + int fineSteps = (int)(pointsCount * CanvasSize); + + for (int i = 0; i <= fineSteps; i++) + { + double t = i / (double)fineSteps; + + GetValueAtT(t, out double x, out double y); + + renderValuesX.Add(x); + renderValuesY.Add(y); + } + + if (isRender) + { + return (renderValuesX, renderValuesY); + } + + // Collect output values + for (int i = 0; i < pointsCount; i++) + { + double targetX = (i / (double)(pointsCount - 1) * CanvasSize); + int closestIndex = renderValuesX.IndexOf(renderValuesX.OrderBy(x => Math.Abs(x - targetX)).First()); + double y = renderValuesY[closestIndex]; + + valuesX.Add(targetX); + valuesY.Add(y); + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs b/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs new file mode 100644 index 00000000000..cb60497892d --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a control line in the CurveMapper. + /// This is used for auxiliary control of other curves, particularly Bezier curves. + /// + public class ControlLine : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + + public ControlLine(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + } + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + { + return (new List { ControlPoint1X, ControlPoint2X }, new List { ControlPoint1Y, ControlPoint2Y }); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs new file mode 100644 index 00000000000..a3ce5c18d52 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a base class for all curve types in the CurveMapper. + /// Provides common functionality for generating and retrieving curve values. + /// + public abstract class CurveBase + { + protected double CanvasSize; + protected const double renderIncrementX = 1.0; + + protected CurveBase(double canvasSize) + { + CanvasSize = canvasSize; + } + + /// + /// Abstract method to be implemented by derived classes for generating curve values. + /// + protected abstract (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender); + + /// + /// Common method for retrieving X values. + /// + public virtual List GetCurveXValues(int pointsCount, bool isRender = false) + { + return GenerateCurve(pointsCount, isRender).XValues; + } + + /// + /// Common method for retrieving Y values. + /// + public virtual List GetCurveYValues(int pointsCount, bool isRender = false) + { + return GenerateCurve(pointsCount, isRender).YValues; + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs new file mode 100644 index 00000000000..aad65891ff9 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + public class CurveMapperGenerator + { + private static int rounding = 10; + + public static List> CalculateValues( + List controlPoints, double canvasSize, + double minX, double maxX, double minY, double maxY, + int pointsCount, string graphType + ) + { + var xValues = new List() { double.NaN }; + var yValues = new List() { double.NaN }; + + if (minX != maxX && minY != maxY && pointsCount >= 2) + { + // Unpack the control points + double cp1x = GetCP(controlPoints, 0), cp1y = GetCP(controlPoints, 1); + double cp2x = GetCP(controlPoints, 2), cp2y = GetCP(controlPoints, 3); + double cp3x = GetCP(controlPoints, 4), cp3y = GetCP(controlPoints, 5); + double cp4x = GetCP(controlPoints, 6), cp4y = GetCP(controlPoints, 7); + + object curve = null; + + switch (graphType) + { + case "LinearCurve": + curve = new LinearCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); + break; + case "BezierCurve": + curve = new BezierCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, cp4x, cp4y, canvasSize); + break; + case "SineWave": + curve = new SineWave(cp1x, cp1y, cp2x, cp2y, canvasSize); + break; + case "CosineWave": + curve = new SineWave(cp1x, cp1y, cp2x, cp2y, canvasSize); + break; + case "ParabolicCurve": + curve = new ParabolicCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); + break; + case "PerlinNoiseCurve": + curve = new PerlinNoiseCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, canvasSize); + break; + case "PowerCurve": + curve = new PowerCurve(cp1x, cp1y, canvasSize); + break; + case "SquareRootCurve": + curve = new SquareRootCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); + break; + case "GaussianCurve": + curve = new GaussianCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, cp4x, cp4y, canvasSize); + break; + } + + if (curve != null) + { + dynamic dynamicCurve = curve; + + xValues = MapValues(dynamicCurve.GetCurveXValues(pointsCount), minX, maxX, canvasSize); + yValues = MapValues(dynamicCurve.GetCurveYValues(pointsCount), minY, maxY, canvasSize); + } + } + + return new List> { yValues, xValues }; + } + + private static double GetCP(List controlPoints, int index) + { + return index < controlPoints.Count ? controlPoints[index] : 0; + } + + private static List MapValues(List rawValues, double minLimit, double maxLimit, double canvasSize) + { + var mappedValues = new List(); + + foreach (var value in rawValues) + { + mappedValues.Add(Math.Round(minLimit + value / canvasSize * (maxLimit - minLimit), rounding)); + } + return mappedValues; + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs new file mode 100644 index 00000000000..ead49914ae9 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a Gaussian curve in the CurveMapper. + /// The Gaussian curve follows a bell-shaped distribution defined by four control points. + /// + public class GaussianCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + private double ControlPoint3X; + private double ControlPoint3Y; + private double ControlPoint4X; + private double ControlPoint4Y; + + private double lastControlPoint2X; + private double previousHorizontalOffset; + + /// + /// Indicates whether the node is currently being resized, preventing unintended control point updates. + /// + public GaussianCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double cp3X, double cp3Y, double cp4X, double cp4Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + ControlPoint3X = cp3X; + ControlPoint3Y = cp3Y; + ControlPoint4X = cp4X; + ControlPoint4Y = cp4Y; + } + + private double ComputeGaussianY(double x, double A, double mu, double sigma) + { + double exponent = -Math.Pow(x - mu, 2) / (2 * Math.Pow(sigma, 2)); + double normalizedY = A * Math.Exp(exponent); + + return CanvasSize - normalizedY; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + { + var valuesX = new List(); + var valuesY = new List(); + + // Compute Gaussian parameters + double A = ControlPoint1Y * 4; + double mu = ControlPoint2X; + double sigma = Math.Abs(ControlPoint4X - ControlPoint3X) / 4; + if (sigma < 1) sigma = 1; + + if (isRender) + { + for (double x = 0; x <= ControlPoint2X; x += renderIncrementX) + { + double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); + + if (y <= CanvasSize) + { + valuesX.Add(x); + valuesY.Add(y); + } + else + { + // Find the x value for which y == CanvasSize + double xAtCanvasSize = mu - Math.Sqrt(-2 * Math.Pow(sigma, 2) * Math.Log(CanvasSize / A)); + xAtCanvasSize = Math.Max(xAtCanvasSize, 0); + + if (!valuesX.Contains(xAtCanvasSize)) + { + valuesX.Add(xAtCanvasSize); + valuesY.Add(CanvasSize); + } + break; + } + } + for (double x = ControlPoint2X; x <= CanvasSize; x += renderIncrementX) + { + double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); + + if (y <= CanvasSize) + { + valuesX.Add(x); + valuesY.Add(y); + } + else + { + // Find the x value for which y == CanvasSize + double xAtCanvasSize = mu + Math.Sqrt(-2 * Math.Pow(sigma, 2) * Math.Log(CanvasSize / A)); + xAtCanvasSize = Math.Max(xAtCanvasSize, 0); + + if (!valuesX.Contains(xAtCanvasSize)) + { + valuesX.Add(xAtCanvasSize); + valuesY.Add(CanvasSize); + } + } + } + } + else + { + var step = CanvasSize / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + double x = 0 + i * step; + double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); + valuesX.Add(x); + valuesY.Add(y); + } + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs new file mode 100644 index 00000000000..52849c32630 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a linear curve in the CurveMapper. + /// A linear curve is a straight line between two control points. + /// + public class LinearCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + + private bool flipHorizontally; + private bool flipVertically; + + public LinearCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + } + + /// + /// Calculates the Y values (canvas coordinates) for min and max X values + /// + private double LineEquation(double x) + { + double dx = ControlPoint2X - ControlPoint1X; + double dy = ControlPoint2Y - ControlPoint1Y; + if (Math.Abs(dx) < double.Epsilon) return double.NaN; + + return dy / dx * (x - ControlPoint1X) + ControlPoint1Y; + } + + /// + /// Calculates the X values (canvas coordinates) for min and max Y values + /// + private double SolveForXGivenY(double y) + { + double slope = (ControlPoint2Y - ControlPoint1Y) / (ControlPoint2X - ControlPoint1X); + if (double.IsNaN(slope) || Math.Abs(ControlPoint2X - ControlPoint1X) < double.Epsilon) + { + return double.NaN; + } + + return ((y - ControlPoint1Y) / slope) + ControlPoint1X; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + { + double leftX = SolveForXGivenY(0); + double rightX = SolveForXGivenY(CanvasSize); + + double lowY = LineEquation(0); + double highY = LineEquation(CanvasSize); + + var valuesX = new List(); + var valuesY = new List(); + + flipHorizontally = ControlPoint1X > ControlPoint2X; + flipVertically = ControlPoint1Y > ControlPoint2Y; + + if (isRender) + { + valuesX = new List { System.Math.Clamp(leftX, 0, CanvasSize), System.Math.Clamp(rightX, 0, CanvasSize) }; + + valuesY = (flipHorizontally ^ flipVertically) + ? new List { System.Math.Clamp(highY, 0, CanvasSize), System.Math.Clamp(lowY, 0, CanvasSize) } + : new List { System.Math.Clamp(lowY, 0, CanvasSize), System.Math.Clamp(highY, 0, CanvasSize) }; + } + else + { + // For full point distribution + double stepX = CanvasSize / (pointsCount - 1); + double stepY = (highY - lowY) / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + valuesX.Add(i * stepX); + valuesY.Add((lowY + i * stepY)); + } + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs new file mode 100644 index 00000000000..2418af9358e --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a parabolic curve in the CurveMapper. + /// The curve follows a quadratic equation based on two control points. + /// + public class ParabolicCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + + public ParabolicCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + } + + private double SolveParabolaForX(double y, bool isNegative = false) + { + double a = Math.Pow(ControlPoint2X - ControlPoint1X, 2) / (4.0 * (ControlPoint2Y - ControlPoint1Y)); + double h = ControlPoint1X; + double k = ControlPoint1Y; + return ((isNegative) ? -1.0 : 1.0) * Math.Sqrt(4.0 * a * (y - k)) + h; + } + + private double SolveParabolaForY(double x) + { + double a = Math.Pow(ControlPoint2X - ControlPoint1X, 2) / (4.0 * (ControlPoint2Y - ControlPoint1Y)); + double h = ControlPoint1X; + double k = ControlPoint1Y; + return (Math.Pow(x - h, 2) / (4 * a)) + k; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + { + double leftBoundaryY = (ControlPoint2Y > ControlPoint1Y) ? CanvasSize : 0.0; + double rightBoundaryY = (ControlPoint2Y < ControlPoint1Y) ? CanvasSize : 0.0; + + double startX = SolveParabolaForX(leftBoundaryY, true); + double endX = SolveParabolaForX(leftBoundaryY); + + if (isRender) + { + double minX = Math.Max(0, Math.Min(startX, endX)); + double maxX = Math.Min(CanvasSize, Math.Max(startX, endX)); + + // First point + double firstY = SolveParabolaForY(minX); + + var valuesX = new List { minX }; + var valuesY = new List { firstY }; + + for (double d = minX; d < maxX; d += renderIncrementX) + { + double vy = SolveParabolaForY(d); + if (vy >= 0 && vy < CanvasSize) + { + valuesX.Add(d); + valuesY.Add(vy); + } + } + + // Last point + valuesX.Add(maxX); + valuesY.Add(SolveParabolaForY(maxX)); + + return (valuesX, valuesY); + } + else + { + bool flip = ControlPoint2Y > ControlPoint1Y; + + // First point + double firstY = SolveParabolaForY(leftBoundaryY); + double firstX = leftBoundaryY; + + var valuesX = new List(); + var valuesY = new List(); + + var step = (rightBoundaryY - leftBoundaryY) / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + double d = leftBoundaryY + i * step; + double vy = SolveParabolaForY(d); + + valuesX.Add(d); + valuesY.Add(vy); + } + + // Reverse lists if needed to ensure X values increase from left to right + if (flip) + { + valuesX.Reverse(); + valuesY.Reverse(); + } + + return (valuesX, valuesY); + } + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs new file mode 100644 index 00000000000..48d4773d0ca --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a Perlin noise curve in the CurveMapper. + /// The curve generates procedural noise based on control points and Perlin noise functions. + /// + public class PerlinNoiseCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + private double ControlPoint3X; + private double ControlPoint3Y; + + private readonly List randomValues; + private readonly Random rand; + + private const int PerlinVertices = 2000; + private const int Seed = 1; + private readonly int perlinVerticesMask; + private readonly double initialWidth = 240; + private double amplitude = 1.0; + private double scale = 1.0; + + private int numberOfOctaves; // Number of noise layers (octaves) + private double baseFrequency; // Initial frequency of the noise + private double persistenceFactor; // Controls amplitude reduction per octave + private int randomSeed; // Seed for random noise generation + + + public PerlinNoiseCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double cp3X, double cp3Y, double canvasSize) + : base(canvasSize) + { + //CanvasSize = canvasSize; + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + ControlPoint3X = cp3X; + ControlPoint3Y = cp3Y; + + perlinVerticesMask = PerlinVertices - 1; + rand = new Random(Seed); + randomValues = new List(); + + ConfigureNoiseParameters(4.0, 0.1, 1.0, 1, Seed); + + for (int i = 0; i < PerlinVertices; i++) + { + randomValues.Add(rand.NextDouble() - 0.5); + } + } + + private void ConfigureNoiseParameters(double persistenceFactor, double baseFrequency, double baseAmplitude, int numberOfOctaves, int seed) + { + this.persistenceFactor = persistenceFactor; + randomSeed = seed; + this.numberOfOctaves = numberOfOctaves; + amplitude = baseAmplitude; + this.baseFrequency = baseFrequency; + } + + private double GetHeight(double x, double y) + { + return amplitude * ComputePerlinNoiseSum(x, y); + } + + private double ComputePerlinNoiseSum(double i, double j) + { + double totalNoise = 0.0; + double currentAmplitude = 1; + double currentFrequency = baseFrequency; + + for (int k = 0; k < numberOfOctaves; k++) + { + totalNoise += GetValue(j * currentFrequency + randomSeed + (PerlinVertices / 2), i * currentFrequency + randomSeed + (PerlinVertices / 2)) * currentAmplitude; + currentAmplitude *= persistenceFactor; + currentFrequency *= 2; + } + + return totalNoise; + } + + private double GetValue(double x, double y) + { + int xInt = (int)x; + int yInt = (int)y; + double xFrac = x - xInt; + double yFrac = y - yInt; + + //noise values + double n01 = Noise(xInt - 1, yInt - 1); + double n02 = Noise(xInt + 1, yInt - 1); + double n03 = Noise(xInt - 1, yInt + 1); + double n04 = Noise(xInt + 1, yInt + 1); + double n05 = Noise(xInt - 1, yInt); + double n06 = Noise(xInt + 1, yInt); + double n07 = Noise(xInt, yInt - 1); + double n08 = Noise(xInt, yInt + 1); + double n09 = Noise(xInt, yInt); + + double n12 = Noise(xInt + 2, yInt - 1); + double n14 = Noise(xInt + 2, yInt + 1); + double n16 = Noise(xInt + 2, yInt); + + double n23 = Noise(xInt - 1, yInt + 2); + double n24 = Noise(xInt + 1, yInt + 2); + double n28 = Noise(xInt, yInt + 2); + + double n34 = Noise(xInt + 2, yInt + 2); + + //find the noise values of the four corners + double x0y0 = 0.0625 * (n01 + n02 + n03 + n04) + 0.125 * (n05 + n06 + n07 + n08) + 0.25 * (n09); + double x1y0 = 0.0625 * (n07 + n12 + n08 + n14) + 0.125 * (n09 + n16 + n02 + n04) + 0.25 * (n06); + double x0y1 = 0.0625 * (n05 + n06 + n23 + n24) + 0.125 * (n03 + n04 + n09 + n28) + 0.25 * (n08); + double x1y1 = 0.0625 * (n09 + n16 + n28 + n34) + 0.125 * (n08 + n14 + n06 + n24) + 0.25 * (n04); + + //interpolate between those values according to the x and y fractions + double v1 = Interpolate(x0y0, x1y0, xFrac); //interpolate in x direction (y) + double v2 = Interpolate(x0y1, x1y1, xFrac); //interpolate in x direction (y+1) + double fin = Interpolate(v1, v2, yFrac); //interpolate in y direction + + return fin; + } + + private double Interpolate(double startValue, double endValue, double interpolationFactor) + { + double inverseFactor = 1.0 - interpolationFactor; + double inverseFactorSquared = inverseFactor * inverseFactor; + double weightStart = 3.0 * inverseFactorSquared - 2.0 * inverseFactorSquared * inverseFactor; + + double factorSquared = interpolationFactor * interpolationFactor; + double weightEnd = 3.0 * factorSquared - 2.0 * (factorSquared * interpolationFactor); + + return startValue * weightStart + endValue * weightEnd; + } + + private double Noise(int x, int y) + { + int n = x + y * 57; + n = (n << 13) ^ n; + int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff; + return 1.0 - (double)t * 0.93132257461547858515625e-9; + } + + private double ComputePerlinCurveY(double x) + { + // Calculate the raw point using the adjusted Y + double y = GetHeight(-ControlPoint3X + x, 0.0) + (CanvasSize - ControlPoint3Y); + + // Flipped value to align correctly with WPF's coordinate system + return CanvasSize - y; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + { + var valuesX = new List(); + var valuesY = new List(); + + double scalingFactor = CanvasSize / initialWidth; + scale = (CanvasSize - ControlPoint1X) / (CanvasSize * 3.0); + amplitude = (CanvasSize - ControlPoint2Y) * 1.1; + baseFrequency = scale / scalingFactor; + + if (isRender) + { + for (double x = 0.0; x <= CanvasSize; x += renderIncrementX) + { + var y = ComputePerlinCurveY(x); + + if (y >= 0 && y <= CanvasSize) + { + valuesX.Add(x); + valuesY.Add(y); + } + } + + // Add the intersection points & sort the values + var intersectionValuesX = GetBoundaryIntersections(); + valuesX.AddRange(intersectionValuesX.XValues); + valuesY.AddRange(intersectionValuesX.YValues); + + var sortedPairs = valuesX.Zip(valuesY, (x, y) => new { X = x, Y = y }) + .OrderBy(pair => pair.X) + .ToList(); + valuesX = sortedPairs.Select(p => p.X).ToList(); + valuesY = sortedPairs.Select(p => p.Y).ToList(); + } + else + { + var step = CanvasSize / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + double x = 0 + step * i; + double y = ComputePerlinCurveY(x); + + valuesX.Add(x); + valuesY.Add(y); + } + } + + return (valuesX, valuesY); + } + + private (List XValues, List YValues) GetBoundaryIntersections() + { + var intersectionXPoints = new List(); + var intersectionYPoints = new List(); + + double previousY = ComputePerlinCurveY(0); + double previousX = 0; + double step = renderIncrementX; + + for (double currentX = step; currentX <= CanvasSize; currentX += step) + { + double currentY = ComputePerlinCurveY(currentX); + + // Check for intersection at Y = 0 + if ((previousY < 0 && currentY >= 0) || (previousY > 0 && currentY <= 0)) + { + double intersectX = previousX + ((0 - previousY) * (currentX - previousX) / (currentY - previousY)); + intersectionXPoints.Add(intersectX); + intersectionYPoints.Add(0); + } + + // Check for intersection at Y = CanvasSize + if ((previousY < CanvasSize && currentY >= CanvasSize) || (previousY > CanvasSize && currentY <= CanvasSize)) + { + double intersectX = previousX + ((CanvasSize - previousY) * (currentX - previousX) / (currentY - previousY)); + intersectionXPoints.Add(intersectX); + intersectionYPoints.Add(CanvasSize); + } + + previousX = currentX; + previousY = currentY; + } + + return (intersectionXPoints, intersectionYPoints); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs new file mode 100644 index 00000000000..b286ddc02ae --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a power function curve in the CurveMapper. + /// The curve is defined by a power equation derived from a control point. + /// + public class PowerCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + + public PowerCurve(double cp1X, double cp1Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + } + + private double UpdatePowerEquation() + { + // Normalize x & y of the control point + double xControl = ControlPoint1X / CanvasSize; + double yControl = ControlPoint1Y / CanvasSize; + + if (xControl == yControl) + return 1.0; + + if (xControl <= 0.0 || yControl >= 1.0) + return double.NegativeInfinity; + + if (xControl >= 1.0 || yControl <= 0.0) + return double.PositiveInfinity; + + return Math.Log(yControl) / Math.Log(xControl); + } + + + private double ComputePowerY(double x, double powerFactor) + { + double baseX = x / CanvasSize; + double normalizedY = Math.Pow(baseX, powerFactor); + + return normalizedY * CanvasSize; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + { + var valuesX = new List(); + var valuesY = new List(); + double powerFactor = UpdatePowerEquation(); + + if (isRender) + { + for (double x = 0.0; x <= CanvasSize; x += renderIncrementX) + { + valuesX.Add(x); + double y = ComputePowerY(x, powerFactor); + valuesY.Add(y); + } + } + else + { + double step = CanvasSize / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + double x = i * step; + valuesX.Add(x); + double y = ComputePowerY(x, powerFactor); + valuesY.Add(y); + } + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs new file mode 100644 index 00000000000..83ec7bc0029 --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs @@ -0,0 +1,78 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a sine wave curve in the CurveMapper. + /// The sine wave is defined by two control points and follows a trigonometric function. + /// + public class SineWave : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + + // Coefficients + private double coefA; // Amplitude + private double coefB; // 2*PI/period + private double coefC; // Phase shift + private double coefD; // Vertical shift + + public SineWave(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) + : base(canvasSize) + { + CanvasSize = canvasSize; + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + GetEquationCoefficients(); + } + + private double ConvertXToTrigo(double x) => System.Math.PI * x / CanvasSize; + private double ConvertYToTrigo(double y) => 2.0 * y / CanvasSize - 1.0; + private double ConvertTrigoToY(double unitY) => (unitY + 1.0) * CanvasSize / (2.0); + private double CosineEquation(double x) => -(coefA * System.Math.Cos(coefB * x - coefC)) + coefD; + + private void GetEquationCoefficients() + { + coefA = (ConvertYToTrigo(ControlPoint2Y) - ConvertYToTrigo(ControlPoint1Y)) / 2.0; + coefB = Math.PI / (ConvertXToTrigo(ControlPoint2X) - ConvertXToTrigo(ControlPoint1X)); + coefC = coefB * ConvertXToTrigo(ControlPoint1X); + coefD = (ConvertYToTrigo(ControlPoint1Y) + ConvertYToTrigo(ControlPoint2Y)) / 2.0; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) + { + var valuesX = new List(); + var valuesY = new List(); + + if (isRender) + { + for (double x = 0.0; x < CanvasSize; x += renderIncrementX) + { + valuesX.Add(x); + double y = CosineEquation(ConvertXToTrigo(x)); + valuesY.Add(ConvertTrigoToY(y)); + } + } + else + { + double step = CanvasSize / (pointsCount - 1); + for (int i = 0; i < pointsCount; i++) + { + double x = i * step; + valuesX.Add(x); + double y = CosineEquation(ConvertXToTrigo(x)); + valuesY.Add(ConvertTrigoToY(y)); + } + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs new file mode 100644 index 00000000000..ec54d7130ca --- /dev/null +++ b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs @@ -0,0 +1,109 @@ +using System.Collections.Generic; + +namespace DSCore.CurveMapper +{ + /// + /// Represents a square root curve in the CurveMapper. + /// The curve follows a square root function and is influenced by two control points. + /// + public class SquareRootCurve : CurveBase + { + private double ControlPoint1X; + private double ControlPoint1Y; + private double ControlPoint2X; + private double ControlPoint2Y; + + public SquareRootCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) + : base(canvasSize) + { + ControlPoint1X = cp1X; + ControlPoint1Y = cp1Y; + ControlPoint2X = cp2X; + ControlPoint2Y = cp2Y; + } + + private double ComputeSquareRootFactor() + { + double Ox = ControlPoint1X / CanvasSize; + double Oy = 1 - (ControlPoint1Y / CanvasSize); + double Gx = ControlPoint2X / CanvasSize; + double Gy = 1 - (ControlPoint2Y / CanvasSize); + + double deltaX = Gx - Ox; + if (deltaX == 0) return double.NaN; + + double sqrtFactor = (Gy - Oy) / Math.Sqrt(Math.Abs(deltaX)); + + // If controlPoint1 is to the right of controlPoint2, flip the curve + return (Gx < Ox) ? -sqrtFactor : sqrtFactor; + } + + private double ComputeSquareRootY(double x, double sqrtFactor) + { + double Ox = ControlPoint1X / CanvasSize; + double Oy = 1 - (ControlPoint1Y / CanvasSize); + + double baseX = x / CanvasSize; + double adjustedX = baseX - Ox; + + double sqrtComponent = sqrtFactor * Math.Sqrt(Math.Abs(adjustedX)); + + // Mirror the Y-values if X is to the left of controlPoint1 + double normalizedY = Oy + (adjustedX < 0 ? -sqrtComponent : sqrtComponent); + return (1 - normalizedY) * CanvasSize; + } + + /// + /// Returns X and Y values distributed across the curve. + /// + protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) + { + var valuesX = new List(); + var valuesY = new List(); + + double sqrtFactor = ComputeSquareRootFactor(); + + // Find the first valid X-value where the curve is within the visible canvas + double startX = 0; + + for (double x = 0; x <= CanvasSize; x += renderIncrementX) + { + double y = ComputeSquareRootY(x, sqrtFactor); + if (y >= 0 && y <= CanvasSize) + { + startX = x; + break; + } + } + + if (isRender) + { + for (double x = startX; x <= CanvasSize; x += renderIncrementX) + { + double y = ComputeSquareRootY(x, sqrtFactor); + + // Only add points that are within the visible canvas + if (y >= 0 && y <= CanvasSize) + { + valuesX.Add(x); + valuesY.Add(y); + } + } + } + else + { + var step = CanvasSize / (pointsCount - 1); + + for (int i = 0; i < pointsCount; i++) + { + double x = 0 + i * step; + + valuesX.Add(x); + valuesY.Add(ComputeSquareRootY(x, sqrtFactor)); + } + } + + return (valuesX, valuesY); + } + } +} diff --git a/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml b/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml index 679ef35983e..82cbae32340 100644 --- a/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml +++ b/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml @@ -1,4 +1,4 @@ -_09WhkUH!7f_S180G&hB10KOCH)e_sf7kE5wUK0VHqt8AJR!u$)=sR)%-y}? zipyXl(c?AxrH+#H|Ex)oSWZyj0 zZ$~^>GFqIcK0f>6Le=b{hb0-A{l;r%U+3!#qqx|JPDnhG4z@kQMmXsZ;A;15F-w$n z4BZHlMT~=H3Tqx`KtFu%?`c0V8WgJBZWuznIj5qpf#H5^coCyO@=QONfTkSn9@K=x z$fu8`9ZXMPTc-`*o$YilLciMy2ABkNb6U=}B4=UULSoU>9|O?iwXn3M`=1xIX1%pN z&z)jilJx16;Zz`6#RGREjB|^L5@B!Naw#m(_YIgH-2TB={Ulw26gCJ7n&t`>MBbyD zYivQMDmu6Af-&Wo!!j)ci<1?=Io6aojEVgRt_dJPCVheoM()i`E3y!i#Lwb~xafx= z2Hi;pBXeJ_VO`jVcPCD?pW5OGP1#C2sp(pf#r0C;<(AJeWu>_vzh|M}Mo#-0$8Bdi zDO^Hu>gQo{TnS}5mNZEUamlc+P)0#`j#vyrNC`O8Q`tOs!4e=Z8WNmGUA z9o9r2-Ibv`$@PhcH2=rwo=vabF0Vo^=%NQct)p8a9YFmA{0Lz^x_V!|zyl2x(Fys3 zOYh&j4G&y4JfZcKPU-Ezo%RHR%fHd%HPK(63VodeQePD;RjfLCsT-VP;1B4H6+Lui z@(h1o|HUc>U-m3GeZYLpIEXLxZ=y@^|Sc+X0Ga_&E^yRcW ztb}65LE)SK#ywS!N?xJJu8QffCiX|;>W~5c{HoYl=$%L_fCUhi&vcAXfenqEIq<-$ zTYB1F8PeYqJ-D55sZR<3IZ7#2v+k?v>XwrADBhg4t-DYcZdz8)LVe~57hqx5u8|2Z zxGQtsK|*%%!b1Iq7)1Fu>cGl9Ob9Qo@>%+yQxW+mer(yR^^;5d-*K*-v4R6X|IzkK zYdzAN;(y&c>)7Gr>rW2(D-lx6|3j!PFV73|x0QlNv>H-#w)G7Snbz!+t7>?U so*G)5cU?^kll^t{j|BhQaK5$%%CB}U;#K=c*Ix^u9t=28=}8y-4|qQJvH$=8 literal 0 HcmV?d00001 diff --git a/src/Resources/CoreNodeModels/SmallIcons/CoreNodeModels.CurveMapper.Small.png b/src/Resources/CoreNodeModels/SmallIcons/CoreNodeModels.CurveMapper.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f4ff114fe3679dbfc41037bd4477c6514d872c GIT binary patch literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7e6l0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#N0|Vop0G|+7pn{t>Z|=GLr1(xOeONt(!L=-nzEo;)|2l?k_&~CjQ|6(xd;{PyeYo@vrFUf1oZP$~_EaK)7Yc z{#Bj$50`}qLdeaRp6|T$wC4Dqf+Hwe;A$S+x_0Hp?WPky3t@)CrGW0ab@N8Y$?upZ z-@bWc#+i?p;y}GXT?ei_!jyV?`wFrZ_itU#!Qmu`1AuYBppPUAC4`Y(IyUXN<%=fne zIqW5#zOL*qnS}Wal?afw|7d;XNt3{ zW8A@%rr;2+prN93h-bRk(SSo5*X$i-);Tx@c}c}Ko^gq^Ok1&MPt&4Fo48hSxrLgR z@~&X=)y++ljkT=}VP$J`J0@UzMs1nenKNn6#5CLtqM{;icNjJ2ZrFO6#p>d{gMy)r zlQ(y3M>Njf&3=M^-m+_2`&oI5rv@@IbL z<_Z+uU??u?KP!NV+v(Y*O+_KBJ!h;$r-oiV^Fd?fO4slMG2!-P`HFDj(jliH)Epd$~ zNl7e8wMs5Z1yT$~21cg321dGumLY~FRwfo!Mn>8O237_JPIZ6gplHa=PsvQH#I3NoH Date: Thu, 6 Mar 2025 16:04:56 +0000 Subject: [PATCH 2/9] [DYN-8419] Adjust the warning bar in workspace view extension (#15899) --- .../WorkspaceDependencyView.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml b/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml index 00a517efc19..2735f15c03f 100644 --- a/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml +++ b/src/WorkspaceDependencyViewExtension/WorkspaceDependencyView.xaml @@ -456,7 +456,7 @@ - + - + From b4d0c0c47629439361ee4261ca709f387fd99aa9 Mon Sep 17 00:00:00 2001 From: Ivo Petrov <48355182+ivaylo-matov@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:51:58 +0000 Subject: [PATCH 3/9] [DYN-6370] Restore UI change in group context menu (#15898) --- .../UI/Themes/Modern/DynamoModern.xaml | 108 ++++++++++++++++++ .../PackageManagerPackagesControl.xaml.cs | 2 +- .../Controls/PackageManagerSearchControl.xaml | 7 +- .../PackageManagerSearchView.xaml | 6 +- 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml b/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml index 608914f6bd8..7b81b5533c0 100644 --- a/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml +++ b/src/DynamoCoreWpf/UI/Themes/Modern/DynamoModern.xaml @@ -4106,6 +4106,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4274,6 +4371,17 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs deleted file mode 100644 index edb68c9d89f..00000000000 --- a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControl.xaml.cs +++ /dev/null @@ -1,465 +0,0 @@ -using CoreNodeModels; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Media; -using System.Windows.Shapes; - -namespace Dynamo.Wpf.CurveMapper -{ - /// - /// Interaction logic for CurveMapperControl.xaml - /// - public partial class CurveMapperControl : UserControl, INotifyPropertyChanged - { - private readonly CurveMapperNodeModel curveMapperNodeModel; - private CurveMapperControlPoint linearCurveControlPoint1; - private CurveMapperControlPoint linearCurveControlPoint2; - private CurveMapperControlPoint bezierCurveControlPoint1; - private CurveMapperControlPoint bezierCurveControlPoint2; - private CurveMapperControlPoint bezierCurveControlPoint3; - private CurveMapperControlPoint bezierCurveControlPoint4; - private CurveMapperControlPoint sineWaveControlPoint1; - private CurveMapperControlPoint sineWaveControlPoint2; - private CurveMapperControlPoint cosineWaveControlPoint1; - private CurveMapperControlPoint cosineWaveControlPoint2; - private CurveMapperControlPoint parabolicCurveControlPoint1; - private CurveMapperControlPoint parabolicCurveControlPoint2; - private CurveMapperControlPoint perlinNoiseCurveControlPoint1; - private CurveMapperControlPoint perlinNoiseCurveControlPoint2; - private CurveMapperControlPoint perlinNoiseCurveControlPoint3; - private CurveMapperControlPoint powerCurveControlPoint1; - private CurveMapperControlPoint squareRootCurveControlPoint1; - private CurveMapperControlPoint squareRootCurveControlPoint2; - private CurveMapperControlPoint gaussianCurveControlPoint1; - private CurveMapperControlPoint gaussianCurveControlPoint2; - private CurveMapperControlPoint gaussianCurveControlPoint3; - private CurveMapperControlPoint gaussianCurveControlPoint4; - - private readonly Dictionary controlPointProperties = - new Dictionary - { - { nameof(bezierCurveControlPoint1), (true, true) }, - { nameof(bezierCurveControlPoint2), (true, true) }, - { nameof(perlinNoiseCurveControlPoint1), (true, false) }, - { nameof(perlinNoiseCurveControlPoint2), (true, true) }, - { nameof(gaussianCurveControlPoint1), (true, true) }, - { nameof(gaussianCurveControlPoint2), (true, false) }, - { nameof(gaussianCurveControlPoint3), (true, false) }, - { nameof(gaussianCurveControlPoint4), (true, false) } - }; - - private const double offsetValue = 6; - private const int gridSize = 10; - private const int minCanvasSize = 240; - private const int controlLabelsWidth = 70; - private const int controlLabelsHeight = 100; - - /// - /// Occurs when a property value changes, notifying bound UI elements of updates. - /// - public event PropertyChangedEventHandler PropertyChanged; - - public CurveMapperControl(CurveMapperNodeModel model, double canvasSize) - { - InitializeComponent(); - this.curveMapperNodeModel = model; - DataContext = model; - - Width = canvasSize + controlLabelsWidth; - Height = canvasSize + controlLabelsHeight; - - - model.PropertyChanged += NodeModel_PropertyChanged; - this.Unloaded += Unload; - - DrawGrid(); - - // Dictionary to map UI control points to their corresponding data - var controlPointsMap = BuildControlPointsDictionary(); - RecreateControlPoints(controlPointsMap); - - RenderCurve(); - - ToggleControlPointsLock(); - UpdateLockButton(); - } - - private void RenderCurve() - { - // Remove existing curves (without affecting control points) - Dispatcher.Invoke(() => - { - for (int i = GraphCanvas.Children.Count - 1; i >= 0; i--) - { - if (GraphCanvas.Children[i] is Path) - { - GraphCanvas.Children.RemoveAt(i); - } - } - - // Determine rendering behavior based on graph type - bool isGaussian = curveMapperNodeModel.SelectedGraphType == GraphTypes.GaussianCurve; - bool isPerlin = curveMapperNodeModel.SelectedGraphType == GraphTypes.PerlinNoiseCurve; - - // Only render the curve on valid selection - if (curveMapperNodeModel.SelectedGraphType != GraphTypes.Empty) - { - var paths = CurveRenderer.RenderCurve( - curveMapperNodeModel.RenderValuesX, - curveMapperNodeModel.RenderValuesY, - curveMapperNodeModel.DynamicCanvasSize, - false, isGaussian, isPerlin - ); - - if (paths != null) - { - paths.ForEach(path => GraphCanvas.Children.Add(path)); - } - } - - // Render control lines for Bezier curve - if (curveMapperNodeModel.SelectedGraphType == GraphTypes.BezierCurve) - { - var controlLine1 = CurveRenderer.RenderCurve( - new List { - curveMapperNodeModel.BezierCurveControlPointData1.X, - curveMapperNodeModel.BezierCurveControlPointData3.X - }, - new List { - curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData1.Y, - curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData3.Y - }, - curveMapperNodeModel.DynamicCanvasSize, - true - ); - var controlLine2 = CurveRenderer.RenderCurve( - new List { - curveMapperNodeModel.BezierCurveControlPointData2.X, - curveMapperNodeModel.BezierCurveControlPointData4.X - }, - new List { - curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData2.Y, - curveMapperNodeModel.DynamicCanvasSize - curveMapperNodeModel.BezierCurveControlPointData4.Y - }, - curveMapperNodeModel.DynamicCanvasSize, - true - ); - - GraphCanvas.Children.Add(controlLine1.FirstOrDefault()); - GraphCanvas.Children.Add(controlLine2.FirstOrDefault()); - } - }); - } - - private void ResetButton_Click(object sender, RoutedEventArgs e) - { - if (curveMapperNodeModel.IsLocked) return; - - curveMapperNodeModel.ResetControlPointData(); - - // Dictionary to map UI control points to their corresponding data - var controlPointsResetMap = BuildControlPointsDictionary(); - RecreateControlPoints(controlPointsResetMap); - - curveMapperNodeModel.GenerateRenderValues(); - RenderCurve(); - } - - private void LockButton_Click(object sender, RoutedEventArgs e) - { - var button = sender as Button; - if (button != null) - { - curveMapperNodeModel.IsLocked = !curveMapperNodeModel.IsLocked; - UpdateLockButton(); - ToggleControlPointsLock(); - } - } - - private void NodeModel_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(curveMapperNodeModel.DynamicCanvasSize)) - { - double newSize = curveMapperNodeModel.DynamicCanvasSize; - - // Dictionary to map UI control points to their corresponding data - var controlPointsMap = BuildControlPointsDictionary(); - - // Dynamically retrieve control points from controlPointsMap - foreach (var (pointNames, dataPoints) in controlPointsMap.Values) - { - for (int i = 0; i < pointNames.Count; i++) - { - var pointField = GetType().GetField(pointNames[i], System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var controlPoint = pointField?.GetValue(this) as UIElement; - - if (controlPoint != null && dataPoints[i] != null) - { - UpdateControlPointPosition(controlPoint, dataPoints[i], newSize); - } - } - } - - RenderCurve(); - } - - if (e.PropertyName == nameof(curveMapperNodeModel.SelectedGraphType)) - { - var controlPointsMap = BuildControlPointsDictionary(); - RecreateControlPoints(controlPointsMap); - - curveMapperNodeModel.GenerateRenderValues(); - RenderCurve(); - - ToggleControlPointsLock(); - } - - if (e.PropertyName == nameof(curveMapperNodeModel.RenderValuesX) || - e.PropertyName == nameof(curveMapperNodeModel.RenderValuesY)) - { - RenderCurve(); - } - - if (e.PropertyName == nameof(curveMapperNodeModel.GaussianCurveControlPointData3)) - { - UpdateGaussianControlPoint(gaussianCurveControlPoint3, curveMapperNodeModel.GaussianCurveControlPointData3); - } - if (e.PropertyName == nameof(curveMapperNodeModel.GaussianCurveControlPointData4)) - { - UpdateGaussianControlPoint(gaussianCurveControlPoint4, curveMapperNodeModel.GaussianCurveControlPointData4); - } - } - - private void Unload(object sender, RoutedEventArgs e) - { - this.curveMapperNodeModel.PropertyChanged -= NodeModel_PropertyChanged; - Unloaded -= Unload; - } - - private void RecreateControlPoints(Dictionary pointNames, List dataPoints)> controlPointsMap) - { - // Remove existing control points - var existingControlPoints = GraphCanvas.Children.OfType().ToList(); - foreach (var cp in existingControlPoints) - { - GraphCanvas.Children.Remove(cp); - } - - // Recreate control points for the selected graph - var selectedType = curveMapperNodeModel.SelectedGraphType; - if (controlPointsMap.ContainsKey(selectedType)) - { - var (pointNames, dataPoints) = controlPointsMap[selectedType]; - Type controlType = this.GetType(); - - for (int i = 0; i < pointNames.Count; i++) - { - // Get the field dynamically & remove the old control point - var pointField = controlType.GetField(pointNames[i], System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - var oldPoint = pointField?.GetValue(this) as CurveMapperControlPoint; - if (oldPoint != null) GraphCanvas.Children.Remove(oldPoint); - - // Determine if this control point should be orthogonal or vertical - (bool isOrthogonal, bool isVertical) = controlPointProperties.TryGetValue(pointNames[i], out var props) - ? props - : (false, false); - - var newPoint = new CurveMapperControlPoint(dataPoints[i], curveMapperNodeModel.DynamicCanvasSize, curveMapperNodeModel, RenderCurve, isOrthogonal, isVertical); - pointField?.SetValue(this, newPoint); - GraphCanvas.Children.Add(newPoint); - - // Ensure correct visibility on graph load - double newX = dataPoints[i].X; - double newY = dataPoints[i].Y; - double canvasSize = curveMapperNodeModel.DynamicCanvasSize; - newPoint.Visibility = (newX < 0 || newX > canvasSize || newY < 0 || newY > canvasSize) - ? Visibility.Hidden - : Visibility.Visible; - - Canvas.SetLeft(newPoint, dataPoints[i].X - offsetValue); - Canvas.SetTop(newPoint, dataPoints[i].Y - offsetValue); - } - } - } - - private void UpdateControlPointPosition(UIElement controlPoint, ControlPointData dataPoint, double newSize) - { - if (controlPoint != null && dataPoint != null) - { - double newX = (dataPoint.X / newSize) * newSize; - double newY = ((curveMapperNodeModel.DynamicCanvasSize - dataPoint.Y) / newSize) * newSize; - - Canvas.SetLeft(controlPoint, newX - offsetValue); - Canvas.SetTop(controlPoint, newSize - newY - offsetValue); - } - } - - private void UpdateGaussianControlPoint(UIElement controlPoint, ControlPointData dataPoint) - { - if (controlPoint != null && dataPoint != null) - { - double newX = dataPoint.X; - double newY = dataPoint.Y; - double canvasSize = curveMapperNodeModel.DynamicCanvasSize; - - // Update position - Canvas.SetLeft(controlPoint, newX - offsetValue); - Canvas.SetTop(controlPoint, newY - offsetValue); - - // Hide if out of bounds - controlPoint.Visibility = (newX < 0 || newX > canvasSize || newY < 0 || newY > canvasSize) - ? Visibility.Hidden - : Visibility.Visible; - } - } - - - private void DrawGrid() - { - // Remove current grid lines - var gridLines = GraphCanvas.Children - .OfType() - .Where(child => (child as FrameworkElement)?.Tag?.ToString() == "GridLine") - .ToList(); - - foreach (var line in gridLines) - { - GraphCanvas.Children.Remove(line); - } - - // Draw grid lines - double xPixelsPerStep = curveMapperNodeModel.DynamicCanvasSize / gridSize; - double yPixelsPerStep = curveMapperNodeModel.DynamicCanvasSize / gridSize; - - for (int i = 0; i <= gridSize; i++) - { - double xPos = i * xPixelsPerStep; - DrawLine(xPos, 0, xPos, curveMapperNodeModel.DynamicCanvasSize); - } - - for (int i = 0; i <= gridSize; i++) - { - double yPos = i * yPixelsPerStep; - DrawLine(0, yPos, curveMapperNodeModel.DynamicCanvasSize, yPos); - } - } - - private void DrawLine(double x1, double y1, double x2, double y2) - { - var line = new System.Windows.Shapes.Line - { - X1 = x1, - Y1 = y1, - X2 = x2, - Y2 = y2, - Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5e5e5e")), - StrokeThickness = 0.6, - Tag = "GridLine" - }; - Canvas.SetZIndex(line, 0); - GraphCanvas.Children.Add(line); - } - - /// - /// Dictionary mapping UI control points to their corresponding data. - /// Although it seems redundant to recreate this dictionary, reusing a property does not work - /// because control point data references are updated dynamically. - /// - private Dictionary pointNames, List dataPoints)> BuildControlPointsDictionary() - { - var controlPointsResetMap = new Dictionary pointNames, List dataPoints)> - { - { GraphTypes.LinearCurve, (new List { nameof(linearCurveControlPoint1), nameof(linearCurveControlPoint2) }, - new List { curveMapperNodeModel.LinearCurveControlPointData1, curveMapperNodeModel.LinearCurveControlPointData2 }) }, - - { GraphTypes.BezierCurve, (new List { nameof(bezierCurveControlPoint1), nameof(bezierCurveControlPoint2), nameof(bezierCurveControlPoint3), nameof(bezierCurveControlPoint4) }, - new List { curveMapperNodeModel.BezierCurveControlPointData1, curveMapperNodeModel.BezierCurveControlPointData2, - curveMapperNodeModel.BezierCurveControlPointData3, curveMapperNodeModel.BezierCurveControlPointData4 }) }, - - { GraphTypes.SineWave, (new List { nameof(sineWaveControlPoint1), nameof(sineWaveControlPoint2) }, - new List { curveMapperNodeModel.SineWaveControlPointData1, curveMapperNodeModel.SineWaveControlPointData2 }) }, - - { GraphTypes.CosineWave, (new List { nameof(cosineWaveControlPoint1), nameof(cosineWaveControlPoint2) }, - new List { curveMapperNodeModel.CosineWaveControlPointData1, curveMapperNodeModel.CosineWaveControlPointData2 }) }, - - { GraphTypes.ParabolicCurve, (new List { nameof(parabolicCurveControlPoint1), nameof(parabolicCurveControlPoint2) }, - new List { curveMapperNodeModel.ParabolicCurveControlPointData1, curveMapperNodeModel.ParabolicCurveControlPointData2 }) }, - - { GraphTypes.PerlinNoiseCurve, (new List { nameof(perlinNoiseCurveControlPoint1), nameof(perlinNoiseCurveControlPoint2), nameof(perlinNoiseCurveControlPoint3) }, - new List { curveMapperNodeModel.PerlinNoiseControlPointData1, curveMapperNodeModel.PerlinNoiseControlPointData2, - curveMapperNodeModel.PerlinNoiseControlPointData3 }) }, - - { GraphTypes.PowerCurve, (new List { nameof(powerCurveControlPoint1) }, - new List { curveMapperNodeModel.PowerCurveControlPointData1 }) }, - - { GraphTypes.SquareRootCurve, (new List { nameof(squareRootCurveControlPoint1), nameof(squareRootCurveControlPoint2) }, - new List { curveMapperNodeModel.SquareRootCurveControlPointData1, curveMapperNodeModel.SquareRootCurveControlPointData2 }) }, - - { GraphTypes.GaussianCurve, (new List { nameof(gaussianCurveControlPoint1), nameof(gaussianCurveControlPoint2), nameof(gaussianCurveControlPoint3), nameof(gaussianCurveControlPoint4) }, - new List { curveMapperNodeModel.GaussianCurveControlPointData1, curveMapperNodeModel.GaussianCurveControlPointData2, - curveMapperNodeModel.GaussianCurveControlPointData3, curveMapperNodeModel.GaussianCurveControlPointData4}) } - }; - - return controlPointsResetMap; - } - - private void ToggleControlPointsLock() - { - foreach (var child in GraphCanvas.Children) - { - if (child is CurveMapperControlPoint controlPoint) - { - controlPoint.IsMoveEnabled = !curveMapperNodeModel.IsLocked; - } - } - } - - private void UpdateLockButton() - { - if (LockButton != null) - { - LockButton.Tag = curveMapperNodeModel.IsLocked ? "Locked" : "Unlocked"; - if (LockButton.ToolTip is ToolTip lockTooltip) - { - lockTooltip.Content = curveMapperNodeModel.IsLocked - ? "Curve cannot be modified. Click to unlock." - : "Click to lock the curve."; - } - ResetButton.Tag = curveMapperNodeModel.IsLocked ? "Locked" : "Unlocked"; - if (ResetButton.ToolTip is ToolTip resetTooltip) - { - resetTooltip.Content = curveMapperNodeModel.IsLocked - ? "The curve has been locked and cannot be reset. Please unlock the curve first." - : "Reset the curve."; - } - } - } - - private void ThumbResizeThumbOnDragDeltaHandler(object sender, DragDeltaEventArgs e) - { - var sizeChange = Math.Min(e.VerticalChange, e.HorizontalChange); - var yAdjust = ActualHeight + sizeChange; - var xAdjust = ActualWidth + sizeChange; - - // Ensure the mainGrid doesn't resize below its minimum size - yAdjust = Math.Max(yAdjust, minCanvasSize + controlLabelsHeight); - xAdjust = Math.Max(xAdjust, minCanvasSize + controlLabelsWidth); - - Width = xAdjust; - Height = yAdjust; - - // Adjust the size of the GraphCanvas dynamically - curveMapperNodeModel.DynamicCanvasSize = Math.Max(xAdjust - controlLabelsWidth, minCanvasSize); - DrawGrid(); - - // Reposition control points based on the new size - NodeModel_PropertyChanged(this, new PropertyChangedEventArgs(nameof(curveMapperNodeModel.DynamicCanvasSize))); - curveMapperNodeModel.GenerateRenderValues(); - } - } -} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml deleted file mode 100644 index ad93c10608b..00000000000 --- a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - Drag to modify the curve. - - - - - - - diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs deleted file mode 100644 index def8d9a6956..00000000000 --- a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperControlPoint.xaml.cs +++ /dev/null @@ -1,192 +0,0 @@ -using CoreNodeModels; -using Newtonsoft.Json; -using System; -using System.ComponentModel; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Input; - -namespace Dynamo.Wpf.CurveMapper -{ - /// - /// Interaction logic for CurveMapperControlPoint.xaml - /// - public partial class CurveMapperControlPoint : Thumb, INotifyPropertyChanged - { - private CurveMapperNodeModel curveMapperNodeModel; - private const double offsetValue = 6; - private bool isMoveEnabled = true; - private bool isOrthogonal; - private bool isVertical; - private ControlPointData controlPointData; - - /// - /// Represents the dynamic canvas size for the control point. - /// - public double CanvasSize - { - get => (double)GetValue(DynamicCanvasSizeProperty); - set => SetValue(DynamicCanvasSizeProperty, value); - } - - /// - /// Gets the scaled coordinates of the control point, normalized based on Min/Max limits. - /// - [JsonIgnore] - public string ScaledCoordinates - { - get - { - double scaledX = curveMapperNodeModel.MinLimitX + - (controlPointData.X / CanvasSize) * - (curveMapperNodeModel.MaxLimitX - curveMapperNodeModel.MinLimitX); - - double scaledY = curveMapperNodeModel.MinLimitY + - (1 - (controlPointData.Y / CanvasSize)) * - (curveMapperNodeModel.MaxLimitY - curveMapperNodeModel.MinLimitY); - - return $"Coordinates: ({scaledX:F2}, {scaledY:F2})"; - } - } - - /// - /// Gets or sets a value indicating whether the control point can be moved by the user. - /// - [JsonIgnore] - public bool IsMoveEnabled - { - get => isMoveEnabled; - set - { - if (isMoveEnabled != value) - { - isMoveEnabled = value; - UpdateCursor(); - } - } - } - - /// - /// Identifies the DynamicCanvasSize dependency property, which represents the dynamic canvas size - /// used for scaling and rendering the control point. - /// - public static readonly DependencyProperty DynamicCanvasSizeProperty = RegisterProperty(nameof(DynamicCanvasSizeProperty), 240.0); - - /// - /// Gets or sets the action invoked when the control point is moved. - /// - public Action OnControlPointMoved { get; set; } - - public event PropertyChangedEventHandler PropertyChanged; - - public CurveMapperControlPoint( - ControlPointData controlPoint, - double canvasSize, - CurveMapperNodeModel model, - Action updateCurve, - bool isOrthogonal = false, bool isVertical = false - ) - { - InitializeComponent(); - DataContext = this; - - controlPointData = controlPoint; - CanvasSize = canvasSize; - curveMapperNodeModel = model; - OnControlPointMoved = updateCurve; - this.isOrthogonal = isOrthogonal; - this.isVertical = isVertical; - - Canvas.SetLeft(this, controlPoint.X - offsetValue); - Canvas.SetTop(this, controlPoint.Y - offsetValue); - Canvas.SetZIndex(this, 25); - - UpdateCursor(); - - curveMapperNodeModel.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(curveMapperNodeModel.MinLimitX) || - e.PropertyName == nameof(curveMapperNodeModel.MaxLimitX) || - e.PropertyName == nameof(curveMapperNodeModel.MinLimitY) || - e.PropertyName == nameof(curveMapperNodeModel.MaxLimitY)) - { - RaisePropertyChanged(nameof(ScaledCoordinates)); - } - }; - } - - private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) - { - if (!IsMoveEnabled) return; - - double newCanvasSize = curveMapperNodeModel.DynamicCanvasSize; - - // Calculate horizontal and vertical deltas within canvas boundaries - // Requires for Gaussian control points. - var deltaL = Canvas.GetLeft(this) + offsetValue; - var deltaR = newCanvasSize - Canvas.GetLeft(this) - offsetValue; - var deltaX = e.HorizontalChange > 0 ? - Math.Min(e.HorizontalChange, deltaR) : - Math.Max(e.HorizontalChange, -deltaL); - - var deltaT = Canvas.GetTop(this) + offsetValue; - var deltaB = newCanvasSize - Canvas.GetTop(this) - offsetValue; - var deltaY = e.VerticalChange > 0 ? - Math.Min(e.VerticalChange, deltaB) : - Math.Max(e.VerticalChange, -deltaT); - - // Calculate new positions for X and Y based on drag changes - double newX = Canvas.GetLeft(this) + (isOrthogonal && isVertical ? 0.0 : deltaX) + offsetValue; - double newY = Canvas.GetTop(this) + (isOrthogonal && !isVertical ? 0.0 : deltaY) + offsetValue; - - controlPointData.X = newX; - controlPointData.Y = newY; - Canvas.SetLeft(this, newX - offsetValue); - Canvas.SetTop(this, newY - offsetValue); - - // Handle Gaussian curve control points - string tag = controlPointData.Tag; - - if (tag.Contains("GaussianCurveControlPointData")) - { - curveMapperNodeModel.UpdateGaussianCurveControlPoints(deltaX, tag); - } - - // Refresh scaled coordinates in tooltip - RaisePropertyChanged(nameof(ScaledCoordinates)); - - // Notify the mode and UI to update the curve - curveMapperNodeModel.GenerateRenderValues(); - OnControlPointMoved?.Invoke(); - } - - private void UpdateCursor() - { - this.Cursor = IsMoveEnabled ? Cursors.Hand : Cursors.Arrow; - } - - public void RaisePropertyChanged(string propertyName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - - /// - /// Helper method to register dependency properties with a common property changed callback. - /// - /// - private static DependencyProperty RegisterProperty(string name, double defaultValue) - { - return DependencyProperty.Register(name, typeof(double), typeof(CurveMapperControlPoint), - new PropertyMetadata(defaultValue, OnPropertyUpdated)); - } - - /// - /// Common property changed handler to raise updates for ScaledCoordinates. - /// - private static void OnPropertyUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - } - } -} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs deleted file mode 100644 index d92f7bce274..00000000000 --- a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveMapperNodeView.cs +++ /dev/null @@ -1,25 +0,0 @@ -using CoreNodeModels; -using Dynamo.Controls; - -namespace Dynamo.Wpf.CurveMapper -{ - public class CurveMapperNodeView : INodeViewCustomization - { - private CurveMapperNodeModel curveMapperNodeModel; - private CurveMapperControl curveMapperControl; - - public void CustomizeView(CurveMapperNodeModel model, NodeView nodeView) - { - curveMapperNodeModel = model; - curveMapperControl = new CurveMapperControl(model, model.DynamicCanvasSize); - curveMapperControl.DataContext = model; - curveMapperNodeModel = model; - - nodeView.inputGrid.Children.Add(curveMapperControl); - } - - public void Dispose() - { - } - } -} diff --git a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs b/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs deleted file mode 100644 index f5b48082b1a..00000000000 --- a/src/Libraries/CoreNodeModelsWpf/CurveMapper/CurveRenderer.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using System.Windows; -using System.Windows.Media; -using System.Windows.Shapes; - -namespace Dynamo.Wpf.CurveMapper -{ - public class CurveRenderer - { - /// - /// Renders a curve as a Path object based on given X and Y values, adjusting for the inverted Y-axis in WPF. - /// - public static List RenderCurve( - List xValues, List yValues, double canvasSize, - bool isControlLine = false, bool isGaussian = false, bool isPerlin = false) - { - if (xValues == null || yValues == null || xValues.Count != yValues.Count || xValues.Count < 2) - return null; - - List paths = new List(); - PathGeometry currentGeometry = new PathGeometry(); - PathFigure currentFigure = null; - int hitCount = (isPerlin && (yValues[0] == 0 || yValues[0] == canvasSize)) ? 1 : 0; - - for (int i = 0; i < xValues.Count; i++) - { - Point currentPoint = new Point(xValues[i], canvasSize - yValues[i]); - bool isBoundaryGaussian = isGaussian && yValues[i] == canvasSize; - bool isBoundaryPerlin = isPerlin && (yValues[i] == 0 || yValues[i] == canvasSize); - - - if (isBoundaryGaussian || isBoundaryPerlin) // Odd occurrences: Close the path and reset - { - hitCount++; - if (hitCount % 2 == 1) - { - if (currentFigure != null) - { - currentFigure.Segments.Add(new LineSegment(currentPoint, true)); - currentGeometry.Figures.Add(currentFigure); - paths.Add(CreatePathFromGeometry(currentGeometry, isControlLine)); - } - currentGeometry = new PathGeometry(); - currentFigure = null; - } - else - { - currentFigure = new PathFigure { StartPoint = currentPoint }; - currentGeometry.Figures.Add(currentFigure); - } - } - else // Regular points: Add to the current path normally - { - if (currentFigure == null) - { - currentFigure = new PathFigure { StartPoint = currentPoint }; - currentGeometry.Figures.Add(currentFigure); - } - else - { - currentFigure.Segments.Add(new LineSegment(currentPoint, true)); - } - } - } - - if (currentFigure != null) - { - paths.Add(CreatePathFromGeometry(currentGeometry, isControlLine)); - } - - return paths; - } - - /// - /// Creates a styled Path object from a given PathGeometry. - /// - private static Path CreatePathFromGeometry(PathGeometry geometry, bool isControlLine) - { - return new Path - { - Data = geometry, - Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#B385F2")), - StrokeThickness = isControlLine ? 1 : 3, - StrokeDashArray = isControlLine ? new DoubleCollection { 4, 4 } : null - }; - } - } - -} diff --git a/src/Libraries/CoreNodes/Color.cs b/src/Libraries/CoreNodes/Color.cs index 70c46fd418b..a42d43d7e9c 100644 --- a/src/Libraries/CoreNodes/Color.cs +++ b/src/Libraries/CoreNodes/Color.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -584,4 +584,4 @@ public Color GetColorAtParameter(UV parameter) return color; } } -} +} \ No newline at end of file diff --git a/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs b/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs deleted file mode 100644 index 67a46e1b399..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/BezierCurve.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a Bezier curve in the CurveMapper. - /// A Bezier curve is defined by four control points and provides smooth interpolation. - /// - public class BezierCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - private double ControlPoint3X; - private double ControlPoint3Y; - private double ControlPoint4X; - private double ControlPoint4Y; - - private Dictionary xToYMap = new Dictionary(); - private double tFactor; - - public BezierCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, - double cp3X, double cp3Y, double cp4X, double cp4Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - ControlPoint3X = cp3X; - ControlPoint3Y = cp3Y; - ControlPoint4X = cp4X; - ControlPoint4Y = cp4Y; - - tFactor = 1.0 / canvasSize; - } - - private void GetValueAtT(double t, out double x, out double y) - { - x = Math.Pow(1 - t, 3) * ControlPoint1X + - 3 * Math.Pow(1 - t, 2) * t * ControlPoint3X + - 3 * (1 - t) * t * t * ControlPoint4X + - t * t * t * ControlPoint2X; - - y = Math.Pow(1 - t, 3) * ControlPoint1Y + - 3 * Math.Pow(1 - t, 2) * t * ControlPoint3Y + - 3 * (1 - t) * t * t * ControlPoint4Y + - t * t * t * ControlPoint2Y; - } - - /// - /// Gets interpolated Y values based on the assigned parameters and limits. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) - { - var renderValuesX = new List(); - var renderValuesY = new List(); - var valuesX = new List(); - var valuesY = new List(); - - // Generate fine-grained samples to ensure better interpolation - int fineSteps = (int)(pointsCount * CanvasSize); - - for (int i = 0; i <= fineSteps; i++) - { - double t = i / (double)fineSteps; - - GetValueAtT(t, out double x, out double y); - - renderValuesX.Add(x); - renderValuesY.Add(y); - } - - if (isRender) - { - return (renderValuesX, renderValuesY); - } - - // Collect output values - for (int i = 0; i < pointsCount; i++) - { - double targetX = (i / (double)(pointsCount - 1) * CanvasSize); - int closestIndex = renderValuesX.IndexOf(renderValuesX.OrderBy(x => Math.Abs(x - targetX)).First()); - double y = renderValuesY[closestIndex]; - - valuesX.Add(targetX); - valuesY.Add(y); - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs b/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs deleted file mode 100644 index cb60497892d..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/ControlLine.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a control line in the CurveMapper. - /// This is used for auxiliary control of other curves, particularly Bezier curves. - /// - public class ControlLine : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - - public ControlLine(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - } - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) - { - return (new List { ControlPoint1X, ControlPoint2X }, new List { ControlPoint1Y, ControlPoint2Y }); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs b/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs deleted file mode 100644 index a3ce5c18d52..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/CurveBase.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a base class for all curve types in the CurveMapper. - /// Provides common functionality for generating and retrieving curve values. - /// - public abstract class CurveBase - { - protected double CanvasSize; - protected const double renderIncrementX = 1.0; - - protected CurveBase(double canvasSize) - { - CanvasSize = canvasSize; - } - - /// - /// Abstract method to be implemented by derived classes for generating curve values. - /// - protected abstract (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender); - - /// - /// Common method for retrieving X values. - /// - public virtual List GetCurveXValues(int pointsCount, bool isRender = false) - { - return GenerateCurve(pointsCount, isRender).XValues; - } - - /// - /// Common method for retrieving Y values. - /// - public virtual List GetCurveYValues(int pointsCount, bool isRender = false) - { - return GenerateCurve(pointsCount, isRender).YValues; - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs b/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs deleted file mode 100644 index aad65891ff9..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/CurveMapperGenerator.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - public class CurveMapperGenerator - { - private static int rounding = 10; - - public static List> CalculateValues( - List controlPoints, double canvasSize, - double minX, double maxX, double minY, double maxY, - int pointsCount, string graphType - ) - { - var xValues = new List() { double.NaN }; - var yValues = new List() { double.NaN }; - - if (minX != maxX && minY != maxY && pointsCount >= 2) - { - // Unpack the control points - double cp1x = GetCP(controlPoints, 0), cp1y = GetCP(controlPoints, 1); - double cp2x = GetCP(controlPoints, 2), cp2y = GetCP(controlPoints, 3); - double cp3x = GetCP(controlPoints, 4), cp3y = GetCP(controlPoints, 5); - double cp4x = GetCP(controlPoints, 6), cp4y = GetCP(controlPoints, 7); - - object curve = null; - - switch (graphType) - { - case "LinearCurve": - curve = new LinearCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); - break; - case "BezierCurve": - curve = new BezierCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, cp4x, cp4y, canvasSize); - break; - case "SineWave": - curve = new SineWave(cp1x, cp1y, cp2x, cp2y, canvasSize); - break; - case "CosineWave": - curve = new SineWave(cp1x, cp1y, cp2x, cp2y, canvasSize); - break; - case "ParabolicCurve": - curve = new ParabolicCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); - break; - case "PerlinNoiseCurve": - curve = new PerlinNoiseCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, canvasSize); - break; - case "PowerCurve": - curve = new PowerCurve(cp1x, cp1y, canvasSize); - break; - case "SquareRootCurve": - curve = new SquareRootCurve(cp1x, cp1y, cp2x, cp2y, canvasSize); - break; - case "GaussianCurve": - curve = new GaussianCurve(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y, cp4x, cp4y, canvasSize); - break; - } - - if (curve != null) - { - dynamic dynamicCurve = curve; - - xValues = MapValues(dynamicCurve.GetCurveXValues(pointsCount), minX, maxX, canvasSize); - yValues = MapValues(dynamicCurve.GetCurveYValues(pointsCount), minY, maxY, canvasSize); - } - } - - return new List> { yValues, xValues }; - } - - private static double GetCP(List controlPoints, int index) - { - return index < controlPoints.Count ? controlPoints[index] : 0; - } - - private static List MapValues(List rawValues, double minLimit, double maxLimit, double canvasSize) - { - var mappedValues = new List(); - - foreach (var value in rawValues) - { - mappedValues.Add(Math.Round(minLimit + value / canvasSize * (maxLimit - minLimit), rounding)); - } - return mappedValues; - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs b/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs deleted file mode 100644 index ead49914ae9..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/GaussianCurve.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a Gaussian curve in the CurveMapper. - /// The Gaussian curve follows a bell-shaped distribution defined by four control points. - /// - public class GaussianCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - private double ControlPoint3X; - private double ControlPoint3Y; - private double ControlPoint4X; - private double ControlPoint4Y; - - private double lastControlPoint2X; - private double previousHorizontalOffset; - - /// - /// Indicates whether the node is currently being resized, preventing unintended control point updates. - /// - public GaussianCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double cp3X, double cp3Y, double cp4X, double cp4Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - ControlPoint3X = cp3X; - ControlPoint3Y = cp3Y; - ControlPoint4X = cp4X; - ControlPoint4Y = cp4Y; - } - - private double ComputeGaussianY(double x, double A, double mu, double sigma) - { - double exponent = -Math.Pow(x - mu, 2) / (2 * Math.Pow(sigma, 2)); - double normalizedY = A * Math.Exp(exponent); - - return CanvasSize - normalizedY; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) - { - var valuesX = new List(); - var valuesY = new List(); - - // Compute Gaussian parameters - double A = ControlPoint1Y * 4; - double mu = ControlPoint2X; - double sigma = Math.Abs(ControlPoint4X - ControlPoint3X) / 4; - if (sigma < 1) sigma = 1; - - if (isRender) - { - for (double x = 0; x <= ControlPoint2X; x += renderIncrementX) - { - double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); - - if (y <= CanvasSize) - { - valuesX.Add(x); - valuesY.Add(y); - } - else - { - // Find the x value for which y == CanvasSize - double xAtCanvasSize = mu - Math.Sqrt(-2 * Math.Pow(sigma, 2) * Math.Log(CanvasSize / A)); - xAtCanvasSize = Math.Max(xAtCanvasSize, 0); - - if (!valuesX.Contains(xAtCanvasSize)) - { - valuesX.Add(xAtCanvasSize); - valuesY.Add(CanvasSize); - } - break; - } - } - for (double x = ControlPoint2X; x <= CanvasSize; x += renderIncrementX) - { - double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); - - if (y <= CanvasSize) - { - valuesX.Add(x); - valuesY.Add(y); - } - else - { - // Find the x value for which y == CanvasSize - double xAtCanvasSize = mu + Math.Sqrt(-2 * Math.Pow(sigma, 2) * Math.Log(CanvasSize / A)); - xAtCanvasSize = Math.Max(xAtCanvasSize, 0); - - if (!valuesX.Contains(xAtCanvasSize)) - { - valuesX.Add(xAtCanvasSize); - valuesY.Add(CanvasSize); - } - } - } - } - else - { - var step = CanvasSize / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - double x = 0 + i * step; - double y = CanvasSize - ComputeGaussianY(x, A, mu, sigma); - valuesX.Add(x); - valuesY.Add(y); - } - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs b/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs deleted file mode 100644 index 52849c32630..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/LinearCurve.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a linear curve in the CurveMapper. - /// A linear curve is a straight line between two control points. - /// - public class LinearCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - - private bool flipHorizontally; - private bool flipVertically; - - public LinearCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - } - - /// - /// Calculates the Y values (canvas coordinates) for min and max X values - /// - private double LineEquation(double x) - { - double dx = ControlPoint2X - ControlPoint1X; - double dy = ControlPoint2Y - ControlPoint1Y; - if (Math.Abs(dx) < double.Epsilon) return double.NaN; - - return dy / dx * (x - ControlPoint1X) + ControlPoint1Y; - } - - /// - /// Calculates the X values (canvas coordinates) for min and max Y values - /// - private double SolveForXGivenY(double y) - { - double slope = (ControlPoint2Y - ControlPoint1Y) / (ControlPoint2X - ControlPoint1X); - if (double.IsNaN(slope) || Math.Abs(ControlPoint2X - ControlPoint1X) < double.Epsilon) - { - return double.NaN; - } - - return ((y - ControlPoint1Y) / slope) + ControlPoint1X; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) - { - double leftX = SolveForXGivenY(0); - double rightX = SolveForXGivenY(CanvasSize); - - double lowY = LineEquation(0); - double highY = LineEquation(CanvasSize); - - var valuesX = new List(); - var valuesY = new List(); - - flipHorizontally = ControlPoint1X > ControlPoint2X; - flipVertically = ControlPoint1Y > ControlPoint2Y; - - if (isRender) - { - valuesX = new List { System.Math.Clamp(leftX, 0, CanvasSize), System.Math.Clamp(rightX, 0, CanvasSize) }; - - valuesY = (flipHorizontally ^ flipVertically) - ? new List { System.Math.Clamp(highY, 0, CanvasSize), System.Math.Clamp(lowY, 0, CanvasSize) } - : new List { System.Math.Clamp(lowY, 0, CanvasSize), System.Math.Clamp(highY, 0, CanvasSize) }; - } - else - { - // For full point distribution - double stepX = CanvasSize / (pointsCount - 1); - double stepY = (highY - lowY) / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - valuesX.Add(i * stepX); - valuesY.Add((lowY + i * stepY)); - } - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs b/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs deleted file mode 100644 index 2418af9358e..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/ParabolicCurve.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a parabolic curve in the CurveMapper. - /// The curve follows a quadratic equation based on two control points. - /// - public class ParabolicCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - - public ParabolicCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - } - - private double SolveParabolaForX(double y, bool isNegative = false) - { - double a = Math.Pow(ControlPoint2X - ControlPoint1X, 2) / (4.0 * (ControlPoint2Y - ControlPoint1Y)); - double h = ControlPoint1X; - double k = ControlPoint1Y; - return ((isNegative) ? -1.0 : 1.0) * Math.Sqrt(4.0 * a * (y - k)) + h; - } - - private double SolveParabolaForY(double x) - { - double a = Math.Pow(ControlPoint2X - ControlPoint1X, 2) / (4.0 * (ControlPoint2Y - ControlPoint1Y)); - double h = ControlPoint1X; - double k = ControlPoint1Y; - return (Math.Pow(x - h, 2) / (4 * a)) + k; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) - { - double leftBoundaryY = (ControlPoint2Y > ControlPoint1Y) ? CanvasSize : 0.0; - double rightBoundaryY = (ControlPoint2Y < ControlPoint1Y) ? CanvasSize : 0.0; - - double startX = SolveParabolaForX(leftBoundaryY, true); - double endX = SolveParabolaForX(leftBoundaryY); - - if (isRender) - { - double minX = Math.Max(0, Math.Min(startX, endX)); - double maxX = Math.Min(CanvasSize, Math.Max(startX, endX)); - - // First point - double firstY = SolveParabolaForY(minX); - - var valuesX = new List { minX }; - var valuesY = new List { firstY }; - - for (double d = minX; d < maxX; d += renderIncrementX) - { - double vy = SolveParabolaForY(d); - if (vy >= 0 && vy < CanvasSize) - { - valuesX.Add(d); - valuesY.Add(vy); - } - } - - // Last point - valuesX.Add(maxX); - valuesY.Add(SolveParabolaForY(maxX)); - - return (valuesX, valuesY); - } - else - { - bool flip = ControlPoint2Y > ControlPoint1Y; - - // First point - double firstY = SolveParabolaForY(leftBoundaryY); - double firstX = leftBoundaryY; - - var valuesX = new List(); - var valuesY = new List(); - - var step = (rightBoundaryY - leftBoundaryY) / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - double d = leftBoundaryY + i * step; - double vy = SolveParabolaForY(d); - - valuesX.Add(d); - valuesY.Add(vy); - } - - // Reverse lists if needed to ensure X values increase from left to right - if (flip) - { - valuesX.Reverse(); - valuesY.Reverse(); - } - - return (valuesX, valuesY); - } - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs deleted file mode 100644 index 48d4773d0ca..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/PerlinNoiseCurve.cs +++ /dev/null @@ -1,250 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a Perlin noise curve in the CurveMapper. - /// The curve generates procedural noise based on control points and Perlin noise functions. - /// - public class PerlinNoiseCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - private double ControlPoint3X; - private double ControlPoint3Y; - - private readonly List randomValues; - private readonly Random rand; - - private const int PerlinVertices = 2000; - private const int Seed = 1; - private readonly int perlinVerticesMask; - private readonly double initialWidth = 240; - private double amplitude = 1.0; - private double scale = 1.0; - - private int numberOfOctaves; // Number of noise layers (octaves) - private double baseFrequency; // Initial frequency of the noise - private double persistenceFactor; // Controls amplitude reduction per octave - private int randomSeed; // Seed for random noise generation - - - public PerlinNoiseCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double cp3X, double cp3Y, double canvasSize) - : base(canvasSize) - { - //CanvasSize = canvasSize; - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - ControlPoint3X = cp3X; - ControlPoint3Y = cp3Y; - - perlinVerticesMask = PerlinVertices - 1; - rand = new Random(Seed); - randomValues = new List(); - - ConfigureNoiseParameters(4.0, 0.1, 1.0, 1, Seed); - - for (int i = 0; i < PerlinVertices; i++) - { - randomValues.Add(rand.NextDouble() - 0.5); - } - } - - private void ConfigureNoiseParameters(double persistenceFactor, double baseFrequency, double baseAmplitude, int numberOfOctaves, int seed) - { - this.persistenceFactor = persistenceFactor; - randomSeed = seed; - this.numberOfOctaves = numberOfOctaves; - amplitude = baseAmplitude; - this.baseFrequency = baseFrequency; - } - - private double GetHeight(double x, double y) - { - return amplitude * ComputePerlinNoiseSum(x, y); - } - - private double ComputePerlinNoiseSum(double i, double j) - { - double totalNoise = 0.0; - double currentAmplitude = 1; - double currentFrequency = baseFrequency; - - for (int k = 0; k < numberOfOctaves; k++) - { - totalNoise += GetValue(j * currentFrequency + randomSeed + (PerlinVertices / 2), i * currentFrequency + randomSeed + (PerlinVertices / 2)) * currentAmplitude; - currentAmplitude *= persistenceFactor; - currentFrequency *= 2; - } - - return totalNoise; - } - - private double GetValue(double x, double y) - { - int xInt = (int)x; - int yInt = (int)y; - double xFrac = x - xInt; - double yFrac = y - yInt; - - //noise values - double n01 = Noise(xInt - 1, yInt - 1); - double n02 = Noise(xInt + 1, yInt - 1); - double n03 = Noise(xInt - 1, yInt + 1); - double n04 = Noise(xInt + 1, yInt + 1); - double n05 = Noise(xInt - 1, yInt); - double n06 = Noise(xInt + 1, yInt); - double n07 = Noise(xInt, yInt - 1); - double n08 = Noise(xInt, yInt + 1); - double n09 = Noise(xInt, yInt); - - double n12 = Noise(xInt + 2, yInt - 1); - double n14 = Noise(xInt + 2, yInt + 1); - double n16 = Noise(xInt + 2, yInt); - - double n23 = Noise(xInt - 1, yInt + 2); - double n24 = Noise(xInt + 1, yInt + 2); - double n28 = Noise(xInt, yInt + 2); - - double n34 = Noise(xInt + 2, yInt + 2); - - //find the noise values of the four corners - double x0y0 = 0.0625 * (n01 + n02 + n03 + n04) + 0.125 * (n05 + n06 + n07 + n08) + 0.25 * (n09); - double x1y0 = 0.0625 * (n07 + n12 + n08 + n14) + 0.125 * (n09 + n16 + n02 + n04) + 0.25 * (n06); - double x0y1 = 0.0625 * (n05 + n06 + n23 + n24) + 0.125 * (n03 + n04 + n09 + n28) + 0.25 * (n08); - double x1y1 = 0.0625 * (n09 + n16 + n28 + n34) + 0.125 * (n08 + n14 + n06 + n24) + 0.25 * (n04); - - //interpolate between those values according to the x and y fractions - double v1 = Interpolate(x0y0, x1y0, xFrac); //interpolate in x direction (y) - double v2 = Interpolate(x0y1, x1y1, xFrac); //interpolate in x direction (y+1) - double fin = Interpolate(v1, v2, yFrac); //interpolate in y direction - - return fin; - } - - private double Interpolate(double startValue, double endValue, double interpolationFactor) - { - double inverseFactor = 1.0 - interpolationFactor; - double inverseFactorSquared = inverseFactor * inverseFactor; - double weightStart = 3.0 * inverseFactorSquared - 2.0 * inverseFactorSquared * inverseFactor; - - double factorSquared = interpolationFactor * interpolationFactor; - double weightEnd = 3.0 * factorSquared - 2.0 * (factorSquared * interpolationFactor); - - return startValue * weightStart + endValue * weightEnd; - } - - private double Noise(int x, int y) - { - int n = x + y * 57; - n = (n << 13) ^ n; - int t = (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff; - return 1.0 - (double)t * 0.93132257461547858515625e-9; - } - - private double ComputePerlinCurveY(double x) - { - // Calculate the raw point using the adjusted Y - double y = GetHeight(-ControlPoint3X + x, 0.0) + (CanvasSize - ControlPoint3Y); - - // Flipped value to align correctly with WPF's coordinate system - return CanvasSize - y; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) - { - var valuesX = new List(); - var valuesY = new List(); - - double scalingFactor = CanvasSize / initialWidth; - scale = (CanvasSize - ControlPoint1X) / (CanvasSize * 3.0); - amplitude = (CanvasSize - ControlPoint2Y) * 1.1; - baseFrequency = scale / scalingFactor; - - if (isRender) - { - for (double x = 0.0; x <= CanvasSize; x += renderIncrementX) - { - var y = ComputePerlinCurveY(x); - - if (y >= 0 && y <= CanvasSize) - { - valuesX.Add(x); - valuesY.Add(y); - } - } - - // Add the intersection points & sort the values - var intersectionValuesX = GetBoundaryIntersections(); - valuesX.AddRange(intersectionValuesX.XValues); - valuesY.AddRange(intersectionValuesX.YValues); - - var sortedPairs = valuesX.Zip(valuesY, (x, y) => new { X = x, Y = y }) - .OrderBy(pair => pair.X) - .ToList(); - valuesX = sortedPairs.Select(p => p.X).ToList(); - valuesY = sortedPairs.Select(p => p.Y).ToList(); - } - else - { - var step = CanvasSize / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - double x = 0 + step * i; - double y = ComputePerlinCurveY(x); - - valuesX.Add(x); - valuesY.Add(y); - } - } - - return (valuesX, valuesY); - } - - private (List XValues, List YValues) GetBoundaryIntersections() - { - var intersectionXPoints = new List(); - var intersectionYPoints = new List(); - - double previousY = ComputePerlinCurveY(0); - double previousX = 0; - double step = renderIncrementX; - - for (double currentX = step; currentX <= CanvasSize; currentX += step) - { - double currentY = ComputePerlinCurveY(currentX); - - // Check for intersection at Y = 0 - if ((previousY < 0 && currentY >= 0) || (previousY > 0 && currentY <= 0)) - { - double intersectX = previousX + ((0 - previousY) * (currentX - previousX) / (currentY - previousY)); - intersectionXPoints.Add(intersectX); - intersectionYPoints.Add(0); - } - - // Check for intersection at Y = CanvasSize - if ((previousY < CanvasSize && currentY >= CanvasSize) || (previousY > CanvasSize && currentY <= CanvasSize)) - { - double intersectX = previousX + ((CanvasSize - previousY) * (currentX - previousX) / (currentY - previousY)); - intersectionXPoints.Add(intersectX); - intersectionYPoints.Add(CanvasSize); - } - - previousX = currentX; - previousY = currentY; - } - - return (intersectionXPoints, intersectionYPoints); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs b/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs deleted file mode 100644 index b286ddc02ae..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/PowerCurve.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a power function curve in the CurveMapper. - /// The curve is defined by a power equation derived from a control point. - /// - public class PowerCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - - public PowerCurve(double cp1X, double cp1Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - } - - private double UpdatePowerEquation() - { - // Normalize x & y of the control point - double xControl = ControlPoint1X / CanvasSize; - double yControl = ControlPoint1Y / CanvasSize; - - if (xControl == yControl) - return 1.0; - - if (xControl <= 0.0 || yControl >= 1.0) - return double.NegativeInfinity; - - if (xControl >= 1.0 || yControl <= 0.0) - return double.PositiveInfinity; - - return Math.Log(yControl) / Math.Log(xControl); - } - - - private double ComputePowerY(double x, double powerFactor) - { - double baseX = x / CanvasSize; - double normalizedY = Math.Pow(baseX, powerFactor); - - return normalizedY * CanvasSize; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) - { - var valuesX = new List(); - var valuesY = new List(); - double powerFactor = UpdatePowerEquation(); - - if (isRender) - { - for (double x = 0.0; x <= CanvasSize; x += renderIncrementX) - { - valuesX.Add(x); - double y = ComputePowerY(x, powerFactor); - valuesY.Add(y); - } - } - else - { - double step = CanvasSize / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - double x = i * step; - valuesX.Add(x); - double y = ComputePowerY(x, powerFactor); - valuesY.Add(y); - } - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs b/src/Libraries/CoreNodes/CurveMapper/SineWave.cs deleted file mode 100644 index 83ec7bc0029..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/SineWave.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a sine wave curve in the CurveMapper. - /// The sine wave is defined by two control points and follows a trigonometric function. - /// - public class SineWave : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - - // Coefficients - private double coefA; // Amplitude - private double coefB; // 2*PI/period - private double coefC; // Phase shift - private double coefD; // Vertical shift - - public SineWave(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) - : base(canvasSize) - { - CanvasSize = canvasSize; - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - GetEquationCoefficients(); - } - - private double ConvertXToTrigo(double x) => System.Math.PI * x / CanvasSize; - private double ConvertYToTrigo(double y) => 2.0 * y / CanvasSize - 1.0; - private double ConvertTrigoToY(double unitY) => (unitY + 1.0) * CanvasSize / (2.0); - private double CosineEquation(double x) => -(coefA * System.Math.Cos(coefB * x - coefC)) + coefD; - - private void GetEquationCoefficients() - { - coefA = (ConvertYToTrigo(ControlPoint2Y) - ConvertYToTrigo(ControlPoint1Y)) / 2.0; - coefB = Math.PI / (ConvertXToTrigo(ControlPoint2X) - ConvertXToTrigo(ControlPoint1X)); - coefC = coefB * ConvertXToTrigo(ControlPoint1X); - coefD = (ConvertYToTrigo(ControlPoint1Y) + ConvertYToTrigo(ControlPoint2Y)) / 2.0; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender = false) - { - var valuesX = new List(); - var valuesY = new List(); - - if (isRender) - { - for (double x = 0.0; x < CanvasSize; x += renderIncrementX) - { - valuesX.Add(x); - double y = CosineEquation(ConvertXToTrigo(x)); - valuesY.Add(ConvertTrigoToY(y)); - } - } - else - { - double step = CanvasSize / (pointsCount - 1); - for (int i = 0; i < pointsCount; i++) - { - double x = i * step; - valuesX.Add(x); - double y = CosineEquation(ConvertXToTrigo(x)); - valuesY.Add(ConvertTrigoToY(y)); - } - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs b/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs deleted file mode 100644 index ec54d7130ca..00000000000 --- a/src/Libraries/CoreNodes/CurveMapper/SquareRootCurve.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Collections.Generic; - -namespace DSCore.CurveMapper -{ - /// - /// Represents a square root curve in the CurveMapper. - /// The curve follows a square root function and is influenced by two control points. - /// - public class SquareRootCurve : CurveBase - { - private double ControlPoint1X; - private double ControlPoint1Y; - private double ControlPoint2X; - private double ControlPoint2Y; - - public SquareRootCurve(double cp1X, double cp1Y, double cp2X, double cp2Y, double canvasSize) - : base(canvasSize) - { - ControlPoint1X = cp1X; - ControlPoint1Y = cp1Y; - ControlPoint2X = cp2X; - ControlPoint2Y = cp2Y; - } - - private double ComputeSquareRootFactor() - { - double Ox = ControlPoint1X / CanvasSize; - double Oy = 1 - (ControlPoint1Y / CanvasSize); - double Gx = ControlPoint2X / CanvasSize; - double Gy = 1 - (ControlPoint2Y / CanvasSize); - - double deltaX = Gx - Ox; - if (deltaX == 0) return double.NaN; - - double sqrtFactor = (Gy - Oy) / Math.Sqrt(Math.Abs(deltaX)); - - // If controlPoint1 is to the right of controlPoint2, flip the curve - return (Gx < Ox) ? -sqrtFactor : sqrtFactor; - } - - private double ComputeSquareRootY(double x, double sqrtFactor) - { - double Ox = ControlPoint1X / CanvasSize; - double Oy = 1 - (ControlPoint1Y / CanvasSize); - - double baseX = x / CanvasSize; - double adjustedX = baseX - Ox; - - double sqrtComponent = sqrtFactor * Math.Sqrt(Math.Abs(adjustedX)); - - // Mirror the Y-values if X is to the left of controlPoint1 - double normalizedY = Oy + (adjustedX < 0 ? -sqrtComponent : sqrtComponent); - return (1 - normalizedY) * CanvasSize; - } - - /// - /// Returns X and Y values distributed across the curve. - /// - protected override (List XValues, List YValues) GenerateCurve(int pointsCount, bool isRender) - { - var valuesX = new List(); - var valuesY = new List(); - - double sqrtFactor = ComputeSquareRootFactor(); - - // Find the first valid X-value where the curve is within the visible canvas - double startX = 0; - - for (double x = 0; x <= CanvasSize; x += renderIncrementX) - { - double y = ComputeSquareRootY(x, sqrtFactor); - if (y >= 0 && y <= CanvasSize) - { - startX = x; - break; - } - } - - if (isRender) - { - for (double x = startX; x <= CanvasSize; x += renderIncrementX) - { - double y = ComputeSquareRootY(x, sqrtFactor); - - // Only add points that are within the visible canvas - if (y >= 0 && y <= CanvasSize) - { - valuesX.Add(x); - valuesY.Add(y); - } - } - } - else - { - var step = CanvasSize / (pointsCount - 1); - - for (int i = 0; i < pointsCount; i++) - { - double x = 0 + i * step; - - valuesX.Add(x); - valuesY.Add(ComputeSquareRootY(x, sqrtFactor)); - } - } - - return (valuesX, valuesY); - } - } -} diff --git a/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml b/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml index 82cbae32340..679ef35983e 100644 --- a/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml +++ b/src/Libraries/GeometryUIWpf/Controls/ExportWithUnitsControl.xaml @@ -1,4 +1,4 @@ -_09WhkUH!7f_S180G&hB10KOCH)e_sf7kE5wUK0VHqt8AJR!u$)=sR)%-y}? zipyXl(c?AxrH+#H|Ex)oSWZyj0 zZ$~^>GFqIcK0f>6Le=b{hb0-A{l;r%U+3!#qqx|JPDnhG4z@kQMmXsZ;A;15F-w$n z4BZHlMT~=H3Tqx`KtFu%?`c0V8WgJBZWuznIj5qpf#H5^coCyO@=QONfTkSn9@K=x z$fu8`9ZXMPTc-`*o$YilLciMy2ABkNb6U=}B4=UULSoU>9|O?iwXn3M`=1xIX1%pN z&z)jilJx16;Zz`6#RGREjB|^L5@B!Naw#m(_YIgH-2TB={Ulw26gCJ7n&t`>MBbyD zYivQMDmu6Af-&Wo!!j)ci<1?=Io6aojEVgRt_dJPCVheoM()i`E3y!i#Lwb~xafx= z2Hi;pBXeJ_VO`jVcPCD?pW5OGP1#C2sp(pf#r0C;<(AJeWu>_vzh|M}Mo#-0$8Bdi zDO^Hu>gQo{TnS}5mNZEUamlc+P)0#`j#vyrNC`O8Q`tOs!4e=Z8WNmGUA z9o9r2-Ibv`$@PhcH2=rwo=vabF0Vo^=%NQct)p8a9YFmA{0Lz^x_V!|zyl2x(Fys3 zOYh&j4G&y4JfZcKPU-Ezo%RHR%fHd%HPK(63VodeQePD;RjfLCsT-VP;1B4H6+Lui z@(h1o|HUc>U-m3GeZYLpIEXLxZ=y@^|Sc+X0Ga_&E^yRcW ztb}65LE)SK#ywS!N?xJJu8QffCiX|;>W~5c{HoYl=$%L_fCUhi&vcAXfenqEIq<-$ zTYB1F8PeYqJ-D55sZR<3IZ7#2v+k?v>XwrADBhg4t-DYcZdz8)LVe~57hqx5u8|2Z zxGQtsK|*%%!b1Iq7)1Fu>cGl9Ob9Qo@>%+yQxW+mer(yR^^;5d-*K*-v4R6X|IzkK zYdzAN;(y&c>)7Gr>rW2(D-lx6|3j!PFV73|x0QlNv>H-#w)G7Snbz!+t7>?U so*G)5cU?^kll^t{j|BhQaK5$%%CB}U;#K=c*Ix^u9t=28=}8y-4|qQJvH$=8 diff --git a/src/Resources/CoreNodeModels/SmallIcons/CoreNodeModels.CurveMapper.Small.png b/src/Resources/CoreNodeModels/SmallIcons/CoreNodeModels.CurveMapper.Small.png deleted file mode 100644 index a7f4ff114fe3679dbfc41037bd4477c6514d872c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1210 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7e6l0AZa85pY67#JE_7#My5g&JNk zFq9fFFuY1&V6d9Oz#v{QXIG#N0|Vop0G|+7pn{t>Z|=GLr1(xOeONt(!L=-nzEo;)|2l?k_&~CjQ|6(xd;{PyeYo@vrFUf1oZP$~_EaK)7Yc z{#Bj$50`}qLdeaRp6|T$wC4Dqf+Hwe;A$S+x_0Hp?WPky3t@)CrGW0ab@N8Y$?upZ z-@bWc#+i?p;y}GXT?ei_!jyV?`wFrZ_itU#!Qmu`1AuYBppPUAC4`Y(IyUXN<%=fne zIqW5#zOL*qnS}Wal?afw|7d;XNt3{ zW8A@%rr;2+prN93h-bRk(SSo5*X$i-);Tx@c}c}Ko^gq^Ok1&MPt&4Fo48hSxrLgR z@~&X=)y++ljkT=}VP$J`J0@UzMs1nenKNn6#5CLtqM{;icNjJ2ZrFO6#p>d{gMy)r zlQ(y3M>Njf&3=M^-m+_2`&oI5rv@@IbL z<_Z+uU??u?KP!NV+v(Y*O+_KBJ!h;$r-oiV^Fd?fO4slMG2!-P`HFDj(jliH)Epd$~ zNl7e8wMs5Z1yT$~21cg321dGumLY~FRwfo!Mn>8O237_JPIZ6gplHa=PsvQH#I3NoH Date: Fri, 7 Mar 2025 13:09:58 -0500 Subject: [PATCH 8/9] Merging back "DYN-8444 Added 2 missing .md files" (#15912) --- ...AGD4YGKJ6XHPIKDL7GZX63CDAFMI6KUSR6XMXEBGJJOATEI5IA.md | 9 ++++----- .../CoreNodeModels.DefineData.md | 9 ++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/doc/distrib/NodeHelpFiles/en-US/RUAGD4YGKJ6XHPIKDL7GZX63CDAFMI6KUSR6XMXEBGJJOATEI5IA.md b/doc/distrib/NodeHelpFiles/en-US/RUAGD4YGKJ6XHPIKDL7GZX63CDAFMI6KUSR6XMXEBGJJOATEI5IA.md index 8a0182e58b7..3fa1b9c9bf6 100644 --- a/doc/distrib/NodeHelpFiles/en-US/RUAGD4YGKJ6XHPIKDL7GZX63CDAFMI6KUSR6XMXEBGJJOATEI5IA.md +++ b/doc/distrib/NodeHelpFiles/en-US/RUAGD4YGKJ6XHPIKDL7GZX63CDAFMI6KUSR6XMXEBGJJOATEI5IA.md @@ -1,7 +1,6 @@ - - -## BySphereBestFit - Documentation -This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.14.0.3986, Culture=neutral, PublicKeyToken=null. +## In-Depth +In the example below, a T-Spline sphere primitive surface is created using the `TSplineSurface.BySphereBestFit` node from a provided list of random points. `radiusSpans` and `heightSpans` inputs control the number of faces along the height and radial spans (or meridians). The `symmetry` input controls if the shape has initial symmetry and, finally, `inSmoothMode` defines if the preview is in smooth or box mode. -For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes +## Example File +![Example](./Autodesk.DesignScript.Geometry.TSpline.TSplineSurface.BySphereBestFit_img.jpg) diff --git a/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.DefineData.md b/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.DefineData.md index 54f2d1fcb9a..8d82769be38 100644 --- a/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.DefineData.md +++ b/test/Tools/docGeneratorTestFiles/TestMdOutput_CoreNodeModels/CoreNodeModels.DefineData.md @@ -1,5 +1,8 @@ -## Define Data - Documentation +## In Depth +In the example below, a planar T-Spline surface with extruded, subdivided and pulled vertices and faces is inspected with the `TSplineTopology.InnerVertices` node. -This documentation file is auto generated by NodeDocumentationMarkdownGenerator, Version=2.13.0.2212, Culture=neutral, PublicKeyToken=null. +The nodes `TSplineVertex.UVNFrame` and `TSplineUVNFrame.Position` are used to highlight the inner vertices in the surface. +___ +## Example File -For more information about adding documentation to nodes see https://github.com/DynamoDS/Dynamo/wiki/Create-and-Add-Custom-Documentation-to-Nodes +![TSplineTopology.InnerVertices](./Autodesk.DesignScript.Geometry.TSpline.TSplineTopology.InnerVertices_img.jpg) From 124720c6367fc7212183aacb421b02ae2ef8b284 Mon Sep 17 00:00:00 2001 From: Ashish Aggarwal Date: Fri, 7 Mar 2025 13:15:09 -0500 Subject: [PATCH 9/9] DYN-8457 Fix when inserting groups from Help doc should have correct name (#15910) --- .../DocumentationBrowserViewModel.cs | 2 +- .../DocumentationBrowserViewExtensionTests.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/DocumentationBrowserViewExtension/DocumentationBrowserViewModel.cs b/src/DocumentationBrowserViewExtension/DocumentationBrowserViewModel.cs index 7176341883b..bad9663d059 100644 --- a/src/DocumentationBrowserViewExtension/DocumentationBrowserViewModel.cs +++ b/src/DocumentationBrowserViewExtension/DocumentationBrowserViewModel.cs @@ -438,7 +438,7 @@ internal void InsertGraph() { if (GraphPath != null) { - var graphName = CurrentPackageName ?? Path.GetFileNameWithoutExtension(GraphPath); + var graphName = string.IsNullOrEmpty(CurrentPackageName) ? Path.GetFileNameWithoutExtension(GraphPath) : CurrentPackageName; raiseInsertGraph(this, new InsertDocumentationLinkEventArgs(GraphPath, graphName)); } else diff --git a/test/DynamoCoreWpf2Tests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs b/test/DynamoCoreWpf2Tests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs index 4353a0d4f04..616eb87fb78 100644 --- a/test/DynamoCoreWpf2Tests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs +++ b/test/DynamoCoreWpf2Tests/ViewExtensions/DocumentationBrowserViewExtensionTests.cs @@ -802,6 +802,13 @@ public void AddGraphInSpecificLocationToWorkspace() //Validates that we have 5 nodes the CurrentWorkspace (after the graph was added) Assert.AreEqual(ViewModel.Model.CurrentWorkspace.Nodes.Count(), 5); DispatcherUtil.DoEvents(); + + //Validates that we have 1 group added containing the inserted graph + Assert.AreEqual(ViewModel.Model.CurrentWorkspace.Annotations.Count(), 1); + + //Validates that correct group name and description was set + Assert.AreEqual(ViewModel.Model.CurrentWorkspace.Annotations.FirstOrDefault().AnnotationText, "BasicAddition"); + Assert.AreEqual(ViewModel.Model.CurrentWorkspace.Annotations.FirstOrDefault().AnnotationDescriptionText, "Inserted Dynamo graph"); } [Test]