Merge pull request #3378 from dubstar-04/feature/CycleTimeEstimate

[PATH] feature/cycle time estimate - fixes #3346
This commit is contained in:
sliptonic
2020-04-22 18:07:52 -05:00
committed by GitHub
6 changed files with 155 additions and 3 deletions

View File

@@ -31,6 +31,7 @@
#include <Base/Reader.h>
#include <Base/Stream.h>
#include <Base/Exception.h>
#include <Base/Console.h>
// 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<Command*>::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:

View File

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

View File

@@ -78,6 +78,11 @@ deletes the command found at the given position or from the end of the path</Use
<UserDocu>returns a copy of this path</UserDocu>
</Documentation>
</Methode>
<Methode Name="getCycleTime" Const="true">
<Documentation>
<UserDocu>return the cycle time estimation for this path in s</UserDocu>
</Documentation>
</Methode>
<!--<ClassDeclarations>
bool touched;
</ClassDeclarations>-->

View File

@@ -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)

View File

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

View File

@@ -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,41 @@ 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')
## 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)