diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index d933278e9..098c6f7b2 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -131,9 +131,15 @@ class ObjectProfile: obj.ClearanceHeight = bb.ZMax + 5.0 obj.SafeHeight = bb.ZMax + 3.0 - if fbb.ZMax < bb.ZMax: - obj.FinalDepth = fbb.ZMax - else: + if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face + obj.FinalDepth = bb.ZMin + elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut + obj.FinalDepth = fbb.ZMin + elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall + obj.FinalDepth = fbb.ZMin + elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf + obj.FinalDepth = fbb.ZMin + else: #catch all obj.FinalDepth = bb.ZMin except: obj.StartDepth = 5.0 @@ -273,23 +279,21 @@ print "y - " + str(point.y) wires.append(h.OuterWire) tempshell = Part.makeShell(vfaces) - slices = tempshell.slice(FreeCAD.Base.Vector(0, 0, 1), 1.5) + slices = tempshell.slice(FreeCAD.Base.Vector(0, 0, 1), tempshell.CenterOfMass.z ) wires = wires + slices for wire in wires: - edgelist = wire.Edges - edgelist = Part.__sortEdges__(edgelist) - if obj.Algorithm == "OCC Native": output += self._buildPathOCC(obj, wire) - else: try: import area except: FreeCAD.Console.PrintError(translate("Path", "libarea needs to be installed for this command to work.\n")) return + edgelist = wire.Edges + edgelist = Part.__sortEdges__(edgelist) output += self._buildPathLibarea(obj, edgelist) if obj.Active: @@ -496,6 +500,48 @@ class TaskPanel: 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.useCompensation.setChecked(self.obj.UseComp) + self.form.useStartPoint.setChecked(self.obj.UseStartPoint) + self.form.useEndPoint.setChecked(self.obj.UseEndPoint) + + index = self.form.algorithmSelect.findText( + self.obj.Algorithm, QtCore.Qt.MatchFixedString) + if index >= 0: + self.form.algorithmSelect.setCurrentIndex(index) + + index = self.form.cutSide.findText( + self.obj.Side, QtCore.Qt.MatchFixedString) + if index >= 0: + self.form.cutSide.setCurrentIndex(index) + + index = self.form.direction.findText( + self.obj.Direction, QtCore.Qt.MatchFixedString) + if index >= 0: + self.form.direction.setCurrentIndex(index) + + for i in self.obj.Base: + self.form.baseList.addItem(i[0].Name + "." + i[1]) + + 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 @@ -505,16 +551,23 @@ class TaskPanel: # check that the selection contains exactly what we want selection = FreeCADGui.Selection.getSelectionEx() - if not len(selection) >= 1: - FreeCAD.Console.PrintError(translate("PathProject", "Please select at least one profileable object\n")) + if len(selection) != 1: + FreeCAD.Console.PrintError(translate("PathProject", "Please select only faces from one solid\n")) return - for s in selection: - if s.HasSubObjects: - for i in s.SubElementNames: - self.obj.Proxy.addprofilebase(self.obj, s.Object, i) - else: - self.obj.Proxy.addprofilebase(self.obj, s.Object) - self.setupUi() # defaults may have changed. Reload. + sel = selection[0] + if not sel.HasSubObjects: + FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) + return + if not selection[0].SubObjects[0].ShapeType == "Face": + FreeCAD.Console.PrintError(translate("PathProject", "Please select faces from one solid\n")) + return + + # if s.HasSubObjects: + for i in sel.SubElementNames: + self.obj.Proxy.addprofilebase(self.obj, sel.Object, i) + #else: + #self.obj.Proxy.addprofilebase(self.obj, s.Object) + self.setFields() # defaults may have changed. Reload. self.form.baseList.clear() for i in self.obj.Base: self.form.baseList.addItem(i[0].Name + "." + i[1]) @@ -644,47 +697,8 @@ class TaskPanel: self.updating = False return + def setupUi(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.useCompensation.setChecked(self.obj.UseComp) - self.form.useStartPoint.setChecked(self.obj.UseStartPoint) - self.form.useEndPoint.setChecked(self.obj.UseEndPoint) - - index = self.form.algorithmSelect.findText( - self.obj.Algorithm, QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.algorithmSelect.setCurrentIndex(index) - - index = self.form.cutSide.findText( - self.obj.Side, QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.cutSide.setCurrentIndex(index) - - index = self.form.direction.findText( - self.obj.Direction, QtCore.Qt.MatchFixedString) - if index >= 0: - self.form.direction.setCurrentIndex(index) - - for i in self.obj.Base: - self.form.baseList.addItem(i[0].Name + "." + i[1]) - - 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) # Connect Signals and Slots # Base Controls @@ -721,6 +735,13 @@ class TaskPanel: self.form.addTag.clicked.connect(self.addElement) self.form.deleteTag.clicked.connect(self.removeElement) + self.setFields() + + sel = FreeCADGui.Selection.getSelectionEx() + if len(sel) != 0 and sel[0].HasSubObjects: +# if sel[0].SubObjects[0].ShapeType == "Face": + self.addBase() + class SelObserver: def __init__(self): diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 2b5c6e3a9..897be775b 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -137,20 +137,21 @@ def filterArcs(arcEdge): if isinstance(s.Curve, Part.Circle): splitlist = [] angle = abs(s.LastParameter - s.FirstParameter) - overhalfcircle = False + # overhalfcircle = False goodarc = False if (angle > math.pi): - overhalfcircle = True + pass + # overhalfcircle = True else: goodarc = True if not goodarc: arcstpt = s.valueAt(s.FirstParameter) arcmid = s.valueAt( (s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter) - arcquad1 = s.valueAt( - (s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter) # future midpt for arc1 - arcquad2 = s.valueAt( - (s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter) # future midpt for arc2 + arcquad1 = s.valueAt((s.LastParameter - s.FirstParameter) + * 0.25 + s.FirstParameter) # future midpt for arc1 + arcquad2 = s.valueAt((s.LastParameter - s.FirstParameter) + * 0.75 + s.FirstParameter) # future midpt for arc2 arcendpt = s.valueAt(s.LastParameter) # reconstruct with 2 arcs arcseg1 = Part.ArcOfCircle(arcstpt, arcquad1, arcmid) @@ -170,8 +171,7 @@ def filterArcs(arcEdge): def reverseEdge(e): if geomType(e) == "Circle": arcstpt = e.valueAt(e.FirstParameter) - arcmid = e.valueAt( - (e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter) + arcmid = e.valueAt((e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter) arcendpt = e.valueAt(e.LastParameter) arcofCirc = Part.ArcOfCircle(arcendpt, arcmid, arcstpt) newedge = arcofCirc.toShape() @@ -183,9 +183,7 @@ def reverseEdge(e): return newedge -def convert( - toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, - vf=1.0, hf=2.0): +def convert(toolpath, Side, radius, clockwise=False, Z=0.0, firstedge=None, vf=1.0, hf=2.0): '''convert(toolpath,Side,radius,clockwise=False,Z=0.0,firstedge=None) Converts lines and arcs to G1,G2,G3 moves. Returns a string.''' last = None output = "" @@ -289,8 +287,8 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): geomType(e) == "BezierCurve" or \ geomType(e) == "Ellipse": edgelist.append(Part.Wire(curvetowire(e, (SegLen)))) - newwire = Part.Wire(edgelist) + if Side == 'Left': # we use the OCC offset feature offset = newwire.makeOffset(radius) # tool is outside line @@ -308,6 +306,9 @@ def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): def MakePath(wire, Side, radius, clockwise, ZClearance, StepDown, ZStart, ZFinalDepth, firstedge=None, PathClosed=True, SegLen=0.5, VertFeed=1.0, HorizFeed=2.0): ''' makes the path - just a simple profile for now ''' offset = SortPath(wire, Side, radius, clockwise, firstedge, SegLen=0.5) + if len(offset.Edges) == 0: + return "" + toolpath = offset.Edges[:] paths = "" paths += "G0 Z" + str(ZClearance) + "\n" @@ -366,10 +367,10 @@ def getLastTool(obj): def getLastToolLoad(obj): # This walks up the hierarchy and tries to find the closest preceding - # ToolLoadOject (tlo). + # toolchange. import PathScripts - tlo = None + tc = None lastfound = None try: @@ -379,30 +380,30 @@ def getLastToolLoad(obj): parent = None while parent is not None: + sibs = parent.Group for g in sibs: if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool): lastfound = g if g == child: - tlo = lastfound + tc = lastfound - if tlo is None: + if tc is None: try: child = parent - parent = child.InList[0] + parent = parent.InList[0] except: parent = None else: - return tlo + return tc - if tlo is None: - for g in FreeCAD.ActiveDocument.Objects: # Look in top level - if hasattr(g, "Proxy"): - if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool): - lastfound = g + if tc is None: + for g in FreeCAD.ActiveDocument.Objects: # top level object + if isinstance(g.Proxy, PathScripts.PathLoadTool.LoadTool): + lastfound = g if g == obj: - tlo = lastfound - return tlo + tc = lastfound + return tc def getTool(obj, number=0): @@ -482,177 +483,6 @@ def frange(start, stop, step, finish): return x -def rapid(x=None, y=None, z=None): - """ Returns gcode string to perform a rapid move.""" - retstr = "G00" - if (x is not None) or (y is not None) or (z is not None): - if (x is not None): - retstr += " X" + str("%.4f" % x) - if (y is not None): - retstr += " Y" + str("%.4f" % y) - if (z is not None): - retstr += " Z" + str("%.4f" % z) - else: - return "" - return retstr + "\n" - -def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0): - """ Return gcode string to perform a linear feed.""" - global feedxy - retstr = "G01 F" - if(x is None) and (y is None): - retstr += str("%.4f" % horizFeed) - else: - retstr += str("%.4f" % vertFeed) - - if (x is not None) or (y is not None) or (z is not None): - if (x is not None): - retstr += " X" + str("%.4f" % x) - if (y is not None): - retstr += " Y" + str("%.4f" % y) - if (z is not None): - retstr += " Z" + str("%.4f" % z) - else: - return "" - return retstr + "\n" - -def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False): - """ - Return gcode string to perform an arc. - - Assumes XY plane or helix around Z - Don't worry about starting Z- assume that's dealt with elsewhere - If start/end radii aren't within eps, abort. - - cx, cy -- arc center coordinates - sx, sy -- arc start coordinates - ex, ey -- arc end coordinates - ez -- ending Z coordinate. None unless helix. - horizFeed -- horiz feed speed - ccw -- arc direction - """ - - eps = 0.01 - if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps: - print "ERROR: Illegal arc: Start and end radii not equal" - return "" - - retstr = "" - if ccw: - retstr += "G03 F" + str(horizFeed) - else: - retstr += "G02 F" + str(horizFeed) - - retstr += " X" + str("%.4f" % ex) + " Y" + str("%.4f" % ey) - - if ez is not None: - retstr += " Z" + str("%.4f" % ez) - - retstr += " I" + str("%.4f" % (cx - sx)) + " J" + str("%.4f" % (cy - sy)) - - return retstr + "\n" - -def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed): - """ - Return gcode string to perform helical entry move. - - plungePos -- vector of the helical entry location - destZ -- the lowest Z position or milling level - startZ -- Starting Z position for helical move - rampangle -- entry angle - toold -- tool diameter - plungeR -- the radius of the entry helix - """ - # toold = self.radius * 2 - - helixCmds = "(START HELICAL PLUNGE)\n" - if(plungePos is None): - raise Exception("Helical plunging requires a position!") - return None - - helixX = plungePos.x + toold/2 * plungeR - helixY = plungePos.y - - helixCirc = math.pi * toold * plungeR - dzPerRev = math.sin(rampangle/180. * math.pi) * helixCirc - - # Go to the start of the helix position - helixCmds += rapid(helixX, helixY) - helixCmds += rapid(z=startZ) - - # Helix as required to get to the requested depth - lastZ = startZ - curZ = max(startZ-dzPerRev, destZ) - done = False - while not done: - done = (curZ == destZ) - # NOTE: FreeCAD doesn't render this, but at least LinuxCNC considers it valid - # helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX, helixY, ez = curZ, ccw=True) - - # Use two half-helixes; FreeCAD renders that correctly, - # and it fits with the other code breaking up 360-degree arcs - helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX - toold * plungeR, helixY, horizFeed, ez=(curZ + lastZ)/2., ccw=True) - helixCmds += arc(plungePos.x, plungePos.y, helixX - toold * plungeR, helixY, helixX, helixY, horizFeed, ez=curZ, ccw=True) - lastZ = curZ - curZ = max(curZ - dzPerRev, destZ) - - return helixCmds - -def rampPlunge(edge, rampangle, destZ, startZ): - """ - Return gcode string to linearly ramp down to milling level. - - edge -- edge to follow - rampangle -- entry angle - destZ -- Final Z depth - startZ -- Starting Z depth - - FIXME: This ramps along the first edge, assuming it's long - enough, NOT just wiggling back and forth by ~0.75 * toolD. - Not sure if that's any worse, but it's simpler - I think this should be changed to be limited to a maximum ramp size. Otherwise machine time will get longer than it needs to be. - """ - - rampCmds = "(START RAMP PLUNGE)\n" - if(edge is None): - raise Exception("Ramp plunging requires an edge!") - return None - - sPoint = edge.Vertexes[0].Point - ePoint = edge.Vertexes[1].Point - # Evidently edges can get flipped- pick the right one in this case - # FIXME: This is iffy code, based on what already existed in the "for vpos ..." loop below - if ePoint == sPoint: - # print "FLIP" - ePoint = edge.Vertexes[-1].Point - - rampDist = edge.Length - rampDZ = math.sin(rampangle/180. * math.pi) * rampDist - - rampCmds += rapid(sPoint.x, sPoint.y) - rampCmds += rapid(z=startZ) - - # Ramp down to the requested depth - # FIXME: This might be an arc, so handle that as well - - curZ = max(startZ-rampDZ, destZ) - done = False - while not done: - done = (curZ == destZ) - - # If it's an arc, handle it! - if isinstance(edge.Curve, Part.Circle): - raise Exception("rampPlunge: Screw it, not handling an arc.") - # Straight feed! Easy! - else: - rampCmds += feed(ePoint.x, ePoint.y, curZ) - rampCmds += feed(sPoint.x, sPoint.y) - - curZ = max(curZ - rampDZ, destZ) - - return rampCmds - - class depth_params: