From 87ce2ec4d406bd8cf861bb6eee0795166a3891d1 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Wed, 22 Apr 2020 19:08:09 +0100 Subject: [PATCH 1/4] Add getCycleTime method to Path::ToolPath --- src/Mod/Path/App/Path.cpp | 62 ++++++++++++++++++++++++++++++++++ src/Mod/Path/App/Path.h | 1 + src/Mod/Path/App/PathPy.xml | 5 +++ src/Mod/Path/App/PathPyImp.cpp | 8 +++++ 4 files changed, 76 insertions(+) diff --git a/src/Mod/Path/App/Path.cpp b/src/Mod/Path/App/Path.cpp index c802a61768..e0451ff0a5 100644 --- a/src/Mod/Path/App/Path.cpp +++ b/src/Mod/Path/App/Path.cpp @@ -31,6 +31,7 @@ #include #include #include +#include // KDL stuff - at the moment, not used //#include "Mod/Robot/App/kdl_cp/path_line.hpp" @@ -146,6 +147,67 @@ double Toolpath::getLength() return l; } +double Toolpath::getCycleTime(double hFeed, double vFeed, double hRapid, double vRapid) +{ + // check the feedrates are set + if ((hFeed == 0) || (vFeed == 0)){ + Base::Console().Warning("Feed Rate Error: Check Tool Controllers have Feed Rates"); + return 0; + } + + if (hRapid == 0){ + hRapid = hFeed; + } + + if (vRapid == 0){ + vRapid = vFeed; + } + + if(vpcCommands.size()==0) + return 0; + double l = 0; + double time = 0; + bool verticalMove = false; + Vector3d last(0,0,0); + Vector3d next; + for(std::vector::const_iterator it = vpcCommands.begin();it!=vpcCommands.end();++it) { + std::string name = (*it)->Name; + float feedrate = (*it)->getParam("F"); + + l = 0; + verticalMove = false; + feedrate = hFeed; + next = (*it)->getPlacement(last).getPosition(); + + if (last.z != next.z){ + verticalMove = true; + feedrate = vFeed; + } + + if ((name == "G0") || (name == "G00")){ + // Rapid Move + l += (next - last).Length(); + feedrate = hRapid; + if(verticalMove){ + feedrate = vRapid; + } + }else if ((name == "G1") || (name == "G01")) { + // Feed Move + l += (next - last).Length(); + }else if ((name == "G2") || (name == "G02") || (name == "G3") || (name == "G03") ) { + // Arc Move + Vector3d center = (*it)->getCenter(); + double radius = (last - center).Length(); + double angle = (next - center).GetAngle(last - center); + l += angle * radius; + } + + time += l / feedrate; + last = next; + } + return time; +} + class BoundBoxSegmentVisitor : public PathSegmentVisitor { public: diff --git a/src/Mod/Path/App/Path.h b/src/Mod/Path/App/Path.h index 24e1023312..9ad4381f7b 100644 --- a/src/Mod/Path/App/Path.h +++ b/src/Mod/Path/App/Path.h @@ -60,6 +60,7 @@ namespace Path void insertCommand(const Command &Cmd, int); // inserts a command void deleteCommand(int); // deletes a command double getLength(void); // return the Length (mm) of the Path + double getCycleTime(double, double, double, double); // return the Cycle Time (s) of the Path void recalculate(void); // recalculates the points void setFromGCode(const std::string); // sets the path from the contents of the given GCode string std::string toGCode(void) const; // gets a gcode string representation from the Path diff --git a/src/Mod/Path/App/PathPy.xml b/src/Mod/Path/App/PathPy.xml index 4440ea2379..94a56ec172 100644 --- a/src/Mod/Path/App/PathPy.xml +++ b/src/Mod/Path/App/PathPy.xml @@ -78,6 +78,11 @@ deletes the command found at the given position or from the end of the pathreturns a copy of this path + + + return the cycle time estimation for this path in s + + diff --git a/src/Mod/Path/App/PathPyImp.cpp b/src/Mod/Path/App/PathPyImp.cpp index 20bac63a8f..f61c5568e6 100644 --- a/src/Mod/Path/App/PathPyImp.cpp +++ b/src/Mod/Path/App/PathPyImp.cpp @@ -189,6 +189,14 @@ PyObject* PathPy::deleteCommand(PyObject * args) Py_Error(Base::BaseExceptionFreeCADError, "Wrong parameters - expected an integer (optional)"); } +PyObject* PathPy::getCycleTime(PyObject * args) +{ + double hFeed, vFeed, hRapid, vRapid; + if (PyArg_ParseTuple(args, "dddd", &hFeed, &vFeed, &hRapid, &vRapid)){ + return PyFloat_FromDouble(getToolpathPtr()->getCycleTime(hFeed, vFeed, hRapid, vRapid)); + } +} + // GCode methods PyObject* PathPy::toGCode(PyObject * args) From 37ed81d07060984f2f00d3059ccca1c3dbef0a89 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Wed, 22 Apr 2020 19:10:18 +0100 Subject: [PATCH 2/4] Add cycleTime Attribute to PathOp --- src/Mod/Path/PathScripts/PathOp.py | 42 +++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index fab24c28c2..c6c1f82b8b 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -31,6 +31,7 @@ import PathScripts.PathUtils as PathUtils from PathScripts.PathUtils import waiting_effects from PySide import QtCore +import time # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -122,6 +123,8 @@ class ObjectOp(object): obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code")) obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "An optional comment for this Operation")) obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "User Assigned Label")) + obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) + obj.setEditorMode('CycleTime', 1) # read-only features = self.opFeatures(obj) @@ -189,7 +192,7 @@ class ObjectOp(object): def setEditorModes(self, obj, features): '''Editor modes are not preserved during document store/restore, set editor modes for all properties''' - for op in ['OpStartDepth', 'OpFinalDepth', 'OpToolDiameter']: + for op in ['OpStartDepth', 'OpFinalDepth', 'OpToolDiameter', 'CycleTime']: if hasattr(obj, op): obj.setEditorMode(op, 1) # read-only @@ -226,6 +229,9 @@ class ObjectOp(object): obj.addProperty("App::PropertyEnumeration", "EnableRotation", "Rotation", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable rotation to gain access to pockets/areas not normal to Z axis.")) obj.EnableRotation = ['Off', 'A(x)', 'B(y)', 'A & B'] + if not hasattr(obj, 'CycleTime'): + obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) + self.setEditorModes(obj, features) self.opOnDocumentRestored(obj) @@ -521,8 +527,42 @@ class ObjectOp(object): path = Path.Path(self.commandlist) obj.Path = path + obj.CycleTime = self.getCycleTimeEstimate(obj) + self.job.Proxy.getCycleTime() return result + def getCycleTimeEstimate(self, obj): + + tc = obj.ToolController + + if tc is None or tc.ToolNumber == 0: + FreeCAD.Console.PrintError("No Tool Controller is selected. Tool feed rates required to calculate the cycle time.\n") + return translate('PathGui', 'Tool Error') + + hFeedrate = tc.HorizFeed.Value + vFeedrate = tc.VertFeed.Value + hRapidrate = tc.HorizRapid.Value + vRapidrate = tc.VertRapid.Value + + if hFeedrate == 0 or vFeedrate == 0: + FreeCAD.Console.PrintError("Tool Controller requires feed rates. Tool feed rates required to calculate the cycle time.\n") + return translate('PathGui', 'Feedrate Error') + + if hRapidrate == 0 or vRapidrate == 0: + FreeCAD.Console.PrintWarning("Add Tool Controller Rapid Speeds on the SetupSheet for more accurate cycle times.\n") + + ## get the cycle time in seconds + seconds = obj.Path.getCycleTime(hFeedrate, vFeedrate, hRapidrate, vRapidrate) + + if not seconds: + return translate('PathGui', 'Cycletime Error') + + print('cycleTime:', seconds) + ## convert the cycle time to a HH:MM:SS format + cycleTime = time.strftime("%H:%M:%S", time.gmtime(seconds)) + + return cycleTime + def addBase(self, obj, base, sub): PathLog.track(obj, base, sub) base = PathUtil.getPublicObject(base) From 638de03c1fea5cbda3090bc9a1a15c0ccb89d721 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Wed, 22 Apr 2020 19:12:14 +0100 Subject: [PATCH 3/4] Add CycleTime Attribute to PathJob --- src/Mod/Path/PathScripts/PathJob.py | 41 +++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 2225c2fa9f..463a36deab 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -30,7 +30,7 @@ import PathScripts.PathSetupSheet as PathSetupSheet import PathScripts.PathStock as PathStock import PathScripts.PathToolController as PathToolController import PathScripts.PathUtil as PathUtil -import json +import json, time # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -99,6 +99,8 @@ class ObjectJob: obj.addProperty("App::PropertyString", "PostProcessorArgs", "Output", QtCore.QT_TRANSLATE_NOOP("PathJob", "Arguments for the Post Processor (specific to the script)")) obj.addProperty("App::PropertyString", "Description", "Path", QtCore.QT_TRANSLATE_NOOP("PathJob","An optional description for this job")) + obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) + obj.setEditorMode('CycleTime', 1) # read-only obj.addProperty("App::PropertyDistance", "GeometryTolerance", "Geometry", QtCore.QT_TRANSLATE_NOOP("PathJob", "For computing Paths; smaller increases accuracy, but slows down computation")) obj.addProperty("App::PropertyLink", "Stock", "Base", QtCore.QT_TRANSLATE_NOOP("PathJob", "Solid object to be used as stock.")) @@ -110,7 +112,7 @@ class ObjectJob: obj.addProperty("App::PropertyStringList", "Fixtures", "WCS", QtCore.QT_TRANSLATE_NOOP("PathJob", "The Work Coordinate Systems for the Job")) obj.OrderOutputBy = ['Fixture', 'Tool', 'Operation'] obj.Fixtures = ['G54'] - + obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile() #obj.setEditorMode("PostProcessorOutputFile", 0) # set to default mode obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors() @@ -252,6 +254,10 @@ class ObjectJob: obj.setEditorMode('Operations', 2) # hide obj.setEditorMode('Placement', 2) + if not hasattr(obj, 'CycleTime'): + obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) + obj.setEditorMode('CycleTime', 1) # read-only + def onChanged(self, obj, prop): if prop == "PostProcessor" and obj.PostProcessor: processor = PostProcessor.load(obj.PostProcessor) @@ -342,6 +348,37 @@ class ObjectJob: def execute(self, obj): obj.Path = obj.Operations.Path + self.getCycleTime() + + def getCycleTime(self): + seconds = 0 + for op in self.obj.Operations.Group: + + # Skip inactive operations + if PathUtil.opProperty(op, 'Active') is False: + continue + + # Skip operations that don't have a cycletime attribute + if not PathUtil.opProperty(op, 'CycleTime') or PathUtil.opProperty(op, 'CycleTime') is None: + continue + + formattedCycleTime = PathUtil.opProperty(op, 'CycleTime') + try: + ## convert the formatted time from HH:MM:SS to just seconds + opCycleTime = sum(x * int(t) for x, t in zip([1, 60, 3600], reversed(formattedCycleTime.split(":")))) + except: + FreeCAD.Console.PrintWarning("Error converting the operations cycle time. Job Cycle time may be innacturate\n") + continue + + if opCycleTime > 0: + seconds = seconds + opCycleTime + + if seconds > 0: + cycleTimeString = time.strftime("%H:%M:%S", time.gmtime(seconds)) + else: + cycleTimeString = translate('PathGui', 'Cycle Time Error') + + self.obj.CycleTime = cycleTimeString def addOperation(self, op, before = None, removeBefore = False): group = self.obj.Operations.Group From 233d760b6b3756b740895e843bcbd1859202c859 Mon Sep 17 00:00:00 2001 From: Daniel Wood Date: Wed, 22 Apr 2020 19:36:24 +0100 Subject: [PATCH 4/4] remove debug print --- src/Mod/Path/PathScripts/PathOp.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Mod/Path/PathScripts/PathOp.py b/src/Mod/Path/PathScripts/PathOp.py index c6c1f82b8b..97a8f0fa19 100644 --- a/src/Mod/Path/PathScripts/PathOp.py +++ b/src/Mod/Path/PathScripts/PathOp.py @@ -231,7 +231,7 @@ class ObjectOp(object): if not hasattr(obj, 'CycleTime'): obj.addProperty("App::PropertyString", "CycleTime", "Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Operations Cycle Time Estimation")) - + self.setEditorModes(obj, features) self.opOnDocumentRestored(obj) @@ -556,8 +556,7 @@ class ObjectOp(object): if not seconds: return translate('PathGui', 'Cycletime Error') - - print('cycleTime:', seconds) + ## convert the cycle time to a HH:MM:SS format cycleTime = time.strftime("%H:%M:%S", time.gmtime(seconds))