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.svgicons/Path-Machine.svgicons/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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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())
+