diff --git a/CNC Controls Lathe/CNC Controls Lathe/BaseViewModel.cs b/CNC Controls Lathe/CNC Controls Lathe/BaseViewModel.cs
index 9f8f659b..a07f788e 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/BaseViewModel.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/BaseViewModel.cs
@@ -1,13 +1,13 @@
/*
* BaseViewModel.cs - part of CNC Controls Lathe library
*
- * v0.03 / 2020-01-28 / Io Engineering (Terje Io)
+ * v0.43 / 2023-06-03 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2020, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -41,7 +41,6 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
using System.Linq;
using CNC.Core;
using System;
-using System.Globalization;
namespace CNC.Controls.Lathe
{
@@ -354,5 +353,72 @@ public double PassdepthLastPass
}
}
}
+
+ public bool Validate(ref double spindleSpeed)
+ {
+ bool ok = true;
+
+ ClearErrors();
+
+ if (FeedRate > config.ZMaxFeedRate)
+ {
+ ok = false;
+ SetError(nameof(FeedRate), "Feed rate > max allowed.");
+ }
+
+ if (FeedRate == 0.0d)
+ {
+ ok = false;
+ SetError(nameof(FeedRate), "Feed rate is required.");
+ }
+
+ if (FeedRateLastPass == 0.0d)
+ {
+ ok = false;
+ SetError(nameof(FeedRateLastPass), "Feed rate is required.");
+ }
+
+ if (FeedRateLastPass > config.ZMaxFeedRate)
+ {
+ ok = false;
+ SetError(nameof(FeedRateLastPass), "Feed rate > max allowed.");
+ }
+
+ if (spindleSpeed == 0.0d)
+ {
+ ok = false;
+ SetError(nameof(CssSpeed), IsCssEnabled ? "Cutting speed is required." : "Spindle RPM is required");
+ }
+ else
+ {
+ if (IsCssEnabled)
+ {
+ spindleSpeed = Math.Round(spindleSpeed / (Math.PI * XStart * UnitFactor) * (IsMetric ? 1000.0d : 12.0d * 25.4d), 0);
+ if (config.CSSMaxRPM > 0.0d)
+ spindleSpeed = Math.Min(spindleSpeed, config.CSSMaxRPM);
+ }
+
+ if (spindleSpeed > (IsCssEnabled && config.CSSMaxRPM > 0.0d ? config.CSSMaxRPM : config.RpmMax))
+ {
+ ok = false;
+ SetError(nameof(CssSpeed), IsCssEnabled ? "Cutting speed is too high for spindle." : "Spindle RPM > max allowed.");
+ }
+ }
+
+ if (spindleSpeed < config.RpmMin)
+ {
+ ok = false;
+ SetError(nameof(CssSpeed), IsCssEnabled ? "Cutting speed is too low for spindle." : "Spindle RPM < min allowed.");
+ }
+
+ if (PassdepthLastPass > Passdepth)
+ {
+ ok = false;
+ SetError(nameof(Passdepth), "Last pass cut depth must be smaller than cut depth.");
+ SetError(nameof(PassdepthLastPass), "Last pass cut depth must be smaller than cut depth.");
+ }
+
+ return ok;
+ }
}
}
diff --git a/CNC Controls Lathe/CNC Controls Lathe/CNC Controls Lathe.csproj b/CNC Controls Lathe/CNC Controls Lathe/CNC Controls Lathe.csproj
index 714529b8..c311d458 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/CNC Controls Lathe.csproj
+++ b/CNC Controls Lathe/CNC Controls Lathe/CNC Controls Lathe.csproj
@@ -60,6 +60,7 @@
CssControl.xaml
+
FacingWizard.xaml
diff --git a/CNC Controls Lathe/CNC Controls Lathe/FacingLogic.cs b/CNC Controls Lathe/CNC Controls Lathe/FacingLogic.cs
new file mode 100644
index 00000000..043715c6
--- /dev/null
+++ b/CNC Controls Lathe/CNC Controls Lathe/FacingLogic.cs
@@ -0,0 +1,204 @@
+/*
+ * FacingLogic.cs - part of CNC Controls Lathe library
+ *
+ * v0.43 / 2023-06-06 / Io Engineering (Terje Io)
+ *
+ */
+
+/*
+
+Copyright (c) 2023, Io Engineering (Terje Io)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+· Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+· Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+· Neither the name of the copyright holder nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+using System;
+using CNC.GCode;
+
+namespace CNC.Controls.Lathe
+{
+ class FacingLogic
+ {
+ private double last_rpm = 0d, last_css = 0d;
+ private BaseViewModel model;
+
+ public FacingLogic()
+ {
+ model = new BaseViewModel("Facing");
+ model.PropertyChanged += Model_PropertyChanged;
+ SetDefaults();
+ }
+
+ private void Model_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(model.Profile):
+ SetDefaults();
+ break;
+
+ case nameof(model.IsCssEnabled):
+
+ //if (css.IsChecked == true)
+ // last_rpm = css.Value;
+ //else
+ // last_css = css.Value;
+
+ //css.Value = css.IsChecked == true ? (int)last_css : (int)last_rpm;
+ break;
+ }
+ }
+
+ public BaseViewModel Model { get { return model; } }
+
+ private void SetDefaults()
+ {
+ if (model.Profile != null && model.config.IsLoaded)
+ {
+ last_css = model.config.CSS && model.config.RPM != 0.0d ? model.config.RPM / (model.IsMetric ? 1d : model.UnitFactor * 0.12d) : 0.0d;
+ last_rpm = model.config.CSS || model.config.RPM == 0.0d ? 0.0d : model.config.RPM;
+
+ model.ZClearance = model.config.ZClearance / model.UnitFactor;
+ model.XClearance = model.config.XClearance / model.UnitFactor;
+ model.Passdepth = model.config.PassDepthFirst / model.UnitFactor;
+ model.PassdepthLastPass = model.config.PassDepthLast / model.UnitFactor;
+ model.FeedRate = model.config.Feedrate / model.UnitFactor;
+ model.FeedRateLastPass = model.config.FeedrateLast / model.UnitFactor;
+
+ model.IsCssEnabled = model.config.CSS;
+ model.CssSpeed = (uint)(model.IsCssEnabled ? last_css : last_rpm);
+ }
+ }
+
+ public void Calculate()
+ {
+ double speed = model.CssSpeed;
+
+ if (!model.Validate(ref speed))
+ return;
+
+ if (model.ZStart < model.ZTarget)
+ {
+ model.SetError(nameof(model.ZStart), "Start must be greater than target.");
+ model.SetError(nameof(model.ZTarget), "Start must be greater than target.");
+ return;
+ }
+
+ double passdepth = model.Passdepth;
+ double passdepth_last = model.PassdepthLastPass;
+ double zstart = model.ZStart;
+ double ztarget = model.ZTarget;
+ double xtarget = model.XTarget;
+ double xclearance = xtarget + model.XClearance;
+ double diameter = model.XStart;
+
+ if (Math.Abs(diameter - xtarget) == 0.0d) // nothing to do...
+ return;
+
+ if (model.config.xmode == LatheMode.Radius)
+ {
+ xtarget /= 2.0d;
+ xclearance /= 2.0d;
+ diameter /= 2.0d;
+ }
+
+ double xstart = diameter;
+ double xclear = xstart + xclearance;
+ double zclearance = model.ZClearance;
+
+ PassCalc cut = new PassCalc(zstart - ztarget, passdepth, passdepth_last, model.Precision);
+
+ if (cut.Passes < 1)
+ {
+ model.SetError(nameof(model.XStart), "Starting diameter must be larger than target.");
+ return;
+ }
+
+ uint pass = 1;
+ string cssCmd = model.IsCssEnabled ? string.Format(model.config.CSSMaxRPM > 0.0d ? "G96S{0}D{1}" : "G96S{0}",
+ model.CssSpeed, model.config.CSSMaxRPM) : "";
+
+ model.gCode.Clear();
+ model.gCode.Add(string.Format("G18 G{0} G{1}", model.config.xmode == LatheMode.Radius ? "8" : "7", model.IsMetric ? "21" : "20"));
+ if (!model.IsCssEnabled)
+ model.gCode.Add("G97");
+ model.gCode.Add(string.Format("M3S{0} G4P1", speed.ToString()));
+ model.gCode.Add(string.Format("G0 X{0}", model.FormatValue(xclear)));
+ model.gCode.Add(string.Format("G0 Z{0}", model.FormatValue(zstart + zclearance)));
+
+ do
+ {
+ ztarget = cut.GetPassTarget(pass, zstart, true);
+ double feedrate = cut.IsLastPass ? model.FeedRateLastPass : model.FeedRate;
+
+ model.gCode.Add(string.Format("(Pass: {0}, DOC: {1} {2})", pass, ztarget, cut.DOC));
+
+ if (model.IsCssEnabled)
+ model.gCode.Add(cssCmd);
+ model.gCode.Add(string.Format("G1 Z{0} F{1}", model.FormatValue(ztarget), model.FormatValue(feedrate)));
+ model.gCode.Add(string.Format("G1 X{0}", model.FormatValue(xtarget)));
+ if (!cut.IsLastPass || !(model.IsSpringPassesEnabled && model.SpringPasses > 0))
+ {
+ model.gCode.Add(string.Format("G0 Z{0}", model.FormatValue(ztarget + zclearance)));
+ if (model.IsCssEnabled)
+ model.gCode.Add(string.Format("G97S{0}", speed.ToString()));
+ model.gCode.Add(string.Format("G0 X{0}", model.FormatValue(xclear)));
+ }
+
+ } while (++pass <= cut.Passes);
+
+ if(model.IsSpringPassesEnabled && model.SpringPasses > 0)
+ {
+ model.gCode.Add(string.Format("(Pass: {0}, springpass)", pass));
+ model.gCode.Add(string.Format("G1 X{0}", model.FormatValue(xclear)));
+ while (model.SpringPasses > 1)
+ {
+ model.SpringPasses--;
+ model.gCode.Add(string.Format("(Pass: {0}, springpass)", ++pass));
+ model.gCode.Add(string.Format("G0 Z{0}", model.FormatValue(ztarget + zclearance)));
+ model.gCode.Add(string.Format("G0 X{0}", model.FormatValue(xtarget)));
+ model.gCode.Add(string.Format("G1 Z{0}", model.FormatValue(ztarget)));
+ model.gCode.Add(string.Format("G1 X{0}", model.FormatValue(xclear)));
+ }
+ }
+
+ GCode.File.AddBlock("Wizard: Facing", Core.Action.New);
+ GCode.File.AddBlock(string.Format("({0}, Start: {1}, Target: {2}, Length{3})",
+ "Facing",
+ model.FormatValue(zstart), model.FormatValue(ztarget), model.FormatValue(0d)), Core.Action.Add);
+ GCode.File.AddBlock(string.Format("(Passdepth: {0}, Feedrate: {1}, {2}: {3})",
+ model.FormatValue(passdepth), model.FormatValue(model.FeedRate),
+ (model.IsCssEnabled ? "CSS" : "RPM"), model.FormatValue((double)model.CssSpeed)), Core.Action.Add);
+
+ foreach (string s in model.gCode)
+ GCode.File.AddBlock(s, Core.Action.Add);
+
+ GCode.File.AddBlock("M30", Core.Action.End);
+ }
+ }
+}
diff --git a/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml b/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml
index 0d67f5b9..e10e9edb 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml
+++ b/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml
@@ -1,12 +1,108 @@
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml.cs b/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml.cs
index 797bfb48..743aa420 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/FacingWizard.xaml.cs
@@ -1,28 +1,132 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
+/*
+ * FacingWizard.xaml.cs - part of CNC Controls library
+ *
+ * v0.43 / 2023-06-03 / Io Engineering (Terje Io)
+ *
+ */
+
+/*
+
+Copyright (c) 2023, Io Engineering (Terje Io)
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+· Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+· Redistributions in binary form must reproduce the above copyright notice, this
+list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+· Neither the name of the copyright holder nor the names of its contributors may
+be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+using System;
+using System.Collections.ObjectModel;
using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using System.Windows.Shapes;
+using CNC.Core;
namespace CNC.Controls.Lathe
{
///
/// Interaction logic for FacingView.xaml
///
- public partial class FacingView : UserControl
+ public partial class FacingWizard : UserControl, ICNCView
{
- public FacingView()
+ private bool initOk = false, resetProfileBindings = true;
+ private double last_rpm = 0d, last_css = 0d;
+ private BaseViewModel model;
+ private FacingLogic logic = new FacingLogic();
+
+ public FacingWizard()
{
InitializeComponent();
+
+ DataContext = model = logic.Model;
+ }
+
+ void FacingWizard_Load(object sender, EventArgs e)
+ {
+ //if (this.DesignMode)
+ // return;
+
+ //error = new ErrorProvider(this);
+
+ //UIUtils.SetMask(txtTaper, "#0.0##");
+ //UIUtils.SetMask(txtSpindleRPM, "###0");
+ //// cvFeedRate.Format = "###0";
+
+ //UIUtils.GroupBoxCaptionBold(groupBox1);
+ //UIUtils.GroupBoxCaptionBold(groupBox2);
+ }
+
+ public ObservableCollection gCode { get; private set; }
+
+ #region Methods and properties required by CNCView interface
+
+ public ViewType ViewType { get { return ViewType.Facing; } }
+ public bool CanEnable { get { return true; } }
+
+ public void Activate(bool activate, ViewType chgMode)
+ {
+ if (activate && GrblSettings.IsLoaded)
+ {
+ if (!initOk)
+ {
+ initOk = true;
+ if (config == null)
+ {
+ // cbxProfile.BindOptions(config, mode);
+ }
+ model.config.Update();
+ Converters.IsMetric = model.IsMetric = GrblParserState.IsMetric;
+ model.XStart = model.IsMetric ? 10.0d : 0.5d;
+ }
+ else
+ {
+ model.gCode.Clear();
+ model.PassData.Clear();
+ }
+ }
+ }
+
+ public void CloseFile()
+ {
+ }
+
+ public void Setup(UIViewModel model, AppConfig profile)
+ {
+ this.model.wz.ApplySettings(profile.Lathe);
+ if (!model.IsConfigControlInstantiated())
+ model.ConfigControls.Add(new ConfigControl());
+ }
+
+ #endregion
+ public void InitUI()
+ {
+ }
+
+ public WizardConfig config { get; private set; }
+
+ private void btnCalculate_Click(object sender, System.Windows.RoutedEventArgs e)
+ {
+ logic.Calculate();
}
}
}
diff --git a/CNC Controls Lathe/CNC Controls Lathe/PartingLogic.cs b/CNC Controls Lathe/CNC Controls Lathe/PartingLogic.cs
index 467ee183..cac83378 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/PartingLogic.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/PartingLogic.cs
@@ -1,13 +1,13 @@
/*
* PartingLogic.cs - part of CNC Controls Lathe library
*
- * v0.19 / 2020-05-21 / Io Engineering (Terje Io)
+ * v0.43 / 2023-06-03 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2020, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -96,66 +96,10 @@ private void SetDefaults()
public void Calculate()
{
- model.ClearErrors();
-
double speed = model.CssSpeed;
- bool css = model.IsCssEnabled;
-
- if (model.FeedRate > model.config.ZMaxFeedRate)
- {
- model.SetError(nameof(model.FeedRate), "Feed rate > max allowed.");
- return;
- }
- if (model.FeedRate == 0.0d)
- {
- model.SetError(nameof(model.FeedRate), "Feed rate is required.");
+ if (!model.Validate(ref speed))
return;
- }
-
- if (speed == 0.0d)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM is required.");
- return;
- }
-
- if (css)
- {
- speed = Math.Round(speed / (Math.PI * model.XStart * model.UnitFactor) * (model.IsMetric ? 1000.0d : 12.0d * 25.4d), 0);
- if (model.config.CSSMaxRPM > 0.0d)
- speed = Math.Min(speed, model.config.CSSMaxRPM);
- }
-
- if (speed > model.config.RpmMax && model.config.CSSMaxRPM == 0.0d)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM > max allowed.");
- return;
- }
-
- if (speed < model.config.RpmMin)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM < min allowed.");
- return;
- }
-
- if (css)
- {
- speed = Math.Round(speed / (Math.PI * model.XStart * model.UnitFactor) * (model.IsMetric ? 1000.0d : 12.0d * 25.4d), 0);
- if (model.config.CSSMaxRPM > 0.0d)
- speed = Math.Min(speed, model.config.CSSMaxRPM);
- }
-
- if (speed > model.config.RpmMax && model.config.CSSMaxRPM == 0.0d)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM > max allowed.");
- return;
- }
-
- if (speed < model.config.RpmMin)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM < min allowed.");
- return;
- }
double passdepth = model.Passdepth;
double passdepth_last = model.PassdepthLastPass;
@@ -172,13 +116,6 @@ public void Calculate()
//if (xtarget == 0.0d) // nothing to do...
// return;
- if (passdepth_last > passdepth)
- {
- model.SetError(nameof(model.Passdepth), "Last pass cut depth must be smaller than cut depth.");
- model.SetError(nameof(model.PassdepthLastPass), "Last pass cut depth must be smaller than cut depth.");
- return;
- }
-
if (model.config.xmode == LatheMode.Radius)
{
xtarget /= 2.0d;
@@ -191,7 +128,6 @@ public void Calculate()
// xclearance *= 2.0d;
//}
- double angle = 0.0d;
double xstart = diameter;
double xclear = xstart;
@@ -213,8 +149,8 @@ public void Calculate()
model.gCode.Add(string.Format("M3S{0} G4P1", speed.ToString()));
model.gCode.Add(string.Format("G0 X{0}", model.FormatValue(diameter + xclearance)));
model.gCode.Add(string.Format("G0 Z{0}", model.FormatValue(zstart + model.config.ZClearance / model.UnitFactor)));
- model.gCode.Add(css ? string.Format(model.config.CSSMaxRPM > 0.0d ? "G96S{0}D{1}" : "G96S{0}",
- model.CssSpeed, model.config.CSSMaxRPM) : "G97");
+ model.gCode.Add(model.IsCssEnabled ? string.Format(model.config.CSSMaxRPM > 0.0d ? "G96S{0}D{1}" : "G96S{0}",
+ model.CssSpeed, model.config.CSSMaxRPM) : "G97");
do
{
@@ -242,7 +178,7 @@ public void Calculate()
model.FormatValue(zstart), model.FormatValue(ztarget), model.FormatValue(0d)), Core.Action.Add);
GCode.File.AddBlock(string.Format("(Passdepth: {0}, Feedrate: {1}, {2}: {3})",
model.FormatValue(passdepth), model.FormatValue(model.FeedRate),
- (css ? "CSS" : "RPM"), model.FormatValue((double)model.CssSpeed)), Core.Action.Add);
+ (model.IsCssEnabled ? "CSS" : "RPM"), model.FormatValue((double)model.CssSpeed)), Core.Action.Add);
foreach (string s in model.gCode)
GCode.File.AddBlock(s, Core.Action.Add);
diff --git a/CNC Controls Lathe/CNC Controls Lathe/PartingWizard.xaml b/CNC Controls Lathe/CNC Controls Lathe/PartingWizard.xaml
index 8f778f25..7ec8ca81 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/PartingWizard.xaml
+++ b/CNC Controls Lathe/CNC Controls Lathe/PartingWizard.xaml
@@ -10,9 +10,36 @@
d:DesignHeight="515" d:DesignWidth="875">
+
+
@@ -30,17 +57,17 @@
-
-
+
+
-
-
+
+
-
+
@@ -55,6 +82,9 @@
+
+
+
diff --git a/CNC Controls Lathe/CNC Controls Lathe/Properties/AssemblyInfo.cs b/CNC Controls Lathe/CNC Controls Lathe/Properties/AssemblyInfo.cs
index 5c32c2d5..a4c5d28b 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/Properties/AssemblyInfo.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/Properties/AssemblyInfo.cs
@@ -12,7 +12,7 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Io Engineering")]
[assembly: AssemblyProduct("ioSender")]
-[assembly: AssemblyCopyright("Copyright © 2021 Io Engineering")]
+[assembly: AssemblyCopyright("Copyright © 2023 Io Engineering")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -51,5 +51,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.35.0")]
-[assembly: AssemblyFileVersion("2.0.35.0")]
+[assembly: AssemblyVersion("2.0.43.0")]
+[assembly: AssemblyFileVersion("2.0.43.0")]
diff --git a/CNC Controls Lathe/CNC Controls Lathe/ThreadingWizard.xaml b/CNC Controls Lathe/CNC Controls Lathe/ThreadingWizard.xaml
index 2e3c3eb7..d15b5cf9 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/ThreadingWizard.xaml
+++ b/CNC Controls Lathe/CNC Controls Lathe/ThreadingWizard.xaml
@@ -125,6 +125,9 @@
+
+
+
diff --git a/CNC Controls Lathe/CNC Controls Lathe/TurningLogic.cs b/CNC Controls Lathe/CNC Controls Lathe/TurningLogic.cs
index f0274564..68eaea10 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/TurningLogic.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/TurningLogic.cs
@@ -1,13 +1,13 @@
/*
* TurningLogic.cs - part of CNC Controls Lathe library
*
- * v0.18 / 2020-05-01 / Io Engineering (Terje Io)
+ * v0.43 / 2023-06-03 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2020, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -38,7 +38,6 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
*/
using System;
-using CNC.Core;
using CNC.GCode;
namespace CNC.Controls.Lathe
@@ -97,58 +96,13 @@ private void SetDefaults()
public void Calculate()
{
- model.ClearErrors();
-
double speed = model.CssSpeed;
- bool css = model.IsCssEnabled;
-
- if (model.FeedRate > model.config.ZMaxFeedRate)
- {
- model.SetError(nameof(model.FeedRate), "Feed rate > max allowed.");
- return;
- }
-
- if (model.FeedRate == 0.0d)
- {
- model.SetError(nameof(model.FeedRate), "Feed rate is required.");
- return;
- }
- if (speed == 0.0d)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM is required.");
+ if (!model.Validate(ref speed))
return;
- }
-
- if (css)
- {
- speed = Math.Round(speed / (Math.PI * model.XStart * model.UnitFactor) * (model.IsMetric ? 1000.0d : 12.0d * 25.4d), 0);
- if (model.config.CSSMaxRPM > 0.0d)
- speed = Math.Min(speed, model.config.CSSMaxRPM);
- }
-
- if (speed > model.config.RpmMax && model.config.CSSMaxRPM == 0.0d)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM > max allowed.");
- return;
- }
-
- if (speed < model.config.RpmMin)
- {
- model.SetError(nameof(model.RPM), "Spindle RPM < min allowed.");
- return;
- }
double passdepth = model.Passdepth;
double passdepth_last = model.PassdepthLastPass;
-
- if (passdepth_last > passdepth)
- {
- model.SetError(nameof(model.Passdepth), "Last pass cut depth must be smaller than cut depth.");
- model.SetError(nameof(model.PassdepthLastPass), "Last pass cut depth must be smaller than cut depth.");
- return;
- }
-
double zstart = model.ZStart;
double zlength = model.ZLength;
double ztarget = (zstart + zlength * model.config.ZDirection);
@@ -202,8 +156,8 @@ public void Calculate()
model.gCode.Add(string.Format("M3S{0} G4P1", speed.ToString()));
model.gCode.Add(string.Format("G0 X{0}", model.FormatValue(diameter + xclearance)));
model.gCode.Add(string.Format("G0 Z{0}", model.FormatValue(zstart + model.config.ZClearance / model.UnitFactor)));
- model.gCode.Add(css ? string.Format(model.config.CSSMaxRPM > 0.0d ? "G96S{0}D{1}" : "G96S{0}",
- model.CssSpeed, model.config.CSSMaxRPM) : "G97");
+ model.gCode.Add(model.IsCssEnabled ? string.Format(model.config.CSSMaxRPM > 0.0d ? "G96S{0}D{1}" : "G96S{0}",
+ model.CssSpeed, model.config.CSSMaxRPM) : "G97");
do
{
@@ -235,7 +189,7 @@ public void Calculate()
model.FormatValue(diameter), model.FormatValue(xtarget), model.FormatValue(zlength)), Core.Action.Add);
GCode.File.AddBlock(string.Format("(Passdepth: {0}, Feedrate: {1}, {2}: {3})",
model.FormatValue(passdepth), model.FormatValue(model.FeedRate),
- (css ? "CSS" : "RPM"), model.FormatValue((double)model.CssSpeed)), Core.Action.Add);
+ (model.IsCssEnabled ? "CSS" : "RPM"), model.FormatValue((double)model.CssSpeed)), Core.Action.Add);
foreach (string s in model.gCode)
GCode.File.AddBlock(s, Core.Action.Add);
diff --git a/CNC Controls Lathe/CNC Controls Lathe/TurningWizard.xaml b/CNC Controls Lathe/CNC Controls Lathe/TurningWizard.xaml
index 34b58618..707bcb68 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/TurningWizard.xaml
+++ b/CNC Controls Lathe/CNC Controls Lathe/TurningWizard.xaml
@@ -10,9 +10,36 @@
d:DesignHeight="515" d:DesignWidth="875">
+
+
@@ -34,17 +61,17 @@
-
-
+
+
-
-
+
+
-
+
@@ -59,6 +86,9 @@
+
+
+
diff --git a/CNC Controls Lathe/CNC Controls Lathe/WizardConfig.cs b/CNC Controls Lathe/CNC Controls Lathe/WizardConfig.cs
index 5ddb0771..5a01039c 100644
--- a/CNC Controls Lathe/CNC Controls Lathe/WizardConfig.cs
+++ b/CNC Controls Lathe/CNC Controls Lathe/WizardConfig.cs
@@ -81,7 +81,7 @@ public ProfileData Profile
public double FeedrateMax { get { return active.FeedrateMax; } }
public double RPM { get { return active.RPM; } }
public bool CSS { get { return active.CSS; } }
- public double CSSMaxRPM { get { return active.CSSMaxRPM; } }
+ public double CSSMaxRPM { get { return active.CSSMaxRPM; } } // ? GrblSettings.GetDouble(GrblSetting.RpmMax) : active.CSSMaxRPM; } }
public double RpmMin { get; private set; }
public double RpmMax { get; private set; }
diff --git a/CNC Controls Probing/CNC Controls Probing/Converters.cs b/CNC Controls Probing/CNC Controls Probing/Converters.cs
index c03e59a3..0f6352ad 100644
--- a/CNC Controls Probing/CNC Controls Probing/Converters.cs
+++ b/CNC Controls Probing/CNC Controls Probing/Converters.cs
@@ -1,13 +1,13 @@
/*
* Converters.cs - part of CNC Probing library
*
- * v0.38 / 2022-04-20 / Io Engineering (Terje Io)
+ * v0.43 / 2023-07-29 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2022, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -48,6 +48,7 @@ public static class Converters
{
public static EnumValueToVisibleConverter EnumValueToVisibleConverter = new EnumValueToVisibleConverter();
public static OriginToBooleanConverter OriginToBooleanConverter = new OriginToBooleanConverter();
+ public static OriginToCurrentPositionConverter OriginToCurrentPositionConverter = new OriginToCurrentPositionConverter();
}
public class EnumValueToVisibleConverter : IMultiValueConverter
@@ -79,4 +80,17 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu
return (bool)value ? OriginControl.Origin.None : OriginControl.Origin.Center;
}
}
+
+ public class OriginToCurrentPositionConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return value is OriginControl.Origin && (OriginControl.Origin)value == OriginControl.Origin.CurrentPos;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return (bool)value ? OriginControl.Origin.CurrentPos : OriginControl.Origin.None;
+ }
+ }
}
diff --git a/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml b/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml
index 5f5ecd70..c35951f6 100644
--- a/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml
+++ b/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml
@@ -84,7 +84,7 @@
-
+
@@ -98,7 +98,7 @@
-
+
diff --git a/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml.cs b/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml.cs
index 058afb1f..8093195e 100644
--- a/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml.cs
+++ b/CNC Controls Probing/CNC Controls Probing/ProbingView.xaml.cs
@@ -1,7 +1,7 @@
/*
* ProbingView.xaml.cs - part of CNC Probing library
*
- * v0.42 / 2023-03-21 / Io Engineering (Terje Io)
+ * v0.43 / 2023-07-25 / Io Engineering (Terje Io)
*
*/
@@ -136,8 +136,11 @@ private bool StopProbe(Key key)
private bool StartProbe(Key key)
{
- focusedControl = Keyboard.FocusedElement;
- getView(tab.SelectedItem as TabItem)?.Start(model.PreviewEnable);
+ if (!grbl.IsJobRunning)
+ {
+ focusedControl = Keyboard.FocusedElement;
+ getView(tab.SelectedItem as TabItem)?.Start(model.PreviewEnable);
+ }
return true;
}
@@ -202,7 +205,7 @@ private void Grbl_PropertyChanged(object sender, System.ComponentModel.PropertyC
probeDisconnected = grbl.Signals.Value.HasFlag(Signals.ProbeDisconnected);
DisplayPosition(grbl);
var signals = ((GrblViewModel)sender).Signals.Value;
- if (signals.HasFlag(Signals.CycleStart) && !signals.HasFlag(Signals.Hold) && !cycleStartSignal)
+ if (!grbl.IsJobRunning && signals.HasFlag(Signals.CycleStart) && !signals.HasFlag(Signals.Hold) && !cycleStartSignal)
StartProbe(Key.R);
cycleStartSignal = signals.HasFlag(Signals.CycleStart);
break;
@@ -215,23 +218,6 @@ private void ProbingView_SizeChanged(object sender, SizeChangedEventArgs e)
showProbeProperties();
}
- private void showProbeProperties()
- {
- double height;
-
- if (probeProperties.Visibility == Visibility.Collapsed)
- {
- probeProperties.Visibility = Visibility.Hidden;
- probeProperties.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
- height = probeProperties.DesiredSize.Height;
- probeProperties.Visibility = Visibility.Collapsed;
- }
- else
- height = probeProperties.ActualHeight;
-
- probeProperties.Visibility = (dp.ActualHeight - t1.ActualHeight - Jog.ActualHeight + probeProperties.ActualHeight) > height ? Visibility.Visible : Visibility.Collapsed;
- }
-
#region Methods and properties required by CNCView interface
public ViewType ViewType { get { return ViewType.Probing; } }
@@ -435,6 +421,24 @@ private void tab_SelectionChanged(object sender, SelectionChangedEventArgs e)
}
// https://stackoverflow.com/questions/5707143/how-to-get-the-width-height-of-a-collapsed-control-in-wpf
+
+ private void showProbeProperties()
+ {
+ double height;
+
+ if (probeProperties.Visibility == Visibility.Collapsed)
+ {
+ probeProperties.Visibility = Visibility.Hidden;
+ probeProperties.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
+ height = probeProperties.DesiredSize.Height;
+ probeProperties.Visibility = Visibility.Collapsed;
+ }
+ else
+ height = probeProperties.ActualHeight;
+
+ probeProperties.Visibility = (t1.ActualHeight - (Clearances.TranslatePoint(new Point(0, Clearances.ActualHeight), dp).Y + Jog.ActualHeight + Position.ActualHeight) + probeProperties.ActualHeight) > height ? Visibility.Visible : Visibility.Collapsed;
+ }
+
private void showDRO()
{
double width;
diff --git a/CNC Controls Probing/CNC Controls Probing/ProbingViewModel.cs b/CNC Controls Probing/CNC Controls Probing/ProbingViewModel.cs
index 6f131047..736dbc04 100644
--- a/CNC Controls Probing/CNC Controls Probing/ProbingViewModel.cs
+++ b/CNC Controls Probing/CNC Controls Probing/ProbingViewModel.cs
@@ -1,7 +1,7 @@
/*
* ProbingViewModel.cs - part of CNC Probing library
*
- * v0.42 / 2023-03-22 / Io Engineering (Terje Io)
+ * v0.43 / 2023-06-30 / Io Engineering (Terje Io)
*
*/
@@ -451,6 +451,7 @@ public ProbingType ProbingType
public double ProbeFeedRate { get { return _probeFeedRate; } set { _probeFeedRate = value; OnPropertyChanged(); } }
public double ProbeDistance { get { return _probeDistance; } set { _probeDistance = value; OnPropertyChanged(); } }
public double ProbeDiameter { get { return _ProbeDiameter; } set { _ProbeDiameter = value; OnPropertyChanged(); } }
+ public double ProbeRadius { get { return _ProbeDiameter / 2d; } }
public double LatchDistance { get { return _latchDistance; } set { _latchDistance = value; OnPropertyChanged(); } }
public double LatchFeedRate { get { return _latchFeedRate; } set { _latchFeedRate = value; OnPropertyChanged(); } }
public double RapidsFeedRate { get { return _rapidsFeedRate; } set { _rapidsFeedRate = value; OnPropertyChanged(); } }
diff --git a/CNC Controls Probing/CNC Controls Probing/Properties/AssemblyInfo.cs b/CNC Controls Probing/CNC Controls Probing/Properties/AssemblyInfo.cs
index ad4976f0..3914ed92 100644
--- a/CNC Controls Probing/CNC Controls Probing/Properties/AssemblyInfo.cs
+++ b/CNC Controls Probing/CNC Controls Probing/Properties/AssemblyInfo.cs
@@ -34,5 +34,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.0.42.0")]
-[assembly: AssemblyFileVersion("2.0.42.0")]
+[assembly: AssemblyVersion("2.0.43.0")]
+[assembly: AssemblyFileVersion("2.0.43.0")]
diff --git a/CNC Controls Probing/CNC Controls Probing/Resources/Rotater.xar b/CNC Controls Probing/CNC Controls Probing/Resources/Rotater.xar
index 7dde38ca..e6f908e7 100644
Binary files a/CNC Controls Probing/CNC Controls Probing/Resources/Rotater.xar and b/CNC Controls Probing/CNC Controls Probing/Resources/Rotater.xar differ
diff --git a/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB.png b/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB.png
index 2aa20fcf..c7129290 100644
Binary files a/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB.png and b/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB.png differ
diff --git a/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB2.png b/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB2.png
new file mode 100644
index 00000000..2aa20fcf
Binary files /dev/null and b/CNC Controls Probing/CNC Controls Probing/Resources/RotaterAB2.png differ
diff --git a/CNC Controls Probing/CNC Controls Probing/Resources/rotater2.png b/CNC Controls Probing/CNC Controls Probing/Resources/rotater2.png
new file mode 100644
index 00000000..03d58eeb
Binary files /dev/null and b/CNC Controls Probing/CNC Controls Probing/Resources/rotater2.png differ
diff --git a/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml b/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml
index 948b9758..a8edbaa3 100644
--- a/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml
+++ b/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml
@@ -18,6 +18,7 @@
+
-
+
+
+
+
+
+
+
-
+
+
+
+
@@ -93,7 +103,7 @@
-
diff --git a/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml.cs b/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml.cs
index a8ee31c6..15778d08 100644
--- a/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml.cs
+++ b/CNC Controls Probing/CNC Controls Probing/RotationControl.xaml.cs
@@ -1,13 +1,13 @@
/*
* RotationControl.xaml.cs - part of CNC Probing library
*
- * v0.42 / 2023-03-22 / Io Engineering (Terje Io)
+ * v0.43 / 2023-07-29 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2021-2022, Io Engineering (Terje Io)
+Copyright (c) 2021-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -50,7 +50,11 @@ namespace CNC.Controls.Probing
///
public partial class RotationControl : UserControl, IProbeTab
{
+ private bool addAction = false;
+ private volatile bool isCancelled = false;
private double[] af = new double[3];
+ private double probedAngle = 0d;
+ private Position p1, p2;
public RotationControl()
{
@@ -67,23 +71,44 @@ public void Activate(bool activate)
if (probing.ProbeEdge == Edge.A || probing.ProbeEdge == Edge.B || probing.ProbeEdge == Edge.C || probing.ProbeEdge == Edge.D)
probing.ProbeEdge = Edge.None;
- probing.Instructions = ((string)FindResource("Instructions")).Replace("\\n", "\n");
probing.PropertyChanged += Probing_PropertyChanged;
+
+ probing.AllowMeasure = false;
+ probing.AddAction = addAction;
+ probing.Instructions = ((string)FindResource("Instructions")).Replace("\\n", "\n");
} else
probing.PropertyChanged -= Probing_PropertyChanged;
}
private void Probing_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
- if(e.PropertyName == nameof(ProbingViewModel.CameraPositions))
+ var probing = DataContext as ProbingViewModel;
+
+ switch (e.PropertyName)
{
- var probing = DataContext as ProbingViewModel;
+ case nameof(ProbingViewModel.CameraPositions):
+ if (probing.CameraPositions == 1 && probing.ProbeEdge == Edge.None)
+ probing.PreviewText += ((string)grd_action.ToolTip).Replace('.', '!');
- if (probing.CameraPositions == 1 && probing.ProbeEdge == Edge.None)
- probing.PreviewText += ((string)grd_action.ToolTip).Replace('.', '!');
+ if ((probing.CanApplyTransform = probing.CameraPositions == 2 && probing.ProbeEdge != Edge.None))
+ {
+ probedAngle = getAngle();
+ OutputAngle();
+ }
+ break;
- if ((probing.CanApplyTransform = probing.CameraPositions == 2 && probing.ProbeEdge != Edge.None))
- getAngle();
+ case nameof(ProbingViewModel.AddAction):
+ if ((addAction = probing.AddAction))
+ {
+ probing.Origin = OriginControl.Origin.None;
+ probing.ProbeEdge = Edge.AB;
+ }
+ break;
+
+ case nameof(ProbingViewModel.ProbeEdge):
+ if (probing.ProbeEdge != Edge.AB)
+ probing.AddAction = false;
+ break;
}
}
@@ -106,10 +131,13 @@ public void Start(bool preview = false)
if (!probing.Program.Init())
return;
+ probedAngle = 0d;
+ isCancelled = false;
+
if (preview)
probing.StartPosition.Zero();
- var XYClearance = probing.XYClearance + probing.ProbeDiameter / 2d;
+ var XYClearance = probing.XYClearance + probing.ProbeRadius;
probing.Program.Add(string.Format("G91F{0}", probing.ProbeFeedRate.ToInvariantString()));
@@ -125,6 +153,8 @@ public void Start(bool preview = false)
af[GrblConstants.X_AXIS] = 1.0d;
af[GrblConstants.Y_AXIS] = 1.0d;
AddEdge(probing, GrblConstants.X_AXIS, GrblConstants.Y_AXIS, XYClearance);
+ if (probing.AddAction && preview)
+ AddActionEdge(probing, GrblConstants.Y_AXIS, GrblConstants.X_AXIS, XYClearance);
break;
case Edge.CB:
@@ -176,28 +206,121 @@ private void AddEdge(ProbingViewModel probing, int offsetAxis, int clearanceAxis
probing.Program.AddRapidToMPos(probing.StartPosition, AxisFlags.XY);
}
+ private void AddActionEdge(ProbingViewModel probing, int offsetAxis, int clearanceAxis, double XYClearance)
+ {
+ AxisFlags probeAxis = GrblInfo.AxisIndexToFlag(clearanceAxis);
+ Position rapidto = new Position(probing.StartPosition);
+
+ rapidto.Values[clearanceAxis] -= XYClearance * af[clearanceAxis];
+ rapidto.Values[offsetAxis] += probing.Offset * af[offsetAxis];
+ rapidto.Z -= probing.Depth;
+
+ probing.Program.AddRapidToMPos(rapidto, AxisFlags.XY);
+ probing.Program.AddRapidToMPos(rapidto, AxisFlags.Z);
+
+ probing.Program.AddProbingAction(probeAxis, af[clearanceAxis] == -1.0d);
+
+ probing.Program.AddRapidToMPos(rapidto, probeAxis);
+ probing.Program.AddRapidToMPos(probing.StartPosition, AxisFlags.Z);
+ }
+
public void Stop()
{
+ isCancelled = true;
(DataContext as ProbingViewModel).Program.Cancel();
}
- private void addpos ()
+ private void OutputAngle ()
+ {
+ (DataContext as ProbingViewModel).Grbl.Message = string.Format((string)FindResource("ProbedAngle"), Math.Round(Math.Atan(probedAngle) * 180d / Math.PI, 3).ToInvariantString());
+ }
+
+ private void OnActionCompleted()
{
var probing = DataContext as ProbingViewModel;
- probing.Positions.Add(new Position(probing.Grbl.MachinePosition, probing.Grbl.UnitFactor));
+ if (!isCancelled && (probing.CanApplyTransform = probing.IsSuccess && probing.Positions.Count == 1))
+ {
+ Position p3 = new Position(probing.Positions[0]),
+ p4 = new Position(p3.X + probing.Offset * probedAngle, p3.Y - probing.Offset, 0d),
+ pos = new Position(probing.StartPosition);
+
+ p1.Y += probing.ProbeOffsetY + probing.ProbeRadius;
+ p2.Y += probing.ProbeOffsetY + probing.ProbeRadius;
+ p3.X += probing.ProbeOffsetX + probing.ProbeRadius;
+ p4.X += probing.ProbeOffsetX + probing.ProbeRadius;
+ var divisor = (p1.X - p2.X) * (p3.Y - p4.Y) - (p1.Y - p2.Y) * (p3.X - p4.X);
+ pos.X = ((p1.X * p2.Y - p1.Y * p2.X) * (p3.X - p4.X) - (p1.X - p2.X) * (p3.X * p4.Y - p3.Y * p4.X)) / divisor;
+ pos.Y = ((p1.X * p2.Y - p1.Y * p2.X) * (p3.Y - p4.Y) - (p1.Y - p2.Y) * (p3.X * p4.Y - p3.Y * p4.X)) / divisor;
+
+ if ((probing.CanApplyTransform = probing.GotoMachinePosition(pos, AxisFlags.XY)))
+ {
+ switch (probing.CoordinateMode)
+ {
+ case ProbingViewModel.CoordMode.G92:
+ pos.X = pos.Y = 0d;
+ probing.WaitForResponse("G92" + pos.ToString(AxisFlags.XY));
+ break;
+
+ case ProbingViewModel.CoordMode.G10:
+ probing.WaitForResponse(string.Format("G10L2P{0}{1}", probing.CoordinateSystem, pos.ToString(AxisFlags.XY)));
+ break;
+ }
+ }
+ }
}
private void OnCompleted()
{
var probing = DataContext as ProbingViewModel;
- probing.CanApplyTransform = probing.IsSuccess && probing.Positions.Count == 2;
+ if (!isCancelled && (probing.CanApplyTransform = probing.IsSuccess && probing.Positions.Count == 2))
+ {
+ probedAngle = getAngle();
+
+ if (probing.AddAction)
+ {
+ p1 = new Position(probing.Positions[0]);
+ p2 = new Position(probing.Positions[1]);
+
+ probing.Program.Clear();
+ probing.Program.Add(string.Format("G91F{0}", probing.ProbeFeedRate.ToInvariantString()));
+
+ switch (probing.ProbeEdge)
+ {
+ case Edge.AD:
+ af[GrblConstants.X_AXIS] = 1.0d;
+ af[GrblConstants.Y_AXIS] = -1.0d;
+ AddActionEdge(probing, GrblConstants.X_AXIS, GrblConstants.Y_AXIS, probing.XYClearance + probing.ProbeRadius);
+ break;
+
+ case Edge.AB:
+ af[GrblConstants.X_AXIS] = 1.0d;
+ af[GrblConstants.Y_AXIS] = 1.0d;
+ AddActionEdge(probing, GrblConstants.Y_AXIS, GrblConstants.X_AXIS, probing.XYClearance + probing.ProbeRadius);
+ break;
+
+ case Edge.CB:
+ af[GrblConstants.X_AXIS] = -1.0d;
+ af[GrblConstants.Y_AXIS] = 1.0d;
+ AddActionEdge(probing, GrblConstants.X_AXIS, GrblConstants.Y_AXIS, probing.XYClearance + probing.ProbeRadius);
+ break;
+
+ case Edge.CD:
+ af[GrblConstants.X_AXIS] = -1.0d;
+ af[GrblConstants.Y_AXIS] = -1.0d;
+ AddActionEdge(probing, GrblConstants.Y_AXIS, GrblConstants.Z_AXIS, probing.XYClearance + probing.ProbeRadius);
+ break;
+ }
+ probing.Program.Execute(true);
+ OnActionCompleted();
+ }
+ }
probing.Program.End((string)FindResource(probing.CanApplyTransform ? "ProbingCompleted" : "ProbingFailed"));
if (probing.CanApplyTransform)
- getAngle();
+ OutputAngle();
if (!probing.Grbl.IsParserStateLive && probing.CoordinateMode == ProbingViewModel.CoordMode.G92)
probing.Grbl.ExecuteCommand(GrblConstants.CMD_GETPARSERSTATE);
@@ -290,12 +413,16 @@ private void apply_Click(object sender, RoutedEventArgs e)
offset = new RP.Math.Vector3(limits.MaxX / 2d, limits.MaxY, 0d);
break;
+ case OriginControl.Origin.CurrentPos:
+ offset = new RP.Math.Vector3(probing.Grbl.Position.X, probing.Grbl.Position.Y, 0d);
+ break;
+
default: // Origin.None -> 0,0
offset = new RP.Math.Vector3();
break;
}
- new GCodeRotate().ApplyRotation(getAngle(), new RP.Math.Vector3(), AppConfig.Settings.Base.AutoCompress);
+ new GCodeRotate().ApplyRotation(probedAngle, offset, AppConfig.Settings.Base.AutoCompress);
}
catch (Exception ex)
{
diff --git a/CNC Controls/CNC Controls/AppConfig.cs b/CNC Controls/CNC Controls/AppConfig.cs
index 13b2b2bd..1ec067be 100644
--- a/CNC Controls/CNC Controls/AppConfig.cs
+++ b/CNC Controls/CNC Controls/AppConfig.cs
@@ -1,13 +1,13 @@
/*
* AppConfig.cs - part of CNC Controls library
*
- * v0.41 / 2022-11-09 / Io Engineering (Terje Io)
+ * v0.43 / 2023-07-21 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2022, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -48,6 +48,7 @@ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
using CNC.Core;
using CNC.GCode;
using static CNC.GCode.GCodeParser;
+using System.Collections.Generic;
namespace CNC.Controls
{
@@ -137,7 +138,10 @@ public class GCodeViewerConfig : ViewModelBase
private bool _showTextOverlay = false, _renderExecuted = false, _blackBackground = false, _scaleTool = true;
Color _cutMotion = Colors.Black, _rapidMotion = Colors.LightPink, _retractMotion = Colors.Green, _toolOrigin = Colors.Green, _grid = Colors.Gray, _highlight = Colors.Crimson;
- public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; OnPropertyChanged(); } }
+ [XmlIgnore]
+ public bool IsHomingEnabled { get { return _isEnabled && GrblInfo.HomingEnabled; } set { OnPropertyChanged(); } }
+
+ public bool IsEnabled { get { return _isEnabled; } set { _isEnabled = value; OnPropertyChanged(); OnPropertyChanged(nameof(IsHomingEnabled)); } }
public int ArcResolution { get { return _arcResolution; } set { _arcResolution = value; OnPropertyChanged(); } }
public double MinDistance { get { return _minDistance; } set { _minDistance = value; OnPropertyChanged(); } }
public bool ToolAutoScale { get { return _scaleTool; } set { _scaleTool = value; OnPropertyChanged(); } }
@@ -145,7 +149,7 @@ public class GCodeViewerConfig : ViewModelBase
public bool ShowGrid { get { return _showGrid; } set { _showGrid = value; OnPropertyChanged(); } }
public bool ShowAxes { get { return _showAxes; } set { _showAxes = value; OnPropertyChanged(); } }
public bool ShowBoundingBox { get { return _showBoundingBox; } set { _showBoundingBox = value; OnPropertyChanged(); } }
- public bool ShowWorkEnvelope { get { return _showWorkEnvelope; } set { _showWorkEnvelope = value; OnPropertyChanged(); } }
+ public bool ShowWorkEnvelope { get { return _showWorkEnvelope && GrblInfo.HomingEnabled; } set { _showWorkEnvelope = value; OnPropertyChanged(); } }
public bool ShowViewCube { get { return _showViewCube; } set { _showViewCube = value; OnPropertyChanged(); } }
public bool ShowTextOverlay { get { return _showTextOverlay; } set { _showTextOverlay = value; OnPropertyChanged(); } }
public bool ShowCoordinateSystem { get { return _showCoordSystem; } set { _showCoordSystem = value; OnPropertyChanged(); } }
@@ -237,7 +241,21 @@ public class Config : ViewModelBase
private int _pollInterval = 200, /* ms*/ _maxBufferSize = 300;
private bool _useBuffering = false, _keepMdiFocus = true, _filterOkResponse = false, _saveWindowSize = false, _autoCompress = false;
private CommandIgnoreState _ignoreM6 = CommandIgnoreState.No, _ignoreM7 = CommandIgnoreState.No, _ignoreM8 = CommandIgnoreState.No, _ignoreG61G64 = CommandIgnoreState.Strip;
+ private string _theme = "default";
+
+ [XmlIgnore]
+ public Dictionary Themes { get; private set; } = new Dictionary();
+ public string Theme
+ {
+ get { return _theme; }
+ set {
+ _theme = value; //.Substring(0, 1).ToUpper() + value.Substring(1);
+ Properties.Settings.Default.ColorMode = value; // value.Substring(0, 1).ToUpper() + value.Substring(1);
+ Properties.Settings.Default.Save();
+ OnPropertyChanged();
+ }
+ }
public int PollInterval { get { return _pollInterval < 100 ? 100 : _pollInterval; } set { _pollInterval = value; OnPropertyChanged(); } }
public string PortParams { get; set; } = "COMn:115200,N,8,1";
public int ResetDelay { get; set; } = 2000;
@@ -269,21 +287,39 @@ public class Config : ViewModelBase
public ProbeConfig Probing { get; set; } = new ProbeConfig();
}
- public class AppConfig
+ public class AppConfig : ViewModelBase
{
private string configfile = null;
private bool? MPGactive = null;
+ private Config _base = null;
public string FileName { get; private set; }
private static readonly Lazy settings = new Lazy(() => new AppConfig());
private AppConfig()
- { }
+ {
+ Properties.Settings.Default.PropertyChanged += Default_PropertyChanged;
+ }
+
+ private void Default_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ OnPropertyChanged(nameof(ColorMode));
+ }
public static AppConfig Settings { get { return settings.Value; } }
- public Config Base { get; private set; } = null;
+ public static string ColorMode { get { return Properties.Settings.Default.ColorMode; } }
+
+ public Config Base
+ {
+ get { return _base; }
+ private set
+ {
+ _base = value;
+ }
+ }
+
public ObservableCollection Macros { get { return Base == null ? null : Base.Macros; } }
public JogConfig Jog { get { return Base == null ? null : Base.Jog; } }
public JogUIConfig JogUiMetric { get { return Base == null ? null : Base.JogUiMetric; } }
@@ -454,6 +490,12 @@ public int SetupAndOpen(string appname, GrblViewModel model, System.Windows.Thre
return 1;
}
+ Base.Themes.Add("Standard", LibStrings.FindResource("ThemeDefault"));
+ Base.Themes.Add("Black", LibStrings.FindResource("ThemeBlack"));
+ Base.Themes.Add("Dark", LibStrings.FindResource("ThemeDark"));
+ Base.Themes.Add("Light", LibStrings.FindResource("ThemeLight"));
+ Base.Themes.Add("White", LibStrings.FindResource("ThemeWhite"));
+
if (jogMode != -1)
Base.Jog.Mode = (JogConfig.JogMode)jogMode;
@@ -528,6 +570,34 @@ public int SetupAndOpen(string appname, GrblViewModel model, System.Windows.Thre
while (MPGactive == null)
EventUtils.DoEvents();
+ if (MPGactive == true)
+ {
+ MPGactive = null;
+
+ new Thread(() =>
+ {
+ MPGactive = WaitFor.SingleEvent(
+ cancellationToken,
+ null,
+ a => model.OnRealtimeStatusProcessed += a,
+ a => model.OnRealtimeStatusProcessed -= a,
+ 500, () => Comms.com.WriteByte(GrblConstants.CMD_STATUS_REPORT_ALL));
+ }).Start();
+
+ while (MPGactive == null)
+ EventUtils.DoEvents();
+
+ if (MPGactive == true)
+ {
+ if (model.IsMPGActive != true && model.AutoReportingEnabled)
+ {
+ MPGactive = false;
+ if (model.AutoReportInterval > 0)
+ Comms.com.WriteByte(GrblConstants.CMD_AUTO_REPORTING_TOGGLE);
+ }
+ }
+ }
+
// ...if so show dialog for wait for it to stop polling and relinquish control.
if (MPGactive == true)
{
@@ -588,14 +658,13 @@ public RestartResult Restart ()
{
if (model.GrblState.State != GrblStates.Unknown)
{
-
switch (model.GrblState.State)
{
case GrblStates.Alarm:
model.Poller.SetState(AppConfig.Settings.Base.PollInterval);
- switch (model.GrblState.Substate)
+ if (!model.SysCommandsAlwaysAvailable) switch(model.GrblState.Substate)
{
case 1: // Hard limits
if (!GrblInfo.IsLoaded)
@@ -701,6 +770,8 @@ public RestartResult Restart ()
}
} while (!exit);
}
+ if(model.GrblState.State == GrblStates.Door && model.GrblState.Substate == 0)
+ Comms.com.WriteByte(GrblConstants.CMD_RESET);
}
else
{
diff --git a/CNC Controls/CNC Controls/AppConfigView.xaml b/CNC Controls/CNC Controls/AppConfigView.xaml
index b07b4a45..14b87011 100644
--- a/CNC Controls/CNC Controls/AppConfigView.xaml
+++ b/CNC Controls/CNC Controls/AppConfigView.xaml
@@ -9,11 +9,18 @@
d:DesignHeight="550" d:DesignWidth="875">
A restart is required after changing settings!
+
-
-
+
+
diff --git a/CNC Controls/CNC Controls/BasicConfigControl.xaml b/CNC Controls/CNC Controls/BasicConfigControl.xaml
index 7176d4e3..1fa0020c 100644
--- a/CNC Controls/CNC Controls/BasicConfigControl.xaml
+++ b/CNC Controls/CNC Controls/BasicConfigControl.xaml
@@ -5,9 +5,17 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CNC.Controls"
mc:Ignorable="d"
- d:DesignHeight="185" d:DesignWidth="310">
+ d:DesignHeight="210" d:DesignWidth="310">
+
+
+
+
+
+
+
+
diff --git a/CNC Controls/CNC Controls/CNC Controls.csproj b/CNC Controls/CNC Controls/CNC Controls.csproj
index 8c3cd0e9..3f2147da 100644
--- a/CNC Controls/CNC Controls/CNC Controls.csproj
+++ b/CNC Controls/CNC Controls/CNC Controls.csproj
@@ -201,6 +201,7 @@
SDCardView.xaml
+
SignalControl.xaml
@@ -220,6 +221,7 @@
THCMonitorControl.xaml
+
ToolView.xaml
@@ -453,6 +455,30 @@
Designer
MSBuild:Compile
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -488,8 +514,9 @@
ResXFileCodeGenerator
Resources.Designer.cs
+
- SettingsSingleFileGenerator
+ PublicSettingsSingleFileGenerator
Settings.Designer.cs
@@ -521,6 +548,12 @@
+
+
+
+
+
+
diff --git a/ioSender/ioSender/App.xaml.cs b/ioSender/ioSender/App.xaml.cs
index c7cbed10..01481395 100644
--- a/ioSender/ioSender/App.xaml.cs
+++ b/ioSender/ioSender/App.xaml.cs
@@ -61,6 +61,18 @@ public App()
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
}
+ public ResourceDictionary ThemeDictionary
+ {
+ // You could probably get it via its name with some query logic as well.
+ get { return Resources.MergedDictionaries[0]; }
+ }
+
+ public void ChangeTheme(Uri uri)
+ {
+ ThemeDictionary.MergedDictionaries.Clear();
+ ThemeDictionary.MergedDictionaries.Add(new ResourceDictionary() { Source = uri });
+ }
+
protected override void OnStartup(StartupEventArgs e)
{
string[] args = Environment.GetCommandLineArgs();
diff --git a/ioSender/ioSender/JobView.xaml b/ioSender/ioSender/JobView.xaml
index 53cd6606..b01ce4b9 100644
--- a/ioSender/ioSender/JobView.xaml
+++ b/ioSender/ioSender/JobView.xaml
@@ -29,7 +29,7 @@
Controller is not responding!
-
+
diff --git a/ioSender/ioSender/JobView.xaml.cs b/ioSender/ioSender/JobView.xaml.cs
index f9f3fe99..4a76e219 100644
--- a/ioSender/ioSender/JobView.xaml.cs
+++ b/ioSender/ioSender/JobView.xaml.cs
@@ -1,13 +1,13 @@
/*
* JobView.xaml.cs - part of Grbl Code Sender
*
- * v0.39 / 2022-06-24 / Io Engineering (Terje Io)
+ * v0.43 / 2023-07-09 / Io Engineering (Terje Io)
*
*/
/*
-Copyright (c) 2019-2022, Io Engineering (Terje Io)
+Copyright (c) 2019-2023, Io Engineering (Terje Io)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
@@ -56,7 +56,7 @@ namespace GCode_Sender
public partial class JobView : UserControl, ICNCView
{
private bool? initOK = null;
- private bool isBooted = false;
+ private bool isBooted = false, isCameraClaimed = false;
private GrblViewModel model;
private IInputElement focusedControl = null;
private Controller Controller = null;
@@ -141,28 +141,28 @@ private void OnDataContextPropertyChanged(object sender, PropertyChangedEventArg
string filename = (sender as GrblViewModel).FileName;
MainWindow.ui.WindowTitle = filename;
- if(string.IsNullOrEmpty(filename))
- MainWindow.CloseFile();
- else if ((sender as GrblViewModel).IsSDCardJob)
- {
- MainWindow.EnableView(false, ViewType.GCodeViewer);
- }
- else if (filename.StartsWith("Wizard:"))
- {
- if (MainWindow.IsViewVisible(ViewType.GCodeViewer))
+ if (string.IsNullOrEmpty(filename))
+ MainWindow.CloseFile();
+ else if ((sender as GrblViewModel).IsSDCardJob)
{
- MainWindow.EnableView(true, ViewType.GCodeViewer);
- gcodeRenderer.Open(GCode.File.Tokens);
+ MainWindow.EnableView(false, ViewType.GCodeViewer);
+ }
+ else if (AppConfig.Settings.GCodeViewer.IsEnabled)
+ {
+ if (filename.StartsWith("Wizard:"))
+ {
+ //MainWindow.EnableView(true, ViewType.GCodeViewer);
+ gcodeRenderer.Open(GCode.File.Tokens);
+ }
+ else if (!string.IsNullOrEmpty(filename))
+ {
+ //MainWindow.GCodeViewer.Open(GCode.File.Tokens);
+ //MainWindow.EnableView(true, ViewType.GCodeViewer);
+ GCodeSender.EnablePolling(false);
+ gcodeRenderer.Open(GCode.File.Tokens);
+ GCodeSender.EnablePolling(true);
+ }
}
- }
- else if (!string.IsNullOrEmpty(filename) && AppConfig.Settings.GCodeViewer.IsEnabled)
- {
- //MainWindow.GCodeViewer.Open(GCode.File.Tokens);
- //MainWindow.EnableView(true, ViewType.GCodeViewer);
- GCodeSender.EnablePolling(false);
- gcodeRenderer.Open(GCode.File.Tokens);
- GCodeSender.EnablePolling(true);
- }
break;
}
}
@@ -177,7 +177,7 @@ public void Activate(bool activate, ViewType chgMode)
if (activate)
{
GCodeSender.RewindFile();
- GCodeSender.CallHandler(GCode.File.IsLoaded ? StreamingState.Idle : (model.IsSDCardJob ? StreamingState.Start : StreamingState.NoFile), false);
+ GCodeSender.CallHandler(model.IsSDCardJob ? StreamingState.Start : (GCode.File.IsLoaded ? StreamingState.Idle : StreamingState.NoFile), false);
model.ResponseLogFilterOk = AppConfig.Settings.Base.FilterOkResponse;
@@ -210,11 +210,11 @@ public void Activate(bool activate, ViewType chgMode)
}
#if ADD_CAMERA
- if (MainWindow.UIViewModel.Camera != null)
+ if (MainWindow.UIViewModel.Camera != null && !isCameraClaimed)
{
MainWindow.UIViewModel.Camera.MoveOffset += Camera_MoveOffset;
MainWindow.UIViewModel.Camera.IsVisibilityChanged += Camera_Opened;
- MainWindow.UIViewModel.Camera.IsMoveEnabled = true;
+ MainWindow.UIViewModel.Camera.IsMoveEnabled = isCameraClaimed = true;
}
#endif
//if (viewer == null)
@@ -241,7 +241,7 @@ public void Activate(bool activate, ViewType chgMode)
#if ADD_CAMERA
if (MainWindow.UIViewModel.Camera != null) {
MainWindow.UIViewModel.Camera.MoveOffset-= Camera_MoveOffset;
- MainWindow.UIViewModel.Camera.IsMoveEnabled = false;
+ MainWindow.UIViewModel.Camera.IsMoveEnabled = isCameraClaimed = false;
}
#endif
focusedControl = AppConfig.Settings.Base.KeepMdiFocus &&
@@ -383,8 +383,8 @@ bool InitSystem()
if (GrblInfo.LatheModeEnabled)
{
MainWindow.EnableView(true, ViewType.Turning);
- // MainWindow.EnableView(true, ViewType.Parting);
- // MainWindow.EnableView(true, ViewType.Facing);
+ MainWindow.EnableView(true, ViewType.Parting);
+ MainWindow.EnableView(true, ViewType.Facing);
MainWindow.EnableView(true, ViewType.G76Threading);
}
else
diff --git a/ioSender/ioSender/MainWindow.xaml b/ioSender/ioSender/MainWindow.xaml
index cd71c5dd..e7d3fae2 100644
--- a/ioSender/ioSender/MainWindow.xaml
+++ b/ioSender/ioSender/MainWindow.xaml
@@ -6,11 +6,11 @@
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:cnccore="clr-namespace:CNC.Core;assembly=CNC.Core"
xmlns:cnccontrols="clr-namespace:CNC.Controls;assembly=CNC.Controls.WPF"
- xmlns:cncviewer="clr-namespace:CNC.Controls.Viewer;assembly=CNC.Controls.Viewer"
xmlns:cnclathecontrols="clr-namespace:CNC.Controls.Lathe;assembly=CNC.Controls.Lathe"
xmlns:cncprobingcontrols="clr-namespace:CNC.Controls.Probing;assembly=CNC.Controls.Probing"
xmlns:local="clr-namespace:GCode_Sender"
mc:Ignorable="d"
+ Background="#FFE5E5E5"
Title="Sender ({0})" Height="640" Width="925" MinHeight="700" MinWidth="1020"
Loaded="Window_Load"
SizeChanged="Window_SizeChanged"
@@ -29,6 +29,7 @@
+