From 0d22ca8eaf9109cf9407f1df157c041b4da23f8b Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 19 Jan 2016 22:31:46 -0200 Subject: [PATCH] Path: Added new series of tools and fixes * Fixed UsePlacement property of Path Compounds * Added Path FaceProfile command - does a simple 2D contour of a face * Added Path FacePocket command - does a simple 2D pocket of a face * Added Path Array command - does an array with copies of a path * Added Path Custom command - to add custom G-Code * Added Inspect command - to inspect the G-Code of a path --- src/Mod/Draft/DraftGeomUtils.py | 29 + src/Mod/Path/CMakeLists.txt | 5 + src/Mod/Path/Gui/Resources/Path.qrc | 5 + .../Path/Gui/Resources/icons/Path-Array.svg | 533 +++++++++++ .../Path/Gui/Resources/icons/Path-Custom.svg | 545 +++++++++++ .../Gui/Resources/icons/Path-FacePocket.svg | 548 +++++++++++ .../Gui/Resources/icons/Path-FaceProfile.svg | 543 +++++++++++ .../Path/Gui/Resources/icons/Path-Inspect.svg | 862 ++++++++++++++++++ .../Path/Gui/Resources/icons/Path-Pocket.svg | 23 +- .../Path/Gui/Resources/icons/Path-Profile.svg | 24 +- src/Mod/Path/InitGui.py | 17 +- src/Mod/Path/PathScripts/PathArray.py | 137 +++ .../Path/PathScripts/PathCompoundExtended.py | 6 +- src/Mod/Path/PathScripts/PathCustom.py | 86 ++ src/Mod/Path/PathScripts/PathFacePocket.py | 179 ++++ src/Mod/Path/PathScripts/PathFaceProfile.py | 147 +++ src/Mod/Path/PathScripts/PathInspect.py | 162 ++++ 17 files changed, 3823 insertions(+), 28 deletions(-) create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Array.svg create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Custom.svg create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-FacePocket.svg create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-FaceProfile.svg create mode 100644 src/Mod/Path/Gui/Resources/icons/Path-Inspect.svg create mode 100644 src/Mod/Path/PathScripts/PathArray.py create mode 100644 src/Mod/Path/PathScripts/PathCustom.py create mode 100644 src/Mod/Path/PathScripts/PathFacePocket.py create mode 100644 src/Mod/Path/PathScripts/PathFaceProfile.py create mode 100644 src/Mod/Path/PathScripts/PathInspect.py diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 5f1caded6..be1b866b7 100755 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -2057,9 +2057,38 @@ def tessellateProjection(shape,seglen): except: print("Debug: error cleaning edge ",e) return Part.makeCompound(newedges) + + +def rebaseWire(wire,vidx): + + """rebaseWire(wire,vidx): returns a new wire which is a copy of the + current wire, but where the first vertex is the vertex indicated by the given + index vidx, starting from 1. 0 will return an exact copy of the wire.""" + + if vidx < 1: + return wire + if vidx > len(wire.Vertexes): + #print("Vertex index above maximum\n") + return wire + basepoint = wire.Vertexes[vidx-1].Point + #wire = Part.__sortEdges__(wire) + edges = [] + start = False + for i in range(len(wire.Edges)): + if wire.Edges[i].Vertexes[0].Point == basepoint: + start = True + edges.append(wire.Edges[i]) + elif start: + edges.append(wire.Edges[i]) + if len(edges) < len(wire.Edges): + f = len(wire.Edges) - len(edges) + edges.extend(wire.Edges[0:f]) + return Part.Wire(edges) + # circle functions ********************************************************* + def getBoundaryAngles(angle,alist): '''returns the 2 closest angles from the list that encompass the given angle''' diff --git a/src/Mod/Path/CMakeLists.txt b/src/Mod/Path/CMakeLists.txt index f53f4b407..61573865e 100644 --- a/src/Mod/Path/CMakeLists.txt +++ b/src/Mod/Path/CMakeLists.txt @@ -50,6 +50,11 @@ SET(PathScripts_SRCS PathScripts/PathKurveUtils.py PathScripts/PathKurve.py PathScripts/slic3r_pre.py + PathScripts/PathFaceProfile.py + PathScripts/PathFacePocket.py + PathScripts/PathArray.py + PathScripts/PathCustom.py + PathScripts/PathInspect.py ) ADD_CUSTOM_TARGET(PathScripts ALL diff --git a/src/Mod/Path/Gui/Resources/Path.qrc b/src/Mod/Path/Gui/Resources/Path.qrc index 6d7080861..464348911 100644 --- a/src/Mod/Path/Gui/Resources/Path.qrc +++ b/src/Mod/Path/Gui/Resources/Path.qrc @@ -23,5 +23,10 @@ icons/Path-Stop.svg icons/Path-Machine.svg icons/Path-Kurve.svg + icons/Path-FaceProfile.svg + icons/Path-FacePocket.svg + icons/Path-Array.svg + icons/Path-Custom.svg + icons/Path-Inspect.svg diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Array.svg b/src/Mod/Path/Gui/Resources/icons/Path-Array.svg new file mode 100644 index 000000000..05f99c4e7 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Array.svg @@ -0,0 +1,533 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Custom.svg b/src/Mod/Path/Gui/Resources/icons/Path-Custom.svg new file mode 100644 index 000000000..a1347f19e --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Custom.svg @@ -0,0 +1,545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + <> + + diff --git a/src/Mod/Path/Gui/Resources/icons/Path-FacePocket.svg b/src/Mod/Path/Gui/Resources/icons/Path-FacePocket.svg new file mode 100644 index 000000000..bcba5aa8a --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-FacePocket.svg @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/icons/Path-FaceProfile.svg b/src/Mod/Path/Gui/Resources/icons/Path-FaceProfile.svg new file mode 100644 index 000000000..05429aebf --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-FaceProfile.svg @@ -0,0 +1,543 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Inspect.svg b/src/Mod/Path/Gui/Resources/icons/Path-Inspect.svg new file mode 100644 index 000000000..540bada73 --- /dev/null +++ b/src/Mod/Path/Gui/Resources/icons/Path-Inspect.svg @@ -0,0 +1,862 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Path/Gui/Resources/icons/Path-Pocket.svg b/src/Mod/Path/Gui/Resources/icons/Path-Pocket.svg index b984632e5..85c154b80 100644 --- a/src/Mod/Path/Gui/Resources/icons/Path-Pocket.svg +++ b/src/Mod/Path/Gui/Resources/icons/Path-Pocket.svg @@ -14,7 +14,7 @@ height="64px" id="svg2816" version="1.1" - inkscape:version="0.48.5 r10040" + inkscape:version="0.91 r13725" sodipodi:docname="Path-Pocket.svg"> @@ -487,9 +487,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="3.8890873" - inkscape:cx="-16.685457" - inkscape:cy="24.803491" + inkscape:zoom="7.7781746" + inkscape:cx="19.057265" + inkscape:cy="33.448158" inkscape:current-layer="layer1" showgrid="true" inkscape:document-units="px" @@ -522,20 +522,21 @@ id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer"> + - @@ -487,9 +487,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="11" - inkscape:cx="34.775007" - inkscape:cy="34.723538" + inkscape:zoom="15.556349" + inkscape:cx="20.688961" + inkscape:cy="31.890843" inkscape:current-layer="layer1" showgrid="true" inkscape:document-units="px" @@ -523,16 +523,22 @@ inkscape:label="Layer 1" inkscape:groupmode="layer"> + + sodipodi:nodetypes="cccccc" /> * +#* * +#* 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,FreeCADGui,Path,PathGui +from PySide import QtCore,QtGui + +"""Path Array 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 ObjectArray: + + + def __init__(self,obj): + obj.addProperty("App::PropertyLink","Base","Path","The path to array") + obj.addProperty("App::PropertyVector","Offset","Path","The spacing between the array copies") + obj.addProperty("App::PropertyInteger","Copies","Path","The number of copies") + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def execute(self,obj): + if obj.Base: + if not obj.Base.isDerivedFrom("Path::Feature"): + return + if not obj.Base.Path: + return + + # build copies + basepath = obj.Base.Path + output = basepath.toGCode() + pl = FreeCAD.Placement() + if obj.Offset != FreeCAD.Vector(): + for i in range(obj.Copies): + pl.move(obj.Offset) + np = Path.Path([cm.transform(pl) for cm in basepath.Commands]) + output += np.toGCode() + + #print output + path = Path.Path(output) + obj.Path = path + + +class ViewProviderArray: + + + def __init__(self,vobj): + vobj.Proxy = self + + def attach(self,vobj): + self.Object = vobj.Object + return + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def claimChildren(self): + if hasattr(self,"Object"): + if hasattr(self.Object,"Base"): + if self.Object.Base: + return self.Object.Base + return [] + + +class CommandPathArray: + + + def GetResources(self): + return {'Pixmap' : 'Path-Array', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathArray","Array"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathArray","Creates an array from a selected path")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathArray","Please select exactly one path object\n")) + return + if not(selection[0].isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError(translate("PathArray","Please select exactly one path object\n")) + return + + # if everything is ok, execute and register the transaction in the undo/redo stack + FreeCAD.ActiveDocument.openTransaction("Create Array") + FreeCADGui.addModule("PathScripts.PathArray") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")') + FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)') + FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.' + selection[0].Name + ')') + #FreeCADGui.doCommand('PathScripts.PathArray.ViewProviderArray(obj.ViewObject)') + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_Array',CommandPathArray()) diff --git a/src/Mod/Path/PathScripts/PathCompoundExtended.py b/src/Mod/Path/PathScripts/PathCompoundExtended.py index 229b8db5e..655accdfc 100644 --- a/src/Mod/Path/PathScripts/PathCompoundExtended.py +++ b/src/Mod/Path/PathScripts/PathCompoundExtended.py @@ -58,7 +58,11 @@ class ObjectCompoundExtended: cmds = [] for child in obj.Group: if child.isDerivedFrom("Path::Feature"): - cmds.extend(child.Path.Commands) + if obj.UsePlacements: + for c in child.Path.Commands: + cmds.append(c.transform(child.Placement)) + else: + cmds.extend(child.Path.Commands) if cmds: path = Path.Path(cmds) obj.Path = path diff --git a/src/Mod/Path/PathScripts/PathCustom.py b/src/Mod/Path/PathScripts/PathCustom.py new file mode 100644 index 000000000..f7c0af067 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathCustom.py @@ -0,0 +1,86 @@ +# -*- 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,FreeCADGui,Path,PathGui +from PySide import QtCore,QtGui + +"""Path Custom 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 ObjectCustom: + + + def __init__(self,obj): + obj.addProperty("App::PropertyStringList","Gcode","Path","The gcode to be inserted") + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def execute(self,obj): + if obj.Gcode: + s = "" + for l in obj.Gcode: + s += str(l) + if s: + path = Path.Path(s) + obj.Path = path + + +class CommandPathCustom: + + + def GetResources(self): + return {'Pixmap' : 'Path-Custom', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Custom","Custom"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Custom","Creates a path object based on custom G-code")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + FreeCAD.ActiveDocument.openTransaction("Create Custom Path") + FreeCADGui.addModule("PathScripts.PathCustom") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Custom")') + FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)') + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_Custom',CommandPathCustom()) diff --git a/src/Mod/Path/PathScripts/PathFacePocket.py b/src/Mod/Path/PathScripts/PathFacePocket.py new file mode 100644 index 000000000..c5c6d518c --- /dev/null +++ b/src/Mod/Path/PathScripts/PathFacePocket.py @@ -0,0 +1,179 @@ +# -*- 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,FreeCADGui,Path,PathGui +from PySide import QtCore,QtGui + +"""Path Pocket 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 ObjectFacePocket: + + + def __init__(self,obj): + obj.addProperty("App::PropertyLinkSub","Base","Path","The base geometry of this object") + obj.addProperty("App::PropertyDistance","Offset","Path","The distance between the face and the path") + obj.addProperty("App::PropertyInteger","StartVertex","Path","The vertex index to start the path from") + obj.addProperty("App::PropertyEnumeration","FirstMove","Path","The type of the first move") + obj.FirstMove = ["G0","G1"] + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def execute(self,obj): + if obj.Base and obj.Offset: + import Part, DraftGeomUtils + if "Face" in obj.Base[1][0]: + shape = getattr(obj.Base[0].Shape,obj.Base[1][0]) + else: + edges = [getattr(obj.Base[0].Shape,sub) for sub in obj.Base[1]] + shape = Part.Wire(edges) + + # absolute coords, millimeters, cancel offsets + output = "G90\nG21\nG40\n" + + # build offsets + offsets = [] + nextradius = obj.Offset + result = DraftGeomUtils.pocket2d(shape,nextradius) + while result: + offsets.extend(result) + nextradius += obj.Offset + result = DraftGeomUtils.pocket2d(shape,nextradius) + + # first move will be rapid, subsequent will be at feed rate + first = True + + # revert the list so we start with the outer wires + offsets.reverse() + + # loop over successive wires + while offsets: + currentWire = offsets.pop() + if first: + currentWire = DraftGeomUtils.rebaseWire(currentWire,obj.StartVertex) + last = None + for edge in currentWire.Edges: + if not last: + # we set the base GO to our first point + if first: + output += obj.FirstMove + first = False + else: + output += "G1" + last = edge.Vertexes[0].Point + output += " X" + str("%f" % last.x) + " Y" + str("%f" % last.y) + " Z" + str("%f" % last.z) + "\n" + if isinstance(edge.Curve,Part.Circle): + point = edge.Vertexes[-1].Point + if point == last: # edges can come flipped + point = edge.Vertexes[0].Point + center = edge.Curve.Center + relcenter = center.sub(last) + v1 = last.sub(center) + v2 = point.sub(center) + if v1.cross(v2).z < 0: + output += "G2" + else: + output += "G3" + output += " X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z) + output += " I" + str("%f" % relcenter.x) + " J" + str("%f" % relcenter.y) + " K" + str("%f" % relcenter.z) + output += "\n" + last = point + else: + point = edge.Vertexes[-1].Point + if point == last: # edges can come flipped + point = edge.Vertexes[0].Point + output += "G1 X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z) + "\n" + last = point + + #print output + path = Path.Path(output) + obj.Path = path + + +class CommandPathFacePocket: + + + def GetResources(self): + return {'Pixmap' : 'Path-FacePocket', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathFacePocket","Face Pocket"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathFacePocket","Creates a pocket inside a loop of edges or a face")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelectionEx() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathFacePocket","Please select an edges loop from one object, or a single face\n")) + return + if len(selection[0].SubObjects) == 0: + FreeCAD.Console.PrintError(translate("PathFacePocket","Please select an edges loop from one object, or a single face\n")) + return + for s in selection[0].SubObjects: + if s.ShapeType != "Edge": + if (s.ShapeType != "Face") or (len(selection[0].SubObjects) != 1): + FreeCAD.Console.PrintError(translate("PathFacePocket","Please select only edges or a single face\n")) + return + if selection[0].SubObjects[0].ShapeType == "Edge": + try: + import Part + w = Part.Wire(selection[0].SubObjects) + except: + FreeCAD.Console.PrintError(translate("PathFacePocket","The selected edges don't form a loop\n")) + return + + # if everything is ok, execute and register the transaction in the undo/redo stack + FreeCAD.ActiveDocument.openTransaction("Create Pocket") + FreeCADGui.addModule("PathScripts.PathFacePocket") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","FacePocket")') + FreeCADGui.doCommand('PathScripts.PathFacePocket.ObjectFacePocket(obj)') + subs = "[" + for s in selection[0].SubElementNames: + subs += '"' + s + '",' + subs += "]" + FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.' + selection[0].ObjectName + ',' + subs + ')') + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_FacePocket',CommandPathFacePocket()) diff --git a/src/Mod/Path/PathScripts/PathFaceProfile.py b/src/Mod/Path/PathScripts/PathFaceProfile.py new file mode 100644 index 000000000..8133d1620 --- /dev/null +++ b/src/Mod/Path/PathScripts/PathFaceProfile.py @@ -0,0 +1,147 @@ +# -*- 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,FreeCADGui,Path,PathGui +from PySide import QtCore,QtGui + +"""Path Profile 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 ObjectFaceProfile: + + + def __init__(self,obj): + obj.addProperty("App::PropertyLinkSub","Base","Path","The base geometry of this object") + obj.addProperty("App::PropertyDistance","Offset","Path","The distance between the face and the path") + obj.addProperty("App::PropertyInteger","StartVertex","Path","The vertex index to start the path from") + obj.addProperty("App::PropertyEnumeration","FirstMove","Path","The type of the first move") + obj.FirstMove = ["G0","G1"] + obj.Proxy = self + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + + def execute(self,obj): + if obj.Base: + import Part,DraftGeomUtils + # we only consider the outer wire if this is a Face + shape = getattr(obj.Base[0].Shape,obj.Base[1][0]) + if shape.ShapeType == "Wire": + wire = shape + else: + wire = shape.OuterWire + # we use the OCC offset feature + if obj.Offset.Value != 0: + offset = wire.makeOffset(obj.Offset.Value) + else: + offset = wire + # absolute coords, millimeters, cancel offsets + output = "G90\nG21\nG40\n" + # reorder the wire + offset = DraftGeomUtils.rebaseWire(offset,obj.StartVertex) + # we create the path from the offset shape + last = None + for edge in offset.Edges: + if not last: + # we set the first move to our first point + last = edge.Vertexes[0].Point + output += obj.FirstMove + " X" + str("%f" % last.x) + " Y" + str("%f" % last.y) + " Z" + str("%f" % last.z) + "\n" + if isinstance(edge.Curve,Part.Circle): + point = edge.Vertexes[-1].Point + if point == last: # edges can come flipped + point = edge.Vertexes[0].Point + center = edge.Curve.Center + relcenter = center.sub(last) + v1 = last.sub(center) + v2 = point.sub(center) + if v1.cross(v2).z < 0: + output += "G2" + else: + output += "G3" + output += " X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z) + output += " I" + str("%f" % relcenter.x) + " J" + str("%f" % relcenter.y) + " K" + str("%f" % relcenter.z) + output += "\n" + last = point + else: + point = edge.Vertexes[-1].Point + if point == last: # edges can come flipped + point = edge.Vertexes[0].Point + output += "G1 X" + str("%f" % point.x) + " Y" + str("%f" % point.y) + " Z" + str("%f" % point.z) + "\n" + last = point + #print output + path = Path.Path(output) + obj.Path = path + + +class CommandPathFaceProfile: + + + def GetResources(self): + return {'Pixmap' : 'Path-FaceProfile', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("PathFaceProfile","Face Profile"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathFaceProfile","Creates a profile object around a selected face")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelectionEx() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathFaceProfile","Please select one face or wire\n")) + return + if len(selection[0].SubObjects) != 1: + FreeCAD.Console.PrintError(translate("PathFaceProfile","Please select only one face or wire\n")) + return + if not selection[0].SubObjects[0].ShapeType in ["Face","Wire"]: + FreeCAD.Console.PrintError(translate("PathFaceProfile","Please select only a face or a wire\n")) + return + + # if everything is ok, execute and register the transaction in the undo/redo stack + FreeCAD.ActiveDocument.openTransaction("Create Profile") + FreeCADGui.addModule("PathScripts.PathFaceProfile") + FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","FaceProfile")') + FreeCADGui.doCommand('PathScripts.PathFaceProfile.ObjectFaceProfile(obj)') + FreeCADGui.doCommand('obj.Base = (FreeCAD.ActiveDocument.'+selection[0].ObjectName+',"'+selection[0].SubElementNames[0]+'")') + FreeCADGui.doCommand('obj.ViewObject.Proxy = 0') + FreeCAD.ActiveDocument.commitTransaction() + FreeCAD.ActiveDocument.recompute() + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_FaceProfile',CommandPathFaceProfile()) diff --git a/src/Mod/Path/PathScripts/PathInspect.py b/src/Mod/Path/PathScripts/PathInspect.py new file mode 100644 index 000000000..a807e6fbf --- /dev/null +++ b/src/Mod/Path/PathScripts/PathInspect.py @@ -0,0 +1,162 @@ +#*************************************************************************** +#* (c) Yorik van Havre (yorik@uncreated.net) 2015 * +#* * +#* This file is part of the FreeCAD CAx development system. * +#* * +#* 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. * +#* * +#* FreeCAD 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 Lesser General Public License for more details. * +#* * +#* You should have received a copy of the GNU Library General Public * +#* License along with FreeCAD; if not, write to the Free Software * +#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +#* USA * +#* * +#***************************************************************************/ + + +from PySide import QtCore, QtGui +import FreeCAD,FreeCADGui + + +# 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 OldHighlighter(QtGui.QSyntaxHighlighter): + + + def highlightBlock(self, text): + + myClassFormat = QtGui.QTextCharFormat() + myClassFormat.setFontWeight(QtGui.QFont.Bold) + myClassFormat.setForeground(QtCore.Qt.green) + # the regex pattern to be colored + pattern = "(G.*?|M.*?)\\s" + expression = QtCore.QRegExp(pattern) + index = text.index(expression) + while index >= 0: + length = expression.matchedLength() + setFormat(index, length, myClassFormat) + index = text.index(expression, index + length) + + +class GCodeHighlighter(QtGui.QSyntaxHighlighter): + + + def __init__(self, parent=None): + + super(GCodeHighlighter, self).__init__(parent) + self.highlightingRules = [] + numberFormat = QtGui.QTextCharFormat() + numberFormat.setForeground(QtGui.QColor(0,90,175)) + self.highlightingRules.append((QtCore.QRegExp("[\\-0-9\\.]"),numberFormat)) + keywordFormat = QtGui.QTextCharFormat() + keywordFormat.setForeground(QtCore.Qt.darkCyan) + keywordFormat.setFontWeight(QtGui.QFont.Bold) + keywordPatterns = ["\\bG[0-9]+\\b", "\\bM[0-9]+\\b"] + self.highlightingRules.extend([(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns]) + speedFormat = QtGui.QTextCharFormat() + speedFormat.setFontWeight(QtGui.QFont.Bold) + speedFormat.setForeground(QtCore.Qt.green) + self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"),speedFormat)) + + def highlightBlock(self, text): + + for pattern, format in self.highlightingRules: + expression = QtCore.QRegExp(pattern) + index = expression.indexIn(text) + while index >= 0: + length = expression.matchedLength() + self.setFormat(index, length, format) + index = expression.indexIn(text, index + length) + + +class GCodeEditorDialog(QtGui.QDialog): + + + def __init__(self, parent = FreeCADGui.getMainWindow()): + + QtGui.QDialog.__init__(self,parent) + layout = QtGui.QVBoxLayout(self) + + # nice text editor widget for editing the gcode + self.editor = QtGui.QTextEdit() + font = QtGui.QFont() + font.setFamily("Courier") + font.setFixedPitch(True) + font.setPointSize(11) + self.editor.setFont(font) + self.editor.setText("G01 X55 Y4.5 F300.0") + self.highlighter = GCodeHighlighter(self.editor.document()) + layout.addWidget(self.editor) + + # OK and Cancel buttons + self.buttons = QtGui.QDialogButtonBox( + QtGui.QDialogButtonBox.Ok, + #QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel, + QtCore.Qt.Horizontal, self) + layout.addWidget(self.buttons) + self.buttons.accepted.connect(self.accept) + #self.buttons.rejected.connect(self.reject) + + +def show(obj): + + "show(obj): shows the G-code data of the given Path object in a dialog" + + if hasattr(obj,"Path"): + if obj.Path: + dia = GCodeEditorDialog() + dia.editor.setText(obj.Path.toGCode()) + result = dia.exec_() + # exec_() returns 0 or 1 depending on the button pressed (Ok or Cancel) + #if result: + # return dia.editor.toPlainText() + #else: + # return inputstring + + +class CommandPathInspect: + + + def GetResources(self): + return {'Pixmap' : 'Path-Inspect', + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Inspect","Inspect"), + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Inspect","Inspects the G-code contents of a path")} + + def IsActive(self): + return not FreeCAD.ActiveDocument is None + + def Activated(self): + # check that the selection contains exactly what we want + selection = FreeCADGui.Selection.getSelection() + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathInspect","Please select exactly one path object\n")) + return + if not(selection[0].isDerivedFrom("Path::Feature")): + FreeCAD.Console.PrintError(translate("PathInspect","Please select exactly one path object\n")) + return + + # if everything is ok, execute + FreeCADGui.addModule("PathScripts.PathInspect") + FreeCADGui.doCommand('PathScripts.PathInspect.show(FreeCAD.ActiveDocument.' + selection[0].Name + ')') + + +if FreeCAD.GuiUp: + # register the FreeCAD command + FreeCADGui.addCommand('Path_Inspect',CommandPathInspect()) +