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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
+ 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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