Skip to content

Commit

Permalink
Merge pull request #3378 from dubstar-04/feature/CycleTimeEstimate
Browse files Browse the repository at this point in the history
[PATH] feature/cycle time estimate - fixes #3346
  • Loading branch information
sliptonic committed Apr 22, 2020
2 parents 028926e + 865f0cc commit 8adadcf
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 3 deletions.
62 changes: 62 additions & 0 deletions src/Mod/Path/App/Path.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/Mod/Path/App/Path.h
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/Mod/Path/App/PathPy.xml
Expand Up @@ -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>-->
Expand Down
8 changes: 8 additions & 0 deletions src/Mod/Path/App/PathPyImp.cpp
Expand Up @@ -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));
}
}

This comment has been minimized.

Copy link
@donovaly

donovaly Apr 23, 2020

Member

I get now this compilation warning:
d:\freecadgit\src\mod\path\app\pathpyimp.cpp(198): warning C4715: 'Path::PathPy::getCycleTime': not all control paths return a value


// GCode methods

PyObject* PathPy::toGCode(PyObject * args)
Expand Down
41 changes: 39 additions & 2 deletions src/Mod/Path/PathScripts/PathJob.py
Expand Up @@ -30,7 +30,7 @@
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
Expand Down Expand Up @@ -99,6 +99,8 @@ def __init__(self, obj, models, templateFile = None):
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."))
Expand All @@ -110,7 +112,7 @@ def __init__(self, obj, models, templateFile = None):
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()
Expand Down Expand Up @@ -252,6 +254,10 @@ def onDocumentRestored(self, obj):
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)
Expand Down Expand Up @@ -342,6 +348,37 @@ def __setstate__(self, state):

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
Expand Down
41 changes: 40 additions & 1 deletion src/Mod/Path/PathScripts/PathOp.py
Expand Up @@ -31,6 +31,7 @@

from PathScripts.PathUtils import waiting_effects
from PySide import QtCore
import time

# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Expand Down Expand Up @@ -122,6 +123,8 @@ def __init__(self, obj, name):
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)

Expand Down Expand Up @@ -189,7 +192,7 @@ def __init__(self, obj, name):
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

Expand Down Expand Up @@ -226,6 +229,9 @@ def onDocumentRestored(self, obj):
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)

Expand Down Expand Up @@ -521,8 +527,41 @@ def execute(self, obj):

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)
Expand Down

0 comments on commit 8adadcf

Please sign in to comment.