diff --git a/src/Mod/Path/Gui/DrillingEdit.ui b/src/Mod/Path/Gui/DrillingEdit.ui index 21d5c0d2a..889cae3db 100644 --- a/src/Mod/Path/Gui/DrillingEdit.ui +++ b/src/Mod/Path/Gui/DrillingEdit.ui @@ -7,7 +7,7 @@ 0 0 322 - 452 + 505 @@ -34,7 +34,7 @@ 0 0 304 - 314 + 367 @@ -45,16 +45,6 @@ Base Geometry - - - - Add item selected in window. - - - add - - - @@ -71,13 +61,13 @@ - - + + - Update the path with the removed and reordered items. + Add item selected in window. - Update + add @@ -91,6 +81,29 @@ + + + + Update the path with the removed and reordered items. + + + Update + + + + + + + All objects will be processed using the same operation properties. + + + Qt::AutoText + + + true + + + diff --git a/src/Mod/Path/Gui/EngraveEdit.ui b/src/Mod/Path/Gui/EngraveEdit.ui index 635566c58..27051fc49 100644 --- a/src/Mod/Path/Gui/EngraveEdit.ui +++ b/src/Mod/Path/Gui/EngraveEdit.ui @@ -7,7 +7,7 @@ 0 0 322 - 452 + 517 @@ -34,7 +34,7 @@ 0 0 304 - 284 + 349 @@ -45,16 +45,6 @@ Base Geometry - - - - Add item selected in window. - - - add - - - @@ -71,13 +61,13 @@ - - + + - Update the path with the removed and reordered items. + Add item selected in window. - Update + add @@ -91,6 +81,29 @@ + + + + Update the path with the removed and reordered items. + + + Update + + + + + + + All objects will be processed using the same operation properties. + + + Qt::AutoText + + + true + + + @@ -99,7 +112,7 @@ 0 0 304 - 284 + 349 @@ -146,7 +159,7 @@ 0 0 304 - 284 + 349 @@ -193,7 +206,7 @@ 0 0 304 - 284 + 349 @@ -240,7 +253,7 @@ 0 0 304 - 284 + 349 diff --git a/src/Mod/Path/Gui/PocketEdit.ui b/src/Mod/Path/Gui/PocketEdit.ui index 35e634a3b..87a5c8f7b 100644 --- a/src/Mod/Path/Gui/PocketEdit.ui +++ b/src/Mod/Path/Gui/PocketEdit.ui @@ -94,7 +94,7 @@ - All objects will be profiled using the same depth and speed settings + All objects will be processed using the same operation properties. Qt::AutoText diff --git a/src/Mod/Path/Gui/ProfileEdit.ui b/src/Mod/Path/Gui/ProfileEdit.ui index ab0b740ac..94cc6c4e0 100644 --- a/src/Mod/Path/Gui/ProfileEdit.ui +++ b/src/Mod/Path/Gui/ProfileEdit.ui @@ -94,7 +94,7 @@ - All objects will be profiled using the same depth and speed settings + All objects will be processed using the same operation properties. Qt::AutoText diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index f704dabc0..4cc2c8f4f 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -64,13 +64,14 @@ class PathWorkbench ( Workbench ): from PathScripts import PathSimpleCopy from PathScripts import PathEngrave from PathScripts import PathSurface + from PathScripts import PathRemote # build commands list projcmdlist = ["Path_Project", "Path_ToolTableEdit","Path_Post","Path_Inspect"] prepcmdlist = ["Path_Plane","Path_Fixture","Path_LoadTool","Path_ToolLenOffset","Path_Comment","Path_Stop","Path_FaceProfile","Path_FacePocket","Path_Custom","Path_FromShape"] opcmdlist = ["Path_Profile","Path_Pocket","Path_Drilling","Path_Engrave","Path_Surfacing"] modcmdlist = ["Path_Copy","Path_CompoundExtended","Path_Dressup","Path_Hop","Path_Array","Path_SimpleCopy"] - + remotecmdlist = ["Path_Remote"] # Add commands to menu and toolbar def QT_TRANSLATE_NOOP(scope, text): @@ -86,6 +87,7 @@ class PathWorkbench ( Workbench ): self.appendMenu([translate("Path","Path"),translate("Path","Partial Commands")],prepcmdlist) self.appendMenu([translate("Path","Path"),translate("Path","New Operations")],opcmdlist) self.appendMenu([translate("Path","Path"),translate("Path","Path Modification")],modcmdlist) + self.appendMenu([translate("Path","Path"),translate("Path","Remote Operations")],remotecmdlist) # Add preferences pages import os @@ -112,7 +114,8 @@ class PathWorkbench ( Workbench ): self.appendContextMenu("",["Add_Tag"]) self.appendContextMenu("",["Set_StartPoint"]) self.appendContextMenu("",["Set_EndPoint"]) - + if "Remote" in FreeCADGui.Selection.getSelection()[0].Name: + self.appendContextMenu("",["Refresh_Path"]) Gui.addWorkbench(PathWorkbench()) diff --git a/src/Mod/Path/PathScripts/PathRemote.py b/src/Mod/Path/PathScripts/PathRemote.py new file mode 100644 index 000000000..03f27aed9 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathRemote.py @@ -0,0 +1,434 @@ +# -*- coding: utf-8 -*- + +#*************************************************************************** +#* * +#* Copyright (c) 2016 sliptonic * +#* * +#* This program is free software; you can redistribute it and/or modify * +#* it under the terms of the GNU Lesser General Public License (LGPL) * +#* as published by the Free Software Foundation; either version 2 of * +#* the License, or (at your option) any later version. * +#* for detail see the LICENCE text file. * +#* * +#* This program is distributed in the hope that it will be useful, * +#* but WITHOUT ANY WARRANTY; without even the implied warranty of * +#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +#* GNU Library General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with this program; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#*************************************************************************** + +import FreeCAD,Path +from PathScripts import PathUtils +import urllib2 +import json + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + from DraftTools import translate + from pivy import coin +else: + def translate(ctxt,txt): + return txt + +__title__="Path Remote Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" + +"""Path Remote processing object and FreeCAD command""" + +# Qt tanslation handling +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def translate(context, text, disambig=None): + return QtGui.QApplication.translate(context, text, disambig) + + +class ObjectRemote: + + def __init__(self,obj): + + obj.addProperty("App::PropertyLinkSubList","Base","Path",translate("Parent Object(s)","The base geometry of this toolpath")) + obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Make False, to prevent operation from generating code")) + obj.addProperty("App::PropertyString","Comment","Path",translate("PathProject","An optional comment for this profile")) + + obj.addProperty("App::PropertyString","URL", "API", translate("RemotePath", "The Base URL of the remote path service")) + obj.addProperty("App::PropertyStringList", "proplist","Path",translate("Path","list of remote properties")) + obj.setEditorMode('proplist',2) #make this hidden + + #Tool Properties + obj.addProperty("App::PropertyIntegerConstraint","ToolNumber","Tool",translate("PathProfile","The tool number in use")) + obj.ToolNumber = (0, 0, 1000, 0) + obj.setEditorMode('ToolNumber',1) #make this read only + + #Depth Properties + obj.addProperty("App::PropertyFloat", "ClearanceHeight", "Depth", translate("PathProject","The height needed to clear clamps and obstructions")) + obj.addProperty("App::PropertyFloat", "SafeHeight", "Depth", translate("PathProject","Rapid Safety Height between locations.")) + obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Step", translate("PathProject","Incremental Step Down of Tool")) + obj.StepDown = (0.0, 0.01, 100.0, 0.5) + obj.addProperty("App::PropertyFloat", "StartDepth", "Depth", translate("PathProject","Starting Depth of Tool- first cut depth in Z")) + obj.addProperty("App::PropertyFloat", "FinalDepth", "Depth", translate("PathProject","Final Depth of Tool- lowest value in Z")) + obj.addProperty("App::PropertyFloat", "FinishDepth", "Depth", translate("PathProject","Maximum material removed on final pass.")) + + obj.Proxy = self + + def addbaseobject(self, obj, ss, sub=""): + baselist = obj.Base + if len(baselist) == 0: #When adding the first base object, guess at heights + try: + bb = ss.Shape.BoundBox #parent boundbox + subobj = ss.Shape.getElement(sub) + fbb = subobj.BoundBox #feature boundbox + obj.StartDepth = bb.ZMax + obj.ClearanceHeight = bb.ZMax + 5.0 + obj.SafeHeight = bb.ZMax + 3.0 + + if fbb.ZMax < bb.ZMax: + obj.FinalDepth = fbb.ZMax + else: + obj.FinalDepth = bb.ZMin + except: + obj.StartDepth = 5.0 + obj.ClearanceHeight = 10.0 + obj.SafeHeight = 8.0 + + item = (ss, sub) + if item in baselist: + FreeCAD.Console.PrintWarning("this object already in the list"+ "\n") + else: + baselist.append (item) + obj.Base = baselist + self.execute(obj) + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def onChanged(self, obj, prop): + + "'''Do something when a property has changed'''" + if prop == "URL": + url = obj.URL + "/api/v1.0/properties" + try: + response = urllib2.urlopen(url) + except: + print "service not defined or not responding" + return + + data = json.load(response) + + properties = data['properties'] + for prop in obj.proplist: + print "removing: " + str(prop) + obj.removeProperty(prop) + + pl = obj.proplist + pl = [] + for prop in properties: + obj.addProperty(prop['type'], prop['propertyname'], "Remote", prop['description']) + pl.append(prop['propertyname']) + print "adding: " + str(prop) + obj.proplist = pl + + def execute(self,obj): + output = "" + + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad == None: + self.vertFeed = 100 + self.horizFeed = 100 + self.radius = 0.25 + obj.ToolNumber= 0 + else: + self.vertFeed = toolLoad.VertFeed.Value + self.horizFeed = toolLoad.HorizFeed.Value + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + self.radius = tool.Diameter/2 + obj.ToolNumber= toolLoad.ToolNumber + + output += "(remote gcode goes here)" + path = Path.Path(output) + obj.Path = path + +class ViewProviderRemote: + def __init__(self,obj): #mandatory +# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property") + obj.Proxy = self + + def __getstate__(self): #mandatory + return None + + def __setstate__(self,state): #mandatory + return None + + def getIcon(self): #optional + return ":/icons/Path-Remote.svg" + + def onChanged(self,obj,prop): #optional + # this is executed when a property of the VIEW PROVIDER changes + pass + + def updateData(self,obj,prop): #optional + # this is executed when a property of the APP OBJECT changes + pass + + def setEdit(self,vobj,mode=0): + FreeCADGui.Control.closeDialog() + taskd = TaskPanel() + taskd.obj = vobj.Object + FreeCADGui.Control.showDialog(taskd) + taskd.setupUi() + return True + + def unsetEdit(self,vobj,mode): #optional + # this is executed when the user cancels or terminates edit mode + pass + +class _RefreshRemotePath: + def GetResources(self): + return {'Pixmap' : 'Path-Refresh', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Refresh Remote Path Data"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Refresh Remote Path Data")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def refresh(self): + obj=FreeCADGui.Selection.getSelection()[0] + values = {} + + for i in obj.PropertiesList: + if obj.getGroupOfProperty(i) in ["Remote"]: + values.update({i : obj.getPropertyByName(i)}) + + if obj.getGroupOfProperty(i) in ["Depth"]: + print str(i) + values.update({i : obj.getPropertyByName(i)}) + + if obj.getGroupOfProperty(i) in ["Step"]: + values.update({i : obj.getPropertyByName(i)}) + + + if obj.getGroupOfProperty(i) in ["Tool"]: + tool = PathUtils.getTool(obj,obj.ToolNumber) + if tool: + tradius = tool.Diameter/2 + tlength = tool.LengthOffset + ttype = tool.ToolType + else: + tradius = 0.25 + tlength = 1 + ttype = "undefined" + + values.update({"tool_diameter" : tradius}) + values.update({"tool_length" : tlength}) + values.update({"tool_type" : ttype}) + + + payload = json.dumps(values) + + url = obj.URL + "/api/v1.0/path" + print url + try: + req = urllib2.Request(url) + req.add_header('Content-Type', 'application/json') + response = urllib2.urlopen(req, payload) + data = json.load(response) + except: + print "service not defined or not responding" + return + + + path = data['path'] + output = "" + for command in path: + output += command['command'] + path = Path.Path(output) + obj.Path = path + + + def Activated(self): + self.refresh() + +class CommandPathRemote: + + + def GetResources(self): + return {'Pixmap' : 'Path-Remote', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Remote"), + 'Accel': "P, R", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Remote","Request a Path from a remote cloud service")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + ztop = 10.0 + zbottom = 0.0 + + FreeCAD.ActiveDocument.openTransaction(translate("Path_Remote","Create remote path operation")) + FreeCADGui.addModule("PathScripts.PathRemote") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Remote")') + FreeCADGui.doCommand('PathScripts.PathRemote.ObjectRemote(obj)') + FreeCADGui.doCommand('obj.Active = True') + FreeCADGui.doCommand('PathScripts.PathRemote.ViewProviderRemote(obj.ViewObject)') + #FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.'+selection[0].ObjectName+',[])') + FreeCADGui.doCommand('from PathScripts import PathUtils') + FreeCADGui.doCommand('obj.ClearanceHeight = ' + str(ztop + 2)) + FreeCADGui.doCommand('obj.StartDepth = ' + str(ztop)) + FreeCADGui.doCommand('obj.SafeHeight = ' + str(ztop + 2)) + FreeCADGui.doCommand('obj.StepDown = ' + str((ztop-zbottom)/8)) + + FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom)) + FreeCADGui.doCommand('PathScripts.PathUtils.addToProject(obj)') + + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.doCommand('obj.ViewObject.startEditing()') + +class TaskPanel: + def __init__(self): + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/RemoteEdit.ui") + #self.form = FreeCADGui.PySideUic.loadUi(":/RemoteEdit.ui") + + def accept(self): + self.getFields() + + FreeCADGui.ActiveDocument.resetEdit() + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + + def reject(self): + FreeCADGui.Control.closeDialog() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.Selection.removeObserver(self.s) + + def getRemoteFields(self): + self.getFields() + self.obj.URL = self.form.remoteURL.text() + + def getFields(self): + if self.obj: + if hasattr(self.obj,"StartDepth"): + self.obj.StartDepth = float(self.form.startDepth.text()) + if hasattr(self.obj,"FinalDepth"): + self.obj.FinalDepth = float(self.form.finalDepth.text()) + if hasattr(self.obj,"SafeHeight"): + self.obj.SafeHeight = float(self.form.safeHeight.text()) + if hasattr(self.obj,"ClearanceHeight"): + self.obj.ClearanceHeight = float(self.form.clearanceHeight.text()) + if hasattr(self.obj,"StepDown"): + self.obj.StepDown = float(self.form.stepDown.value()) + + self.obj.Proxy.execute(self.obj) + + def open(self): + self.s =SelObserver() + # install the function mode resident + FreeCADGui.Selection.addObserver(self.s) + + def addBase(self): + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelectionEx() + + if not len(selection) >= 1: + FreeCAD.Console.PrintError(translate("PathProject","Please select at least one suitable object\n")) + return + for s in selection: + if s.HasSubObjects: + for i in s.SubElementNames: + self.obj.Proxy.addbaseobject(self.obj, s.Object, i) + else: + self.obj.Proxy.addbaseobject(self.obj, s.Object) + + self.setupUi() #defaults may have changed. Reload. + self.form.baseList.clear() + for i in self.obj.Base: + self.form.baseList.addItem(i[0].Name + "." + i[1]) + + def deleteBase(self): + dlist = self.form.baseList.selectedItems() + for d in dlist: + newlist = [] + for i in self.obj.Base: + if not i[0].Name == d.text(): + newlist.append (i) + self.obj.Base = newlist + self.form.baseList.takeItem(self.form.baseList.row(d)) + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + + def itemActivated(self): + FreeCADGui.Selection.clearSelection() + slist = self.form.baseList.selectedItems() + for i in slist: + o = FreeCAD.ActiveDocument.getObject(i.text()) + FreeCADGui.Selection.addSelection(o) + FreeCADGui.updateGui() + + def reorderBase(self): + newlist = [] + for i in range(self.form.baseList.count()): + s = self.form.baseList.item(i).text() + obj = FreeCAD.ActiveDocument.getObject(s) + newlist.append(obj) + self.obj.Base=newlist + self.obj.Proxy.execute(self.obj) + FreeCAD.ActiveDocument.recompute() + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Ok) + + def setupUi(self): + self.form.startDepth.setText(str(self.obj.StartDepth)) + self.form.finalDepth.setText(str(self.obj.FinalDepth)) + self.form.safeHeight.setText(str(self.obj.SafeHeight)) + self.form.clearanceHeight.setText(str(self.obj.ClearanceHeight)) + self.form.remoteURL.setText(str(self.obj.URL)) + + for i in self.obj.Base: + self.form.baseList.addItem(i[0].Name) + + #Connect Signals and Slots + self.form.startDepth.editingFinished.connect(self.getFields) + self.form.finalDepth.editingFinished.connect(self.getFields) + self.form.safeHeight.editingFinished.connect(self.getFields) + self.form.clearanceHeight.editingFinished.connect(self.getFields) + self.form.remoteURL.editingFinished.connect(self.getRemoteFields) + + self.form.addBase.clicked.connect(self.addBase) + self.form.deleteBase.clicked.connect(self.deleteBase) + self.form.reorderBase.clicked.connect(self.reorderBase) + + self.form.baseList.itemSelectionChanged.connect(self.itemActivated) + +class SelObserver: + def __init__(self): + import PathScripts.PathSelection as PST + #PST.surfaceselect() + + def __del__(self): + import PathScripts.PathSelection as PST + PST.clear() + + def addSelection(self,doc,obj,sub,pnt): # Selection object + FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj +')') + FreeCADGui.updateGui() + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_Remote',CommandPathRemote()) + FreeCADGui.addCommand('Refresh_Path',_RefreshRemotePath()) + +FreeCAD.Console.PrintLog("Loading PathRemote... done\n")