diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index fefb56ccc..3ee1fe973 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -5,6 +5,7 @@ icons/Path-Compound.svg icons/Path-Shape.svg icons/Path-Profile.svg + icons/Path-Contour.svg icons/Path-Pocket.svg icons/Path-Drilling.svg icons/Path-Job.svg diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Contour.svg b/src/Mod/Path/Gui/Resources/icons/Path-Contour.svg new file mode 100644 index 000000000..2b3a6ab20 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Contour.svg @@ -0,0 +1,565 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/panels/EngraveEdit.ui b/src/Mod/Path/Gui/Resources/panels/EngraveEdit.ui index d0055a4b6..024577e34 100644 --- a/src/Mod/Path/Gui/Resources/panels/EngraveEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/EngraveEdit.ui @@ -106,30 +106,6 @@ - - - - :/icons/Path-LoadTool.svg:/icons/Path-LoadTool.svg - - - Tool Settings - - - - - 0 - 10 - 301 - 41 - - - - - - - - - diff --git a/src/Mod/Path/Gui/Resources/panels/JobEdit.ui b/src/Mod/Path/Gui/Resources/panels/JobEdit.ui index 54cd85eff..3ce69ac98 100644 --- a/src/Mod/Path/Gui/Resources/panels/JobEdit.ui +++ b/src/Mod/Path/Gui/Resources/panels/JobEdit.ui @@ -6,12 +6,12 @@ 0 0 - 398 - 534 + 396 + 517 - + 0 0 @@ -19,7 +19,7 @@ 0 - 533 + 400 @@ -42,8 +42,8 @@ 0 0 - 380 - 426 + 378 + 409 @@ -56,6 +56,12 @@ + + + 0 + 0 + + @@ -110,7 +116,7 @@ - + 0 0 @@ -132,11 +138,6 @@ - - - - - @@ -144,8 +145,8 @@ 0 0 - 380 - 426 + 451 + 349 @@ -214,8 +215,8 @@ 0 0 - 380 - 426 + 451 + 349 diff --git a/src/Mod/Path/Gui/Resources/panels/ToolControl.ui b/src/Mod/Path/Gui/Resources/panels/ToolControl.ui index ba0208eb0..a7a69ff13 100644 --- a/src/Mod/Path/Gui/Resources/panels/ToolControl.ui +++ b/src/Mod/Path/Gui/Resources/panels/ToolControl.ui @@ -6,22 +6,22 @@ 0 0 - 320 - 503 + 308 + 389 Tool Control - - + + true - + QFrame::StyledPanel @@ -57,7 +57,7 @@ - + QFrame::StyledPanel @@ -86,10 +86,38 @@ + + + + Horiz Rapid + + + + + + + + + + + + + + Vert Rapid + + + + + + + + + + - + QFrame::StyledPanel diff --git a/src/Mod/Path/InitGui.py b/src/Mod/Path/InitGui.py index cff99b995..6497c7651 100644 --- a/src/Mod/Path/InitGui.py +++ b/src/Mod/Path/InitGui.py @@ -67,13 +67,14 @@ class PathWorkbench (Workbench): from PathScripts import PathRemote from PathScripts import PathSanity from PathScripts import DragknifeDressup + from PathScripts import PathContour # build commands list - projcmdlist = ["Path_Job", "Path_Post", "Path_Inspect", "Path_Sanity"] + projcmdlist = ["Path_Job", "Path_Post","Separator", "Path_Inspect", "Path_Sanity"] toolcmdlist = ["Path_ToolLibraryEdit", "Path_LoadTool"] prepcmdlist = ["Path_Plane", "Path_Fixture", "Path_ToolLenOffset", "Path_Comment", "Path_Stop", "Path_FaceProfile", "Path_FacePocket", "Path_Custom", "Path_FromShape"] - opcmdlist = ["Path_Profile", "Path_Pocket", + opcmdlist = ["Path_Contour", "Path_Profile", "Path_Pocket", "Path_Drilling", "Path_Engrave", "Path_Surfacing"] modcmdlist = ["Path_Copy", "Path_CompoundExtended", "Path_Array", "Path_SimpleCopy" ] diff --git a/src/Mod/Path/PathScripts/PathContour.py b/src/Mod/Path/PathScripts/PathContour.py new file mode 100644 index 000000000..208a758c4 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathContour.py @@ -0,0 +1,635 @@ +# -*- coding: utf-8 -*- + +# *************************************************************************** +# * * +# * Copyright (c) 2014 Yorik van Havre * +# * * +# * 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 +import Path +import numpy +from FreeCAD import Vector +from PathScripts import PathUtils +from PathScripts.PathUtils import depth_params + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + # 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) +else: + def translate(ctxt, txt): + return txt + +__title__ = "Path Contour Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" + +"""Path Contour object and FreeCAD command""" + +class ObjectContour: + + def __init__(self, obj): + obj.addProperty("App::PropertyBool", "Active", "Path", "Make False, to prevent operation from generating code") + obj.addProperty("App::PropertyString", "Comment", "Path", "An optional comment for this Contour") + obj.addProperty("App::PropertyString", "UserLabel", "Path", "User Assigned Label") + + obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", "The tool number in use") + obj.ToolNumber = (0, 0, 1000, 1) + obj.setEditorMode('ToolNumber', 1) # make this read only + obj.addProperty("App::PropertyString", "ToolDescription", "Tool", "The description of the tool ") + obj.setEditorMode('ToolDescription', 1) # make this read only + + # Depth Properties + obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", "The height needed to clear clamps and obstructions") + obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", "Rapid Safety Height between locations.") + obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Depth", "Incremental Step Down of Tool") + obj.StepDown = (1, 0.01, 1000, 0.5) + obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", "Starting Depth of Tool- first cut depth in Z") + obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", "Final Depth of Tool- lowest value in Z") + + # Start Point Properties + obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", "The start point of this path") + obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", "make True, if specifying a Start Point") + obj.addProperty("App::PropertyLength", "ExtendAtStart", "Start Point", "extra length of tool path before start of part edge") + obj.addProperty("App::PropertyLength", "LeadInLineLen", "Start Point", "length of straight segment of toolpath that comes in at angle to first part edge") + + # End Point Properties + obj.addProperty("App::PropertyBool", "UseEndPoint", "End Point", "make True, if specifying an End Point") + obj.addProperty("App::PropertyLength", "ExtendAtEnd", "End Point", "extra length of tool path after end of part edge") + obj.addProperty("App::PropertyLength", "LeadOutLineLen", "End Point", "length of straight segment of toolpath that comes in at angle to last part edge") + obj.addProperty("App::PropertyVector", "EndPoint", "End Point", "The end point of this path") + + # Contour Properties + obj.addProperty("App::PropertyEnumeration", "Direction", "Contour", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW") + obj.Direction = ['CW', 'CCW'] # this is the direction that the Contour runs + obj.addProperty("App::PropertyBool", "UseComp", "Contour", "make True, if using Cutter Radius Compensation") + + obj.addProperty("App::PropertyDistance", "RollRadius", "Contour", "Radius at start and end") + obj.addProperty("App::PropertyDistance", "OffsetExtra", "Contour", "Extra value to stay away from final Contour- good for roughing toolpath") + obj.addProperty("App::PropertyLength", "SegLen", "Contour", "Tesselation value for tool paths made from beziers, bsplines, and ellipses") + obj.addProperty("App::PropertyAngle", "PlungeAngle", "Contour", "Plunge angle with which the tool enters the work piece. Straight down is 90 degrees, if set small enough or zero the tool will descent exactly one layer depth down per turn") + + obj.addProperty("App::PropertyVectorList", "locs", "Tags", "List of holding tag locations") + + obj.addProperty("App::PropertyFloatList", "angles", "Tags", "List of angles for the holding tags") + obj.addProperty("App::PropertyFloatList", "heights", "Tags", "List of angles for the holding tags") + obj.addProperty("App::PropertyFloatList", "lengths", "Tags", "List of angles for the holding tags") + locations = [] + angles = [] + lengths = [] + heights = [] + + obj.locs = locations + obj.angles = angles + obj.lengths = lengths + obj.heights = heights + + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + def onChanged(self, obj, prop): + if prop == "UserLabel": + obj.Label = obj.UserLabel + " :" + obj.ToolDescription + + def setDepths(proxy, obj): + parentJob = PathUtils.findParentJob(obj) + if parentJob is None: + return + baseobject = parentJob.Base + if baseobject is None: + return + + try: + bb = baseobject.Shape.BoundBox # parent boundbox + obj.StartDepth = bb.ZMax + obj.ClearanceHeight = bb.ZMax + 5.0 + obj.SafeHeight = bb.ZMax + 3.0 + + except: + obj.StartDepth = 5.0 + obj.ClearanceHeight = 10.0 + obj.SafeHeight = 8.0 + + def _buildPathLibarea(self, obj, edgelist): + import PathScripts.PathKurveUtils as PathKurveUtils + import math + import area + output = "" + if obj.Comment != "": + output += '(' + str(obj.Comment)+')\n' + + if obj.StartPoint and obj.UseStartPoint: + startpoint = obj.StartPoint + else: + startpoint = None + + if obj.EndPoint and obj.UseEndPoint: + endpoint = obj.EndPoint + else: + endpoint = None + + PathKurveUtils.output('mem') + PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed) + + output = "" + output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" + curve = PathKurveUtils.makeAreaCurve(edgelist, obj.Direction, startpoint, endpoint) + + roll_radius = 2.0 + extend_at_start = 0.0 + extend_at_end = 0.0 + lead_in_line_len = 0.0 + lead_out_line_len = 0.0 + + if obj.UseComp is False: + side = 'On' + else: + if obj.Direction == 'CW': + side = 'Left' + else: + side = 'Right' + + PathKurveUtils.clear_tags() + for i in range(len(obj.locs)): + tag = obj.locs[i] + h = obj.heights[i] + l = obj.lengths[i] + a = math.radians(obj.angles[i]) + PathKurveUtils.add_tag(area.Point(tag.x, tag.y), l, a, h) + + depthparams = depth_params( + obj.ClearanceHeight.Value, + obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown, 0.0, + obj.FinalDepth.Value, None) + + PathKurveUtils.profile2( + curve, side, self.radius, self.vertFeed, self.horizFeed, + self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius, + None, None, depthparams, extend_at_start, extend_at_end, + lead_in_line_len, lead_out_line_len) + + output += PathKurveUtils.retrieve_gcode() + return output + + def execute(self, obj): + import Part # math #DraftGeomUtils + output = "" + + toolLoad = PathUtils.getLastToolLoad(obj) + + if toolLoad is None or toolLoad.ToolNumber == 0: + self.vertFeed = 100 + self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 + self.radius = 0.25 + obj.ToolNumber = 0 + obj.ToolDescription = "UNDEFINED" + else: + self.vertFeed = toolLoad.VertFeed.Value + self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if tool.Diameter == 0: + self.radius = 0.25 + else: + self.radius = tool.Diameter/2 + obj.ToolNumber = toolLoad.ToolNumber + obj.ToolDescription = toolLoad.Name + + if obj.UserLabel == "": + obj.Label = obj.Name + " :" + obj.ToolDescription + else: + obj.Label = obj.UserLabel + " :" + obj.ToolDescription + + output += "(" + obj.Label + ")" + if not obj.UseComp: + output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")" + else: + output += "(Uncompensated Tool Path)" + + parentJob = PathUtils.findParentJob(obj) + if parentJob is None: + return + baseobject = parentJob.Base + if baseobject is None: + return + print "base object: " + baseobject.Name + contourwire = PathUtils.silhouette(baseobject) + + edgelist = contourwire.Edges + edgelist = Part.__sortEdges__(edgelist) + output += self._buildPathLibarea(obj, edgelist) + if obj.Active: + path = Path.Path(output) + obj.Path = path + obj.ViewObject.Visibility = True + + else: + path = Path.Path("(inactive operation)") + obj.Path = path + obj.ViewObject.Visibility = False + + +class _ViewProviderContour: + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + self.Object = vobj.Object + return + + def setEdit(self, vobj, mode=0): + FreeCADGui.Control.closeDialog() + taskd = TaskPanel() + taskd.obj = vobj.Object + FreeCADGui.Control.showDialog(taskd) + taskd.setupUi() + return True + + def getIcon(self): + return ":/icons/Path-Contour.svg" + + def __getstate__(self): + return None + + def __setstate__(self, state): + return None + + +class _CommandAddTag: + def GetResources(self): + return {'Pixmap': 'Path-Holding', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Add Holding Tag"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Add Holding Tag")} + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + def setpoint(self, point, o): + obj = FreeCADGui.Selection.getSelection()[0] + obj.StartPoint.x = point.x + obj.StartPoint.y = point.y + loc = obj.locs + h = obj.heights + l = obj.lengths + a = obj.angles + + x = point.x + y = point.y + z = float(0.0) + loc.append(Vector(x, y, z)) + h.append(4.0) + l.append(5.0) + a.append(45.0) + + obj.locs = loc + obj.heights = h + obj.lengths = l + obj.angles = a + + def Activated(self): + + FreeCADGui.Snapper.getPoint(callback=self.setpoint) + + +class _CommandSetStartPoint: + def GetResources(self): + return {'Pixmap': 'Path-Holding', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Pick Start Point"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Pick Start Point")} + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + def setpoint(self, point, o): + obj = FreeCADGui.Selection.getSelection()[0] + obj.StartPoint.x = point.x + obj.StartPoint.y = point.y + + def Activated(self): + + FreeCADGui.Snapper.getPoint(callback=self.setpoint) + + +class _CommandSetEndPoint: + def GetResources(self): + return {'Pixmap': 'Path-Holding', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Pick End Point"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Contour", "Pick End Point")} + + def IsActive(self): + return FreeCAD.ActiveDocument is not None + + def setpoint(self, point, o): + obj = FreeCADGui.Selection.getSelection()[0] + obj.EndPoint.x = point.x + obj.EndPoint.y = point.y + + def Activated(self): + + FreeCADGui.Snapper.getPoint(callback=self.setpoint) + + +class CommandPathContour: + def GetResources(self): + return {'Pixmap': 'Path-Contour', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathContour", "Contour"), + 'Accel': "P, P", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathContour", "Creates a Path Contour object from selected faces")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + ztop = 10.0 + zbottom = 0.0 + + FreeCAD.ActiveDocument.openTransaction(translate("Path", "Create a Contour")) + FreeCADGui.addModule("PathScripts.PathContour") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Contour")') + FreeCADGui.doCommand('PathScripts.PathContour.ObjectContour(obj)') + FreeCADGui.doCommand('PathScripts.PathContour._ViewProviderContour(obj.ViewObject)') + + FreeCADGui.doCommand('obj.Active = True') + + FreeCADGui.doCommand('obj.ClearanceHeight = ' + str(ztop + 10.0)) + FreeCADGui.doCommand('obj.StepDown = 1.0') + FreeCADGui.doCommand('obj.StartDepth= ' + str(ztop)) + FreeCADGui.doCommand('obj.FinalDepth=' + str(zbottom)) + + FreeCADGui.doCommand('obj.SafeHeight = ' + str(ztop + 2.0)) + FreeCADGui.doCommand('obj.OffsetExtra = 0.0') + FreeCADGui.doCommand('obj.Direction = "CW"') + FreeCADGui.doCommand('obj.UseComp = True') + FreeCADGui.doCommand('obj.PlungeAngle = 90.0') + FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') + + FreeCADGui.doCommand('PathScripts.PathContour.ObjectContour.setDepths(obj.Proxy, obj)') + + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + FreeCADGui.doCommand('obj.ViewObject.startEditing()') + + +class TaskPanel: + def __init__(self): + #self.form = FreeCADGui.PySideUic.loadUi(":/panels/ContourEdit.ui") + self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ContourEdit.ui") + self.updating = False + + 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 getFields(self): + if self.obj: + if hasattr(self.obj, "StartDepth"): + self.obj.StartDepth = self.form.startDepth.text() + if hasattr(self.obj, "FinalDepth"): + self.obj.FinalDepth = self.form.finalDepth.text() + if hasattr(self.obj, "SafeHeight"): + self.obj.SafeHeight = self.form.safeHeight.text() + if hasattr(self.obj, "ClearanceHeight"): + self.obj.ClearanceHeight = self.form.clearanceHeight.text() + if hasattr(self.obj, "StepDown"): + self.obj.StepDown = self.form.stepDown.value() + if hasattr(self.obj, "OffsetExtra"): + self.obj.OffsetExtra = self.form.extraOffset.value() + if hasattr(self.obj, "SegLen"): + self.obj.SegLen = self.form.segLen.value() + if hasattr(self.obj, "RollRadius"): + self.obj.RollRadius = self.form.rollRadius.value() + if hasattr(self.obj, "PlungeAngle"): + self.obj.PlungeAngle = str(self.form.plungeAngle.value()) + if hasattr(self.obj, "UseComp"): + self.obj.UseComp = self.form.useCompensation.isChecked() + if hasattr(self.obj, "UseStartPoint"): + self.obj.UseStartPoint = self.form.useStartPoint.isChecked() + if hasattr(self.obj, "UseEndPoint"): + self.obj.UseEndPoint = self.form.useEndPoint.isChecked() + if hasattr(self.obj, "Direction"): + self.obj.Direction = str(self.form.direction.currentText()) + self.obj.Proxy.execute(self.obj) + + def setFields(self): + self.form.startDepth.setText(str(self.obj.StartDepth.Value)) + self.form.finalDepth.setText(str(self.obj.FinalDepth.Value)) + self.form.safeHeight.setText(str(self.obj.SafeHeight.Value)) + self.form.clearanceHeight.setText(str(self.obj.ClearanceHeight.Value)) + self.form.stepDown.setValue(self.obj.StepDown) + self.form.extraOffset.setValue(self.obj.OffsetExtra.Value) + self.form.segLen.setValue(self.obj.SegLen.Value) + self.form.rollRadius.setValue(self.obj.RollRadius.Value) + self.form.plungeAngle.setValue(self.obj.PlungeAngle.Value) + self.form.useCompensation.setChecked(self.obj.UseComp) + self.form.useStartPoint.setChecked(self.obj.UseStartPoint) + self.form.useEndPoint.setChecked(self.obj.UseEndPoint) + + index = self.form.direction.findText( + self.obj.Direction, QtCore.Qt.MatchFixedString) + if index >= 0: + self.form.direction.setCurrentIndex(index) + + for i in range(len(self.obj.locs)): + item = QtGui.QTreeWidgetItem(self.form.tagTree) + item.setText(0, str(i+1)) + l = self.obj.locs[i] + item.setText(1, str(l.x)+", " + str(l.y) + ", " + str(l.z)) + item.setText(2, str(self.obj.heights[i])) + item.setText(3, str(self.obj.lengths[i])) + item.setText(4, str(self.obj.angles[i])) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + item.setTextAlignment(0, QtCore.Qt.AlignLeft) + + def open(self): + self.s = SelObserver() + # install the function mode resident + FreeCADGui.Selection.addObserver(self.s) + + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Ok) + + def edit(self, item, column): + if not self.updating: + self.resetObject() + + def resetObject(self, remove=None): + "transfers the values from the widget to the object" + loc = [] + h = [] + l = [] + a = [] + + for i in range(self.form.tagTree.topLevelItemCount()): + it = self.form.tagTree.findItems( + str(i+1), QtCore.Qt.MatchExactly, 0)[0] + if (remove is None) or (remove != i): + if it.text(1): + x = float(it.text(1).split()[0].rstrip(",")) + y = float(it.text(1).split()[1].rstrip(",")) + z = float(it.text(1).split()[2].rstrip(",")) + loc.append(Vector(x, y, z)) + + else: + loc.append(0.0) + if it.text(2): + h.append(float(it.text(2))) + else: + h.append(4.0) + if it.text(3): + l.append(float(it.text(3))) + else: + l.append(5.0) + if it.text(4): + a.append(float(it.text(4))) + else: + a.append(45.0) + + self.obj.locs = loc + self.obj.heights = h + self.obj.lengths = l + self.obj.angles = a + + self.obj.touch() + FreeCAD.ActiveDocument.recompute() + + def addElement(self): + self.updating = True + + item = QtGui.QTreeWidgetItem(self.form.tagTree) + item.setText(0, str(self.form.tagTree.topLevelItemCount())) + item.setText(1, "0.0, 0.0, 0.0") + item.setText(2, str(float(4.0))) + item.setText(3, str(float(10.0))) + item.setText(4, str(float(45.0))) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + self.updating = False + + self.resetObject() + + def removeElement(self): + it = self.form.tagTree.currentItem() + if it: + nr = int(it.text(0))-1 + self.resetObject(remove=nr) + self.update() + + def update(self): + 'fills the treewidget' + self.updating = True + self.form.tagTree.clear() + if self.obj: + for i in range(len(self.obj.locs)): + item = QtGui.QTreeWidgetItem(self.form.tagTree) + item.setText(0, str(i+1)) + l = self.obj.locs[i] + item.setText(1, str(l.x) + ", " + str(l.y) + ", " + str(l.z)) + item.setText(2, str(self.obj.heights[i])) + item.setText(3, str(self.obj.lengths[i])) + item.setText(4, str(self.obj.angles[i])) + item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable) + item.setTextAlignment(0, QtCore.Qt.AlignLeft) + self.updating = False + return + + + def setupUi(self): + + # Connect Signals and Slots + # Depths + self.form.startDepth.editingFinished.connect(self.getFields) + self.form.finalDepth.editingFinished.connect(self.getFields) + self.form.stepDown.editingFinished.connect(self.getFields) + + # Heights + self.form.safeHeight.editingFinished.connect(self.getFields) + self.form.clearanceHeight.editingFinished.connect(self.getFields) + + # operation + self.form.direction.currentIndexChanged.connect(self.getFields) + self.form.useCompensation.clicked.connect(self.getFields) + self.form.useStartPoint.clicked.connect(self.getFields) + self.form.useEndPoint.clicked.connect(self.getFields) + self.form.extraOffset.editingFinished.connect(self.getFields) + self.form.segLen.editingFinished.connect(self.getFields) + self.form.rollRadius.editingFinished.connect(self.getFields) + + # Tag Form + QtCore.QObject.connect( + self.form.tagTree, + QtCore.SIGNAL("itemChanged(QTreeWidgetItem *, int)"), + self.edit) + self.form.addTag.clicked.connect(self.addElement) + self.form.deleteTag.clicked.connect(self.removeElement) + + self.setFields() + + +class SelObserver: + def __init__(self): + import PathScripts.PathSelection as PST + PST.contourselect() + + def __del__(self): + import PathScripts.PathSelection as PST + PST.clear() + + def addSelection(self, doc, obj, sub, pnt): + FreeCADGui.doCommand('Gui.Selection.addSelection(FreeCAD.ActiveDocument.' + obj + ')') + FreeCADGui.updateGui() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_Contour', CommandPathContour()) + FreeCADGui.addCommand('Add_Tag', _CommandAddTag()) + FreeCADGui.addCommand('Set_StartPoint', _CommandSetStartPoint()) + FreeCADGui.addCommand('Set_EndPoint', _CommandSetEndPoint()) + +FreeCAD.Console.PrintLog("Loading PathContour... done\n") diff --git a/src/Mod/Path/PathScripts/PathDrilling.py b/src/Mod/Path/PathScripts/PathDrilling.py index 2fe3b8c51..79f348f4b 100644 --- a/src/Mod/Path/PathScripts/PathDrilling.py +++ b/src/Mod/Path/PathScripts/PathDrilling.py @@ -60,7 +60,7 @@ class ObjectDrilling: obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Final Depth of Tool- lowest value in Z")) obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","Height to clear top of materil")) obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The height where feed starts and height during retract tool when path is finished")) - + obj.addProperty("App::PropertyFloat", "DwellTime", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The time to dwell between peck cycles")) # Tool Properties obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", QtCore.QT_TRANSLATE_NOOP("App::Property","The tool number in use")) obj.ToolNumber = (0, 0, 1000, 1) @@ -90,15 +90,21 @@ class ObjectDrilling: if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" - else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - self.radius = tool.Diameter/2 + if tool.Diameter == 0: + self.radius = 0.25 + else: + self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name @@ -111,7 +117,9 @@ class ObjectDrilling: output = "(Begin Drilling)\n" if obj.Base: for loc in obj.Base: + #print loc for sub in loc[1]: + #locations.append(self._findDrillingVector(loc)) if "Face" in sub or "Edge" in sub: s = getattr(loc[0].Shape, sub) @@ -136,23 +144,27 @@ class ObjectDrilling: output += "G90 G98\n" # rapid to clearance height - output += "G0 Z" + str(obj.ClearanceHeight.Value) + output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" # rapid to first hole location, with spindle still retracted: p0 = locations[0] - output += "G0 X" + fmt(p0.x) + " Y" + fmt(p0.y) + "\n" + output += "G0 X" + fmt(p0.x) + " Y" + fmt(p0.y) + "F " + PathUtils.fmt(self.horizRapid) + "\n" # move tool to clearance plane - output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "\n" + output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" + pword = "" + qword = "" if obj.PeckDepth.Value > 0: cmd = "G83" qword = " Q" + fmt(obj.PeckDepth.Value) + elif obj.DwellTime > 0: + cmd = "G82" + pword = " P" + fmt(obj.DwellTime) else: cmd = "G81" - qword = "" for p in locations: output += cmd + \ " X" + fmt(p.x) + \ " Y" + fmt(p.y) + \ - " Z" + fmt(obj.FinalDepth.Value) + qword + \ + " Z" + fmt(obj.FinalDepth.Value) + qword + pword + \ " R" + str(obj.RetractHeight.Value) + \ " F" + str(self.vertFeed) + "\n" \ @@ -174,20 +186,22 @@ class ObjectDrilling: def checkdrillable(self, obj, sub): - print "in checkdrillable" drillable = False if obj.ShapeType == 'Vertex': drillable = True elif obj.ShapeType == 'Solid': if sub[0:4] == 'Face': subobj = obj.getElement(sub) - drillable = isinstance(subobj.Edges[0].Curve, Part.Circle) + if isinstance(subobj.Edges[0].Curve, Part.Circle): + drillable = True if str(subobj.Surface) == "": drillable = True - + if len(subobj.Edges[0].Vertexes) > 1: + drillable = False if sub[0:4] == 'Edge': o = obj.getElement(sub) - drillable = isinstance(o.Curve, Part.Circle) + if isinstance(o.Curve, Part.Circle) and len(o.Vertexes) == 1: + drillable = True return drillable @@ -213,16 +227,38 @@ class ObjectDrilling: obj.ClearanceHeight = 10.0 obj.SafeHeight = 8.0 obj.RetractHeight = 6.0 - if self.checkdrillable(ss.Shape,sub): - if item in baselist: - FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n") - else: - baselist.append(item) + if not self.checkdrillable(ss.Shape,sub): + FreeCAD.Console.PrintError("Selected element is not a drillable location" + "\n") + return + + if sub[0:4] == 'Face': + # Check for other drillable faces and give user the option + drillableFaces = [] + + for i in range(len(ss.Shape.Faces)): + if self.checkdrillable(ss.Shape, "Face" + str(i+1)): + drillableFaces.append("Face" + str(i+1)) + if len(drillableFaces) > 1: + reply = QtGui.QMessageBox.question(None,"","Multiple drillable faces found. Drill them all?", + QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: + for i in drillableFaces: + if i in baselist: + FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n") + continue + else: + newitem = (ss, i) + baselist.append(newitem) + else: + if item in baselist: + FreeCAD.Console.PrintWarning("Drillable location already in the list" + "\n") + else: + baselist.append(item) + print baselist obj.Base = baselist self.execute(obj) - class _ViewProviderDrill: def __init__(self, obj): obj.Proxy = self diff --git a/src/Mod/Path/PathScripts/PathEngrave.py b/src/Mod/Path/PathScripts/PathEngrave.py index 3519fbf4c..638788da8 100644 --- a/src/Mod/Path/PathScripts/PathEngrave.py +++ b/src/Mod/Path/PathScripts/PathEngrave.py @@ -91,29 +91,36 @@ class ObjectPathEngrave: if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' - myJob = PathUtils.findMyJob(obj) - if myJob is not None: - controllers = myJob.Proxy.getToolControllers(myJob) - if len(controllers) >= 1: - mlist = [] - for c in controllers: - mlist.append(c.Name) - else: - mlist = ["None"] - obj.ToolController = mlist + # myJob = PathUtils.findParentJob(obj) + # if myJob is not None: + # controllers = myJob.Proxy.getToolControllers(myJob) + # if len(controllers) >= 1: + # mlist = [] + # for c in controllers: + # mlist.append(c.Name) + # else: + # mlist = ["None"] + # obj.ToolController = mlist toolLoad = PathUtils.getLastToolLoad(obj) if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - self.radius = tool.Diameter/2 + if tool.Diameter == 0: + self.radius = 0.25 + else: + self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name @@ -123,7 +130,7 @@ class ObjectPathEngrave: obj.Label = obj.UserLabel + " :" + obj.ToolDescription if obj.Base: - output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value)+"\n" + output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" wires = [] for o in obj.Base: @@ -135,7 +142,7 @@ class ObjectPathEngrave: if obj.Algorithm == "OCC Native": output += self.buildpathocc(obj, wires) - output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value)+"\n" + output += "G0 Z" + PathUtils.fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) +"\n" # print output @@ -173,7 +180,7 @@ class ObjectPathEngrave: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point - output += "G0" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(obj.SafeHeight.Value) # Rapid sto starting position + output += "G0" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(obj.SafeHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) # Rapid sto starting position output += "G1" + " X" + PathUtils.fmt(last.x) + " Y" + PathUtils.fmt(last.y) + " Z" + PathUtils.fmt(obj.FinalDepth.Value) + "F " + PathUtils.fmt(self.vertFeed) + "\n" # Vertical feed to depth if isinstance(edge.Curve, Part.Circle): point = edge.Vertexes[-1].Point @@ -294,6 +301,9 @@ class TaskPanel: def __init__(self): self.form = FreeCADGui.PySideUic.loadUi(":/panels/EngraveEdit.ui") + def __del__(self): + FreeCADGui.Selection.removeObserver(self.s) + def accept(self): self.getFields() diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py index 697a9ddcd..2f09de388 100644 --- a/src/Mod/Path/PathScripts/PathJob.py +++ b/src/Mod/Path/PathScripts/PathJob.py @@ -27,7 +27,7 @@ import Path from PySide import QtCore, QtGui import os import glob -import PathLoadTool +#import PathLoadTool import Draft @@ -123,14 +123,14 @@ class ObjectPathJob: obj.Y_Min = current_post.CORNER_MIN['y'] obj.Z_Min = current_post.CORNER_MIN['z'] - def getToolControllers(self, obj): - '''returns a list of ToolControllers for the current job''' - controllers = [] - for o in obj.Group: - if "Proxy" in o.PropertiesList: - if isinstance(o.Proxy, PathLoadTool.LoadTool): - controllers.append (o.Name) - return controllers + # def getToolControllers(self, obj): + # '''returns a list of ToolControllers for the current job''' + # controllers = [] + # for o in obj.Group: + # if "Proxy" in o.PropertiesList: + # if isinstance(o.Proxy, PathLoadTool.LoadTool): + # controllers.append (o.Name) + # return controllers def execute(self, obj): @@ -204,19 +204,23 @@ class CommandJob: FreeCAD.ActiveDocument.openTransaction(translate("Path_Job", "Create Job")) FreeCADGui.addModule('PathScripts.PathUtils') FreeCADGui.addModule('PathScripts.PathLoadTool') - FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Job")') - FreeCADGui.doCommand('PathScripts.PathJob.ObjectPathJob(obj)') - FreeCADGui.doCommand('PathScripts.PathLoadTool.CommandPathLoadTool.Create(obj.Name)') - FreeCADGui.doCommand('obj.ViewObject.startEditing()') - # FreeCADGui.doCommand('tool = Path.Tool()') - # FreeCADGui.doCommand('tool') - # FreeCADGui.doCommand('tool.Diameter = 5.0') - # FreeCADGui.doCommand('tool.Name = "Default Tool"') - # FreeCADGui.doCommand('tool.cuttingEdgeHeight = 15.0') - # FreeCADGui.doCommand('tool.ToolType = "EndMill"') - # FreeCADGui.doCommand('tool.Material = "HighSpeedSteel"') - # FreeCADGui.doCommand('obj.ToolTable.addTools(tool)') - + snippet = ''' +import PathScripts.PathLoadTool as PathLoadTool +obj = FreeCAD.ActiveDocument.addObject("Path::FeatureCompoundPython", "Job") +PathScripts.PathJob.ObjectPathJob(obj) +PathLoadTool.CommandPathLoadTool.Create(obj.Name) +tl = obj.Group[0] +obj.ViewObject.startEditing() +tool = Path.Tool() +tool.Diameter = 5.0 +tool.Name = "Default Tool" +tool.CuttingEdgeHeight = 15.0 +tool.ToolType = "EndMill" +tool.Material = "HighSpeedSteel" +obj.Tooltable.addTools(tool) +tl.ToolNumber = 1 +''' + FreeCADGui.doCommand(snippet) FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/PathScripts/PathKurveUtils.py b/src/Mod/Path/PathScripts/PathKurveUtils.py index b6fa75add..97f4e5f4c 100644 --- a/src/Mod/Path/PathScripts/PathKurveUtils.py +++ b/src/Mod/Path/PathScripts/PathKurveUtils.py @@ -148,7 +148,7 @@ def profile(curve, side_of_line, radius=1.0, vertfeed=0.0, horizfeed=0.0, offset output = "" output += "G0 Z" + str(clearance) + "\n" - + print "in profile: 151" offset_curve = area.Curve(curve) if offset_curve.getNumVertices() <= 1: raise Exception, "Sketch has no elements!" @@ -297,28 +297,32 @@ and have not been throughly optimized, understood, or tested for FreeCAD.''' def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, - horizfeed=0.0, offset_extra=0.0, roll_radius=2.0, - roll_on=None, roll_off=None, depthparams=None, + horizfeed=0.0, vertrapid=0.0, horizrapid=0.0, offset_extra=0.0, + roll_radius=2.0, roll_on=None, roll_off=None, depthparams=None, extend_at_start=0.0, extend_at_end=0.0, lead_in_line_len=0.0, lead_out_line_len=0.0): - # print "direction: " + str(direction) - # print "radius: " + str(radius) - # print "vertfeed: " + str(vertfeed) - # print "horizfeed: " + str(horizfeed) - # print "offset_extra: " + str(offset_extra) - # print "roll_radius: " + str(roll_radius) - # print "roll_on: " + str(roll_on) - # print "roll_off: " + str(roll_off) - # print "depthparams: " + str(depthparams) - # print "extend_at_start: " + str(extend_at_start) - # print "extend_at_end: " + str(extend_at_end) - # print "lead_in_line_len: " + str(lead_in_line_len) - # print "lead_out_line_len: " + str(lead_out_line_len) + print "direction: " + str(direction) + print "radius: " + str(radius) + print "vertfeed: " + str(vertfeed) + print "horizfeed: " + str(horizfeed) + print "offset_extra: " + str(offset_extra) + print "roll_radius: " + str(roll_radius) + print "roll_on: " + str(roll_on) + print "roll_off: " + str(roll_off) + print "depthparams: " + str(depthparams) + print "extend_at_start: " + str(extend_at_start) + print "extend_at_end: " + str(extend_at_end) + print "lead_in_line_len: " + str(lead_in_line_len) + print "lead_out_line_len: " + str(lead_out_line_len) + print "in profile2: 318" global tags direction = direction.lower() offset_curve = area.Curve(curve) + print "curve: " , str(curve) + print "result curve: ", offset_curve.__dict__ + if direction == "on": use_CRC() == False @@ -340,8 +344,10 @@ def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, using_area_for_offset = True a = area.Area() a.append(curve) + print "curve, offset: " , str(curve), str(offset) a.Offset(-offset) for curve in a.getCurves(): + print "result curve: ", curve curve_cw = curve.IsClockwise() if cw != curve_cw: curve.Reverse() @@ -424,15 +430,17 @@ def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, # start point if (endpoint is None) or (endpoint != s): if use_CRC(): - rapid(crc_start_point.x, crc_start_point.y) + rapid(crc_start_point.x, crc_start_point.y) + "F " + horizrapid + "\n" else: - rapid(s.x, s.y) + rapid(s.x, s.y) #+ "F " + str(horizrapid) + "\n" # rapid down to just above the material if endpoint is None: - rapid(z=mat_depth + depthparams.rapid_safety_space) + rapid(z=mat_depth + depthparams.rapid_safety_space) #+ "F " + vertrapid + "\n" + else: - rapid(z=mat_depth) + rapid(z=mat_depth) #+ "F " + str(vertrapid) + "\n" + # feed down to depth mat_depth = depth @@ -481,7 +489,7 @@ def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, add_CRC_end_line(offset_curve, roll_on_curve, roll_off_curve, radius, direction, crc_end_point, lead_out_line_len) if direction == "on": - rapid(z=depthparams.clearance_height) + rapid(z=depthparams.clearance_height) #+ "F " + vertrapid + "\n" else: feed(crc_end_point.x, crc_end_point.y) @@ -493,11 +501,11 @@ def profile2(curve, direction="on", radius=1.0, vertfeed=0.0, if endpoint != s: # rapid up to the clearance height - rapid(z=depthparams.clearance_height) + rapid(z=depthparams.clearance_height)# + "F " + vertrapid + "\n" prev_depth = depth - rapid(z=depthparams.clearance_height) + rapid(z=depthparams.clearance_height)# + "F " + vertrapid + "\n" del offset_curve diff --git a/src/Mod/Path/PathScripts/PathLoadTool.py b/src/Mod/Path/PathScripts/PathLoadTool.py index c6eb5591a..aae314cb0 100644 --- a/src/Mod/Path/PathScripts/PathLoadTool.py +++ b/src/Mod/Path/PathScripts/PathLoadTool.py @@ -50,7 +50,8 @@ class LoadTool(): obj.SpindleDir = ['Forward', 'Reverse'] obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("App::Property","Feed rate for vertical moves in Z")) obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("App::Property","Feed rate for horizontal moves")) - + obj.addProperty("App::PropertySpeed", "VertRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("App::Property", "Rapid rate for vertical moves in Z")) + obj.addProperty("App::PropertySpeed", "HorizRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("App::Property", "Rapid rate for horizontal moves")) obj.Proxy = self mode = 2 obj.setEditorMode('Placement', mode) @@ -211,12 +212,17 @@ class TaskPanel: def getFields(self): if self.obj: - if hasattr(self.obj, "VertFeed"): + if hasattr(self.obj, "Label"): self.obj.Label = self.form.tcoName.text() if hasattr(self.obj, "VertFeed"): self.obj.VertFeed = self.form.vertFeed.text() if hasattr(self.obj, "HorizFeed"): self.obj.HorizFeed = self.form.horizFeed.text() + if hasattr(self.obj, "VertRapid"): + self.obj.VertRapid = self.form.vertRapid.text() + if hasattr(self.obj, "HorizRapid"): + self.obj.HorizRapid = self.form.horizRapid.text() + if hasattr(self.obj, "SpindleSpeed"): self.obj.SpindleSpeed = self.form.spindleSpeed.value() if hasattr(self.obj, "SpindleDir"): @@ -229,6 +235,9 @@ class TaskPanel: self.form.vertFeed.setText(str(self.obj.VertFeed.Value)) self.form.horizFeed.setText(str(self.obj.HorizFeed.Value)) + self.form.vertRapid.setText(str(self.obj.VertRapid.Value)) + self.form.horizRapid.setText(str(self.obj.HorizRapid.Value)) + self.form.spindleSpeed.setValue(self.obj.SpindleSpeed) self.form.tcoName.setText(str(self.obj.Label)) diff --git a/src/Mod/Path/PathScripts/PathPocket.py b/src/Mod/Path/PathScripts/PathPocket.py index 539f65553..fedf8905f 100644 --- a/src/Mod/Path/PathScripts/PathPocket.py +++ b/src/Mod/Path/PathScripts/PathPocket.py @@ -230,7 +230,7 @@ class ObjectPocket: output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' - output += 'G0 Z' + fmt(obj.ClearanceHeight.Value) + "\n" + output += 'G0 Z' + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" offsets = [] nextradius = self.radius + extraoffset @@ -318,9 +318,9 @@ class ObjectPocket: else: print "WARNING: Straight-plunging... probably not good, but we didn't find a place to helix or ramp" startPoint = edge.Vertexes[0].Point - output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "\n" + output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" output += "G0 X" + fmt(startPoint.x) + " Y" + fmt(startPoint.y) +\ - " Z" + fmt(obj.ClearanceHeight.Value) + "\n" + " Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) + "\n" first = False # then move slow down to our starting point for our profile last = edge.Vertexes[0].Point @@ -349,7 +349,7 @@ class ObjectPocket: last = point # move back up - output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "\n" + output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" return output # To reload this from FreeCAD, use: import PathScripts.PathPocket; reload(PathScripts.PathPocket) @@ -359,12 +359,16 @@ class ObjectPocket: if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 + self.vertRapid = 100 + self.horiRrapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if tool.Diameter == 0: self.radius = 0.25 diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 41858a2db..0883f84bf 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -61,15 +61,11 @@ class ObjectProfile: obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", QtCore.QT_TRANSLATE_NOOP("App::Property","The library or algorithm used to generate the path")) obj.Algorithm = ['OCC Native', 'libarea'] - obj.addProperty("App::PropertyLink", "ActiveTC", "Tool", QtCore.QT_TRANSLATE_NOOP("App::Property","The tool controler in use")) - obj.addProperty("App::PropertyEnumeration", "ToolControllers", QtCore.QT_TRANSLATE_NOOP("App::Property","The tool controller to use")) - obj.Toolcontrollers = [] - - # obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", "The tool number in use") - # obj.ToolNumber = (0, 0, 1000, 1) - # obj.setEditorMode('ToolNumber', 1) # make this read only - # obj.addProperty("App::PropertyString", "ToolDescription", "Tool", "The description of the tool ") - # obj.setEditorMode('ToolDescription', 1) # make this read onlyt + obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", "The tool number in use") + obj.ToolNumber = (0, 0, 1000, 1) + obj.setEditorMode('ToolNumber', 1) # make this read only + obj.addProperty("App::PropertyString", "ToolDescription", "Tool", "The description of the tool ") + obj.setEditorMode('ToolDescription', 1) # make this read onlyt # Depth Properties obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property","The height needed to clear clamps and obstructions")) @@ -117,7 +113,7 @@ class ObjectProfile: obj.angles = angles obj.lengths = lengths obj.heights = heights - obj.ToolDescription = "UNDEFINED" + #obj.ToolDescription = "UNDEFINED" obj.Proxy = self @@ -188,7 +184,8 @@ class ObjectProfile: wire, obj.Side, self.radius, clockwise, obj.ClearanceHeight.Value, obj.StepDown, obj.StartDepth.Value, obj.FinalDepth.Value, FirstEdge, PathClosed, obj.SegLen.Value, - self.vertFeed, self.horizFeed, PlungeAngle=obj.PlungeAngle.Value) + self.vertFeed, self.horizFeed, self.vertRapid, self.horizRapid, + PlungeAngle=obj.PlungeAngle.Value) return output @@ -214,7 +211,7 @@ class ObjectProfile: PathKurveUtils.feedrate_hv(self.horizFeed, self.vertFeed) output = "" - output += "G0 Z" + str(obj.ClearanceHeight.Value) + output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" curve = PathKurveUtils.makeAreaCurve(edgelist, obj.Direction, startpoint, endpoint) '''The following line uses a profile function written for use with FreeCAD. It's clean but incomplete. It doesn't handle @@ -226,7 +223,8 @@ print "y - " + str(point.y) or probably other features in heekscnc''' # output += PathKurveUtils.profile(curve, side, radius, vf, hf, offset_extra, rapid_safety_space, clearance, start_depth, step_down, final_depth, use_CRC) - '''The following calls the original procedure from heekscnc profile function. This, in turn, calls many other procedures to modify the profile. + '''The following calls the original procedure from h + toolLoad = obj.activeTCeekscnc profile function. This, in turn, calls many other procedures to modify the profile. This procedure is hacked together from heekscnc and has not been thoroughly reviewed or understood for FreeCAD. It can probably be thoroughly optimized and improved but it'll take a smarter mind than mine to do it. -sliptonic Feb16''' roll_radius = 2.0 @@ -255,10 +253,10 @@ print "y - " + str(point.y) obj.FinalDepth.Value, None) PathKurveUtils.profile2( - curve, obj.Side, self.radius, self.vertFeed, - self.horizFeed, obj.OffsetExtra.Value, roll_radius, None, None, - depthparams, extend_at_start, extend_at_end, lead_in_line_len, - lead_out_line_len) + curve, obj.Side, self.radius, self.vertFeed, self.horizFeed, + self.vertRapid, self.horizRapid, obj.OffsetExtra.Value, roll_radius, + None, None, depthparams, extend_at_start, extend_at_end, + lead_in_line_len, lead_out_line_len) output += PathKurveUtils.retrieve_gcode() return output @@ -267,17 +265,23 @@ print "y - " + str(point.y) import Part # math #DraftGeomUtils output = "" - #toolLoad = PathUtils.getLastToolLoad(obj) - toolLoad = obj.activeTC + toolLoad = PathUtils.getLastToolLoad(obj) + # obj.ToolController = PathUtils.getToolControllers(obj) + # toolLoad = PathUtils.getToolLoad(obj, obj.ToolController) + if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) if tool.Diameter == 0: self.radius = 0.25 @@ -485,6 +489,7 @@ class CommandPathProfile: FreeCADGui.doCommand('obj.Direction = "CW"') FreeCADGui.doCommand('obj.UseComp = False') FreeCADGui.doCommand('obj.PlungeAngle = 90.0') + #FreeCADGui.doCommand('obj.ActiveTC = None') FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)') FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/Path/PathScripts/PathSelection.py b/src/Mod/Path/PathScripts/PathSelection.py index bb1f69ba7..a2e3fb455 100644 --- a/src/Mod/Path/PathScripts/PathSelection.py +++ b/src/Mod/Path/PathScripts/PathSelection.py @@ -174,6 +174,13 @@ class POCKETGate: return pocketable +class CONTOURGate: + def allow(self, doc, obj, sub): + pass + +def contourselect(): + FreeCADGui.Selection.addSelectionGate(CONTOURGate()) + FreeCAD.Console.PrintWarning("Contour Select Mode\n") def fselect(): FreeCADGui.Selection.addSelectionGate(FGate()) diff --git a/src/Mod/Path/PathScripts/PathStrategy.py b/src/Mod/Path/PathScripts/PathStrategy.py new file mode 100644 index 000000000..baeb89a8c --- /dev/null +++ b/src/Mod/Path/PathScripts/PathStrategy.py @@ -0,0 +1,401 @@ +# -*- 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 +import Path +from PathScripts import PathUtils + +if FreeCAD.GuiUp: + import FreeCADGui + from PySide import QtCore, QtGui + +__title__ = "Path Surface Operation" +__author__ = "sliptonic (Brad Collette)" +__url__ = "http://www.freecadweb.org" + +"""Path surface 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 ObjectStrategy: + + def __init__(self, obj): + obj.addProperty("App::PropertyLinkSubList", "Base", "Path", "The base geometry of this toolpath") + obj.addProperty("App::PropertyBool", "Active", "Path", "Make False, to prevent operation from generating code") + obj.addProperty("App::PropertyString", "Comment", "Path", "An optional comment for this profile") + obj.addProperty("App::PropertyString", "UserLabel", "Path", "User Assigned Label") + + obj.addProperty("App::PropertyEnumeration", "Algorithm", "Algorithm", "The library to use to generate the path") + obj.Algorithm = ['OCL Dropcutter', 'OCL Waterline'] + + # Tool Properties + obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", "The tool number in use") + obj.ToolNumber = (0, 0, 1000, 0) + obj.setEditorMode('ToolNumber', 1) # make this read only + obj.addProperty("App::PropertyString", "ToolDescription", "Tool", "The description of the tool ") + obj.setEditorMode('ToolDescription', 1) # make this read onlyt + + # Depth Properties + obj.addProperty("App::PropertyDistance", "ClearanceHeight", "Depth", "The height needed to clear clamps and obstructions") + obj.addProperty("App::PropertyDistance", "SafeHeight", "Depth", "Rapid Safety Height between locations.") + obj.addProperty("App::PropertyFloatConstraint", "StepDown", "Depth", "Incremental Step Down of Tool") + obj.StepDown = (0.0, 0.01, 100.0, 0.5) + obj.addProperty("App::PropertyDistance", "StartDepth", "Depth", "Starting Depth of Tool- first cut depth in Z") + obj.addProperty("App::PropertyDistance", "FinalDepth", "Depth", "Final Depth of Tool- lowest value in Z") + obj.addProperty("App::PropertyDistance", "FinishDepth", "Depth", "Maximum material removed on final pass.") + + obj.Proxy = self + + def addbase(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): + return None + + def execute(self, obj): + output = "" + + toolLoad = PathUtils.getLastToolLoad(obj) + if toolLoad is None or toolLoad.ToolNumber == 0: + self.vertFeed = 100 + self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 + self.radius = 0.25 + obj.ToolNumber = 0 + obj.ToolDescription = "UNDEFINED" + else: + self.vertFeed = toolLoad.VertFeed.Value + self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value + tool = PathUtils.getTool(obj, toolLoad.ToolNumber) + if tool.Diameter == 0: + self.radius = 0.25 + else: + self.radius = tool.Diameter/2 + obj.ToolNumber = toolLoad.ToolNumber + obj.ToolDescription = toolLoad.Name + + + output += "(" + obj.Label + ")" + output += "(Compensated Tool Path. Diameter: " + str(self.radius * 2) + ")" + + if obj.Active: + path = Path.Path(output) + obj.Path = path + obj.ViewObject.Visibility = True + + else: + path = Path.Path("(inactive operation)") + obj.Path = path + obj.ViewObject.Visibility = False + +class ViewProviderStrategy: + + 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-Surfacing.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 CommandPathStrategy: + + def GetResources(self): + return {'Pixmap': 'Path-3DSurface', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Strategy", "Strategy"), + 'Accel': "P, D", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Strategy", "Creates a Path Strategy object")} + + def IsActive(self): + if FreeCAD.ActiveDocument is not None: + for o in FreeCAD.ActiveDocument.Objects: + if o.Name[:3] == "Job": + return True + return False + + def Activated(self): + + ztop = 10 + zbottom = 0 + + FreeCAD.ActiveDocument.openTransaction(translate("Path_Strategy", "Create Strategy")) + FreeCADGui.addModule("PathScripts.PathStrategy") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Strategy")') + FreeCADGui.doCommand('PathScripts.PathStrategy.ObjectStrategy(obj)') + FreeCADGui.doCommand('obj.Active = True') + FreeCADGui.doCommand('PathScripts.PathStrategy.ViewProviderStrategy(obj.ViewObject)') + 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.addToJob(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/StrategyEdit.ui") + #self.form = FreeCADGui.PySideUic.loadUi(":/panels/SurfaceEdit.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 getFields(self): + if self.obj: + if hasattr(self.obj, "StartDepth"): + self.obj.StartDepth = self.form.startDepth.text() + if hasattr(self.obj, "FinalDepth"): + self.obj.FinalDepth = self.form.finalDepth.text() + if hasattr(self.obj, "FinishDepth"): + self.obj.FinishDepth = self.form.finishDepth.text() + if hasattr(self.obj, "StepDown"): + self.obj.StepDown = self.form.stepDown.value() + if hasattr(self.obj, "SafeHeight"): + self.obj.SafeHeight = self.form.safeHeight.text() + if hasattr(self.obj, "ClearanceHeight"): + self.obj.ClearanceHeight = self.form.clearanceHeight.text() + + self.obj.Proxy.execute(self.obj) + + def setFields(self): + self.form.startDepth.setText(str(self.obj.StartDepth.Value)) + self.form.finalDepth.setText(str(self.obj.FinalDepth.Value)) + self.form.finishDepth.setText(str(self.obj.FinishDepth.Value)) + self.form.stepDown.setValue(self.obj.StepDown) + + self.form.safeHeight.setText(str(self.obj.SafeHeight.Value)) + self.form.clearanceHeight.setText(str(self.obj.ClearanceHeight.Value)) + + for i in self.obj.Base: + self.form.baseList.addItem(i[0].Name) + + 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 len(selection) != 1: + FreeCAD.Console.PrintError(translate( + "PathSurface", "Please select a single solid object from the project tree\n")) + return + + if not len(selection[0].SubObjects) == 0: + FreeCAD.Console.PrintError(translate( + "PathSurface", "Please select a single solid object from the project tree\n")) + return + + sel = selection[0].Object + # get type of object + # if sel.TypeId.startswith('Mesh'): + # # it is a mesh already + # print 'was already mesh' + + # elif sel.TypeId.startswith('Part') and \ + # (sel.Shape.BoundBox.XLength > 0) and \ + # (sel.Shape.BoundBox.YLength > 0) and \ + # (sel.Shape.BoundBox.ZLength > 0): + # print 'this is a solid Part object' + + # else: + # FreeCAD.Console.PrintError( + # translate("PathSurface", "Cannot work with this object\n")) + # return + + self.obj.Proxy.addbase(self.obj, sel) + + self.setFields() # defaults may have changed. Reload. + self.form.baseList.clear() + for i in self.obj.Base: + self.form.baseList.addItem(i[0].Name) + + 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): + + # Connect Signals and Slots + + #Base Geometry + 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) + + # Depths + self.form.startDepth.editingFinished.connect(self.getFields) + self.form.finalDepth.editingFinished.connect(self.getFields) + self.form.finishDepth.editingFinished.connect(self.getFields) + self.form.stepDown.editingFinished.connect(self.getFields) + + # Heights + self.form.safeHeight.editingFinished.connect(self.getFields) + self.form.clearanceHeight.editingFinished.connect(self.getFields) + + sel = FreeCADGui.Selection.getSelectionEx() + self.setFields() + + if len(sel) != 0: + self.addBase() + + +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_Strategy', CommandPathStrategy()) + +FreeCAD.Console.PrintLog("Loading PathStrategy... done\n") diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 71fd7b2c1..1c785951f 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -131,9 +131,9 @@ class ObjectSurface: for loop in loops: p = loop[0] loopstring = "(loop begin)" + "\n" - loopstring += "G0 Z" + str(obj.SafeHeight.Value) + "\n" + loopstring += "G0 Z" + str(obj.SafeHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" loopstring += "G0 X" + \ - str(fmt(p.x)) + " Y" + str(fmt(p.y)) + "\n" + str(fmt(p.x)) + " Y" + str(fmt(p.y)) + "F " + PathUtils.fmt(self.horizRapid) + "\n" loopstring += "G1 Z" + str(fmt(p.z)) + "\n" for p in loop[1:]: loopstring += "G1 X" + \ @@ -239,8 +239,8 @@ class ObjectSurface: # generate the path commands output = "" - output += "G0 Z" + str(obj.ClearanceHeight.Value) + "\n" - output += "G0 X" + str(clp[0].x) + " Y" + str(clp[0].y) + "\n" + output += "G0 Z" + str(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" + output += "G0 X" + str(clp[0].x) + " Y" + str(clp[0].y) + "F " + PathUtils.fmt(self.horizRapid) + "\n" output += "G1 Z" + str(clp[0].z) + " F" + str(self.vertFeed) + "\n" for c in clp: @@ -261,14 +261,21 @@ class ObjectSurface: if toolLoad is None or toolLoad.ToolNumber == 0: self.vertFeed = 100 self.horizFeed = 100 + self.vertRapid = 100 + self.horizRapid = 100 self.radius = 0.25 obj.ToolNumber = 0 obj.ToolDescription = "UNDEFINED" else: self.vertFeed = toolLoad.VertFeed.Value self.horizFeed = toolLoad.HorizFeed.Value + self.vertRapid = toolLoad.VertRapid.Value + self.horizRapid = toolLoad.HorizRapid.Value tool = PathUtils.getTool(obj, toolLoad.ToolNumber) - self.radius = tool.Diameter/2 + if tool.Diameter == 0: + self.radius = 0.25 + else: + self.radius = tool.Diameter/2 obj.ToolNumber = toolLoad.ToolNumber obj.ToolDescription = toolLoad.Name diff --git a/src/Mod/Path/PathScripts/PathToolTableEdit.py b/src/Mod/Path/PathScripts/PathToolTableEdit.py index 2aa59b587..debc9b4b5 100644 --- a/src/Mod/Path/PathScripts/PathToolTableEdit.py +++ b/src/Mod/Path/PathScripts/PathToolTableEdit.py @@ -1,79 +1,62 @@ # -*- coding: utf-8 -*- -# *************************************************************************** -# * * -# * Copyright (c) 2015 Dan Falck * -# * * -# * 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 * -# * * -# *************************************************************************** +#*************************************************************************** +#* * +#* Copyright (c) 2015 Dan Falck * +#* * +#* 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 -import FreeCADGui -from PySide import QtCore, QtGui +import FreeCAD,FreeCADGui +from PySide import QtCore,QtGui # 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 CommandPathToolTableEdit: - def GetResources(self): - return {'Pixmap': 'Path-ToolTable', - 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolTableEdit", "EditToolTable"), + return {'Pixmap' : 'Path-ToolTable', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolTableEdit","EditToolTable"), 'Accel': "P, T", - 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolTableEdit", "Edits a Tool Table in a selected Project")} + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolTableEdit","Edits a Tool Table in a selected Project")} def IsActive(self): - return FreeCAD.ActiveDocument is not None + return not FreeCAD.ActiveDocument is None def Activated(self): - FreeCAD.ActiveDocument.openTransaction( - translate("Path_ToolTableEdit", "Edits a Tool Table in a selected Project")) - snippet = ''' -from PathScripts import TooltableEditor -from PathScripts import PathUtils -sel = Gui.Selection.getSelectionEx()[0] -obj=sel.Object -if "Tooltable" in obj.PropertiesList: - TooltableEditor.edit(obj.Name) -''' - FreeCADGui.doCommand(snippet) - - # FreeCADGui.doCommand("from PathScripts import TooltableEditor") - # FreeCADGui.doCommand("from PathScripts import PathUtils") - # FreeCADGui.doCommand("sel = Gui.Selection.getSelectionEx()[0]") - # FreeCADGui.doCommand("obj=sel.Object") - # FreeCADGui.doCommand('job = PathUtils.findParentJob(obj)') - # FreeCADGui.doCommand('TooltableEditor.edit(job.Name)') + FreeCAD.ActiveDocument.openTransaction(translate("Path_ToolTableEdit","Edits a Tool Table in a selected Project")) + FreeCADGui.doCommand("from PathScripts import TooltableEditor") + FreeCADGui.doCommand("from PathScripts import PathUtils") + FreeCADGui.doCommand('machine = PathUtils.findMachine()') + FreeCADGui.doCommand('TooltableEditor.edit(machine.Name)') FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() - -if FreeCAD.GuiUp: +if FreeCAD.GuiUp: # register the FreeCAD command - FreeCADGui.addCommand('Path_ToolTableEdit', CommandPathToolTableEdit()) + FreeCADGui.addCommand('Path_ToolTableEdit',CommandPathToolTableEdit()) FreeCAD.Console.PrintLog("Loading PathToolTableEdit... done\n") + diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 588d4e75b..376711db8 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -26,14 +26,15 @@ import FreeCAD import FreeCADGui import Part import math +import Draft import Path +import TechDraw from DraftGeomUtils import geomType from DraftGeomUtils import findWires import DraftVecUtils import PathScripts from PathScripts import PathJob import itertools -from PySide import QtGui def cleanedges(splines, precision): '''cleanedges([splines],precision). Convert BSpline curves, Beziers, to arcs that can be used for cnc paths. @@ -83,6 +84,9 @@ def curvetowire(obj, steps): # fixme set at 4 decimal places for testing def fmt(val): return format(val, '.4f') +def silhouette(obj): + w = TechDraw.findOuterWire(obj.Shape.Edges) + return w def isSameEdge(e1, e2): """isSameEdge(e1,e2): return True if the 2 edges are both lines or arcs/circles and have the same @@ -147,6 +151,26 @@ def is_clockwise(obj): return sum >= 0 +def loopdetect(obj, edge1, edge2): + ''' + Returns a loop wire that includes the two edges. + Useful for detecting boundaries of negative space features ie 'holes' + If a unique loop is not found, returns None + ''' + candidates = [] + for wire in obj.Shape.Wires: + for e in wire.Edges: + if e.hashCode() == edge1.hashCode(): + candidates.append((wire.hashCode(),wire)) + if e.hashCode() == edge2.hashCode(): + candidates.append((wire.hashCode(),wire)) + loop = set([x for x in candidates if candidates.count(x) > 1]) #return the duplicate item + if len(loop) != 1: + return None + loopwire = next(x for x in loop)[1] + return loopwire + + def check_clockwise(poly): ''' check_clockwise(poly) a function for returning a boolean if the selected wire is clockwise or counter clockwise @@ -396,7 +420,9 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): return offset -def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, VertFeed=1.0, HorizFeed=2.0, PlungeAngle=90.0): +def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, + ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, + VertFeed=1.0, HorizFeed=2.0, VertJog=1.0, HorizJog = 2.0, PlungeAngle=90.0): ''' makes the path - just a simple profile for now ''' offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=SegLen) if len(offset.Edges) == 0: @@ -404,9 +430,9 @@ def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinal toolpath = offset.Edges[:] paths = "" - paths += "G0 Z" + str(ZClearance) + "\n" + paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog) + "\n" first = toolpath[0].Vertexes[0].Point - paths += "G0 X" + str(fmt(first.x)) + "Y" + str(fmt(first.y)) + "\n" + paths += "G0 X" + str(fmt(first.x)) + "Y" + str(fmt(first.y)) + "F " + fmt(HorizJog) + "\n" Zprevious = ZStart ZCurrent = ZStart - StepDown @@ -414,9 +440,9 @@ def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinal paths += convert(toolpath, Z=ZCurrent, Zprevious=Zprevious, PlungeAngle=PlungeAngle, vf=VertFeed, hf=HorizFeed) if not PathClosed: - paths += "G0 Z" + str(ZClearance) + paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog) paths += "G0 X" + str(fmt(first.x)) + "Y" + \ - str(fmt(first.y)) + "\n" + str(fmt(first.y)) + "F " + fmt(HorizJog) + "\n" Zprevious = ZCurrent ZCurrent = ZCurrent - abs(StepDown) @@ -434,7 +460,7 @@ def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinal paths += convert(toolpath, Z=ZFinalDepth, Zprevious=Zprevious, StopLength=StopLength, vf=VertFeed, hf=HorizFeed) - paths += "G0 Z" + str(ZClearance) + paths += "G0 Z" + str(ZClearance) + "F " + fmt(VertJog) + "\n" return paths # the next two functions are for automatically populating tool @@ -456,13 +482,6 @@ def changeTool(obj, job): if g == obj: return tlnum -def getLastTool(obj): - toolNum = obj.ToolNumber - if obj.ToolNumber == 0: - # find tool from previous toolchange - job = findJob() - toolNum = changeTool(obj, job) - return getTool(obj, toolNum) def getLastToolLoad(obj): # This walks up the hierarchy and tries to find the closest preceding @@ -507,6 +526,22 @@ def getLastToolLoad(obj): continue return tc +def getToolControllers(obj): + controllers = [] + try: + parent = obj.InList[0] + except: + parent = None + + if parent is not None: + sibs = parent.Group + for g in sibs: + if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool): + controllers.append(g.Name) + return controllers + + + def getTool(obj, number=0): "retrieves a tool from a hosting object with a tooltable, if any" for o in obj.InList: diff --git a/src/Mod/Path/PathScripts/linuxcnc_post.py b/src/Mod/Path/PathScripts/linuxcnc_post.py index 1d3812c0b..c4812a9c4 100644 --- a/src/Mod/Path/PathScripts/linuxcnc_post.py +++ b/src/Mod/Path/PathScripts/linuxcnc_post.py @@ -207,8 +207,9 @@ def parse(pathobj): for param in params: if param in c.Parameters: if param == 'F': - outstring.append( - param + format(c.Parameters['F'], '.2f')) + if c.Name not in ["G0", "G00"]: #linuxcnc doesn't use rapid speeds + outstring.append( + param + format(c.Parameters['F'], '.2f')) elif param == 'T': outstring.append(param + str(c.Parameters['T'])) else: