Merge branch 'master' of https://github.com/FreeCAD/FreeCAD into deburr+dressup

This commit is contained in:
Patrick Felixberger
2020-03-31 17:18:06 +02:00
166 changed files with 11556 additions and 4468 deletions

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
@@ -49,6 +48,7 @@ PathLog.setLevel(LOGLEVEL, PathLog.thisModule())
if LOGLEVEL is PathLog.Level.DEBUG:
PathLog.trackModule()
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -66,7 +66,6 @@ class ObjectOp(PathOp.ObjectOp):
'''opFeatures(obj) ... returns the base features supported by all Path.Area based operations.
The standard feature list is OR'ed with the return value of areaOpFeatures().
Do not overwrite, implement areaOpFeatures(obj) instead.'''
# return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureRotation
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureStepDown | PathOp.FeatureHeights | PathOp.FeatureStartPoint | self.areaOpFeatures(obj) | PathOp.FeatureCoolant
def areaOpFeatures(self, obj):
@@ -304,8 +303,6 @@ class ObjectOp(PathOp.ObjectOp):
pathParams['return_end'] = True
# Note that emitting preambles between moves breaks some dressups and prevents path optimization on some controllers
pathParams['preamble'] = False
#if not self.areaOpRetractTool(obj):
# pathParams['threshold'] = 2.001 * self.radius
if self.endVector is None:
V = hWire.Wires[0].Vertexes
@@ -374,12 +371,6 @@ class ObjectOp(PathOp.ObjectOp):
obj.ClearanceHeight.Value = strDep + self.clrOfset
obj.SafeHeight.Value = strDep + self.safOfst
#if self.initWithRotation is False:
# if obj.FinalDepth.Value == obj.OpFinalDepth.Value:
# obj.FinalDepth.Value = finDep
# if obj.StartDepth.Value == obj.OpStartDepth.Value:
# obj.StartDepth.Value = strDep
# Create visual axes when debugging.
if PathLog.getLevel(PathLog.thisModule()) == 4:
self.visualAxis()
@@ -467,10 +458,14 @@ class ObjectOp(PathOp.ObjectOp):
# Rotate Model to correct angle
ppCmds.insert(0, Path.Command('G0', {axisOfRot: angle, 'F': self.axialRapid}))
# Raise cutter to safe depth and return index to starting position
ppCmds.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
if axis != nextAxis:
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
# Raise cutter to safe height
ppCmds.insert(0, Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
# Return index to starting position if axis of rotation changes.
if numShapes > 1:
if ns != numShapes - 1:
if axis != nextAxis:
ppCmds.append(Path.Command('G0', {axisOfRot: 0.0, 'F': self.axialRapid}))
# Eif
# Save gcode commands to object command list
@@ -483,9 +478,43 @@ class ObjectOp(PathOp.ObjectOp):
# Raise cutter to safe height and rotate back to original orientation
if self.rotateFlag is True:
resetAxis = False
lastJobOp = None
nextJobOp = None
opIdx = 0
JOB = PathUtils.findParentJob(obj)
jobOps = JOB.Operations.Group
numJobOps = len(jobOps)
for joi in range(0, numJobOps):
jo = jobOps[joi]
if jo.Name == obj.Name:
opIdx = joi
lastOpIdx = opIdx - 1
nextOpIdx = opIdx + 1
if lastOpIdx > -1:
lastJobOp = jobOps[lastOpIdx]
if nextOpIdx < numJobOps:
nextJobOp = jobOps[nextOpIdx]
if lastJobOp is not None:
if hasattr(lastJobOp, 'EnableRotation'):
PathLog.debug('Last Op, {}, has `EnableRotation` set to {}'.format(lastJobOp.Label, lastJobOp.EnableRotation))
if lastJobOp.EnableRotation != obj.EnableRotation:
resetAxis = True
if ns == numShapes - 1: # If last shape, check next op EnableRotation setting
if nextJobOp is not None:
if hasattr(nextJobOp, 'EnableRotation'):
PathLog.debug('Next Op, {}, has `EnableRotation` set to {}'.format(nextJobOp.Label, nextJobOp.EnableRotation))
if nextJobOp.EnableRotation != obj.EnableRotation:
resetAxis = True
# Raise to safe height if rotation activated
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
# reset rotational axises if necessary
if resetAxis is True:
self.commandlist.append(Path.Command('G0', {'A': 0.0, 'F': self.axialRapid}))
self.commandlist.append(Path.Command('G0', {'B': 0.0, 'F': self.axialRapid}))
self.useTempJobClones('Delete') # Delete temp job clone group and contents
self.guiMessage('title', None, show=True) # Process GUI messages to user
@@ -531,10 +560,8 @@ class ObjectOp(PathOp.ObjectOp):
Determine rotational radii for 4th-axis rotations, for clearance/safe heights '''
parentJob = PathUtils.findParentJob(obj)
# bb = parentJob.Stock.Shape.BoundBox
xlim = 0.0
ylim = 0.0
# zlim = 0.0
# Determine boundbox radius based upon xzy limits data
if math.fabs(self.stockBB.ZMin) > math.fabs(self.stockBB.ZMax):

View File

@@ -25,9 +25,12 @@ import FreeCAD
import FreeCADGui
import Path
from PySide import QtCore
from copy import copy
__doc__ = """Path Custom object and FreeCAD command"""
movecommands = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03']
# Qt translation handling
def translate(context, text, disambig=None):
@@ -35,10 +38,14 @@ def translate(context, text, disambig=None):
class ObjectCustom:
def __init__(self, obj):
obj.addProperty("App::PropertyStringList", "Gcode", "Path",
QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"))
obj.addProperty("App::PropertyLink", "ToolController", "Path",
QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path"))
obj.addProperty("App::PropertyPlacement", "Offset", "Path",
"Placement Offset")
def __init__(self,obj):
obj.addProperty("App::PropertyStringList", "Gcode", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"))
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("PathCustom", "The tool controller that will be used to calculate the path"))
obj.Proxy = self
def __getstate__(self):
@@ -48,13 +55,22 @@ class ObjectCustom:
return None
def execute(self, obj):
newpath = Path.Path()
if obj.Gcode:
s = ""
for l in obj.Gcode:
s += str(l)
if s:
path = Path.Path(s)
obj.Path = path
newcommand = Path.Command(str(l))
if newcommand.Name in movecommands:
if 'X' in newcommand.Parameters:
newcommand.x += obj.Offset.Base.x
if 'Y' in newcommand.Parameters:
newcommand.y += obj.Offset.Base.y
if 'Z' in newcommand.Parameters:
newcommand.z += obj.Offset.Base.z
newpath.insertCommand(newcommand)
obj.Path=newpath
class CommandPathCustom:
@@ -75,7 +91,7 @@ class CommandPathCustom:
FreeCAD.ActiveDocument.openTransaction("Create Custom Path")
FreeCADGui.addModule("PathScripts.PathCustom")
FreeCADGui.addModule("PathScripts.PathUtils")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Custom")')
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")')
FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)')
FreeCADGui.doCommand('obj.ViewObject.Proxy = 0')
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
@@ -86,4 +102,4 @@ class CommandPathCustom:
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Custom', CommandPathCustom())
FreeCADGui.addCommand('Path_Custom', CommandPathCustom())

View File

@@ -0,0 +1,343 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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 *
# * *
# * Bilinear interpolation code modified heavily from the interpolation *
# * library https://github.com/pmav99/interpolation *
# * Copyright (c) 2013 by Panagiotis Mavrogiorgos *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import Part
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathUtils as PathUtils
from PySide import QtCore, QtGui
"""Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38
"""
LOG_MODULE = PathLog.thisModule()
if False:
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
else:
PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE)
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']
rapidcommands = ['G0', 'G00']
arccommands = ['G2', 'G3', 'G02', 'G03']
class ObjectDressup:
def __init__(self, obj):
obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify"))
obj.addProperty("App::PropertyFile", "probefile", "ProbeData", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing."))
obj.Proxy = self
obj.addProperty("Part::PropertyPartShape", "interpSurface", "Path")
obj.setEditorMode('interpSurface', 2) # hide
obj.addProperty("App::PropertyDistance", "ArcInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Deflection distance for arc interpolation"))
obj.addProperty("App::PropertyDistance", "SegInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrectp", "break segments into smaller segments of this length."))
obj.ArcInterpolate = 0.1
obj.SegInterpolate = 1.0
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def onChanged(self, fp, prop):
if str(prop) == "probefile":
self._loadFile(fp, fp.probefile)
def _bilinearInterpolate(self, surface, x, y):
p1 = FreeCAD.Vector(x, y, 100.0)
p2 = FreeCAD.Vector(x, y, -100.0)
vertical_line = Part.Line(p1, p2)
points, curves = vertical_line.intersectCS(surface)
return points[0].Z
def _loadFile(self, obj, filename):
if filename == "":
return
f1 = open(filename, 'r')
try:
pointlist = []
for line in f1.readlines():
w = line.split()
xval = round(float(w[0]), 2)
yval = round(float(w[1]), 2)
zval = round(float(w[2]), 2)
pointlist.append([xval, yval, zval])
PathLog.debug(pointlist)
cols = list(zip(*pointlist))
PathLog.debug("cols: {}".format(cols))
yindex = list(sorted(set(cols[1])))
PathLog.debug("yindex: {}".format(yindex))
array = []
for y in yindex:
points = sorted([p for p in pointlist if p[1] == y])
inner = []
for p in points:
inner.append(FreeCAD.Vector(p[0], p[1], p[2]))
array.append(inner)
intSurf = Part.BSplineSurface()
intSurf.interpolate(array)
obj.interpSurface = intSurf.toShape()
except Exception:
raise ValueError("File does not contain appropriate point data")
def execute(self, obj):
sampleD = obj.SegInterpolate.Value
curveD = obj.ArcInterpolate.Value
if obj.interpSurface.isNull(): # No valid probe data. return unchanged path
obj.Path = obj.Base.Path
return
surface = obj.interpSurface.toNurbs().Faces[0].Surface
if obj.Base:
if obj.Base.isDerivedFrom("Path::Feature"):
if obj.Base.Path:
if obj.Base.Path.Commands:
pathlist = obj.Base.Path.Commands
newcommandlist = []
currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0}
for c in pathlist:
PathLog.debug(c)
PathLog.debug(" curLoc:{}".format(currLocation))
newparams = dict(c.Parameters)
zval = newparams.get("Z", currLocation['Z'])
if c.Name in movecommands:
curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z'])
arcwire = PathGeom.edgeForCmd(c, curVec)
if arcwire is None:
continue
if c.Name in arccommands:
pointlist = arcwire.discretize(Deflection=curveD)
else:
disc_number = int(arcwire.Length / sampleD)
if disc_number > 1:
pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD))
else:
pointlist = [v.Point for v in arcwire.Vertexes]
for point in pointlist:
offset = self._bilinearInterpolate(surface, point.x, point.y)
newcommand = Path.Command("G1", {'X': point.x, 'Y': point.y, 'Z': point.z + offset})
newcommandlist.append(newcommand)
currLocation.update(newcommand.Parameters)
currLocation['Z'] = zval
else:
# Non Feed Command
newcommandlist.append(c)
currLocation.update(c.Parameters)
path = Path.Path(newcommandlist)
obj.Path = path
class TaskPanel:
def __init__(self, obj):
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ZCorrectEdit.ui")
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Edit Z Correction Dress-up"))
self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface")
self.interpshape.Shape = obj.interpSurface
self.interpshape.ViewObject.Transparency = 60
self.interpshape.ViewObject.ShapeColor = (1.00000, 1.00000, 0.01961)
self.interpshape.ViewObject.Selectable = False
stock = PathUtils.findParentJob(obj).Stock
self.interpshape.Placement.Base.z = stock.Shape.BoundBox.ZMax
def reject(self):
FreeCAD.ActiveDocument.abortTransaction()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def accept(self):
self.getFields()
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.removeObject(self.interpshape.Name)
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
FreeCAD.ActiveDocument.recompute()
def getFields(self):
self.obj.Proxy.execute(self.obj)
def updateUI(self):
if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
for obj in FreeCAD.ActiveDocument.Objects:
if obj.Name.startswith('Shape'):
FreeCAD.ActiveDocument.removeObject(obj.Name)
print('object name %s' % self.obj.Name)
if hasattr(self.obj.Proxy, "shapes"):
PathLog.info("showing shapes attribute")
for shapes in self.obj.Proxy.shapes.itervalues():
for shape in shapes:
Part.show(shape)
else:
PathLog.info("no shapes attribute found")
def updateModel(self):
self.getFields()
self.updateUI()
FreeCAD.ActiveDocument.recompute()
def setFields(self):
self.form.ProbePointFileName.setText(self.obj.probefile)
self.updateUI()
def open(self):
pass
def setupUi(self):
self.setFields()
# now that the form is filled, setup the signal handlers
self.form.ProbePointFileName.editingFinished.connect(self.updateModel)
self.form.SetProbePointFileName.clicked.connect(self.SetProbePointFileName)
def SetProbePointFileName(self):
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Probe Point File"), None, translate("Path_Probe", "All Files (*.*)"))
if filename and filename[0]:
self.obj.probefile = str(filename[0])
self.setFields()
class ViewProviderDressup:
def __init__(self, vobj):
vobj.Proxy = self
def attach(self, vobj):
self.obj = vobj.Object
if self.obj and self.obj.Base:
for i in self.obj.Base.InList:
if hasattr(i, "Group"):
group = i.Group
for g in group:
if g.Name == self.obj.Base.Name:
group.remove(g)
i.Group = group
return
def claimChildren(self):
return [self.obj.Base]
def setEdit(self, vobj, mode=0):
FreeCADGui.Control.closeDialog()
panel = TaskPanel(vobj.Object)
FreeCADGui.Control.showDialog(panel)
panel.setupUi()
return True
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def onDelete(self, arg1=None, arg2=None):
'''this makes sure that the base operation is added back to the project and visible'''
FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True
job = PathUtils.findParentJob(arg1.Object)
job.Proxy.addOperation(arg1.Object.Base)
arg1.Object.Base = None
return True
class CommandPathDressup:
def GetResources(self):
return {'Pixmap': 'Path-Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Z Depth Correction Dress-up"),
'Accel': "",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Use Probe Map to correct Z depth")}
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):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n"))
return
if not selection[0].isDerivedFrom("Path::Feature"):
FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n"))
return
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object"))
return
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Create Dress-up"))
FreeCADGui.addModule("PathScripts.PathDressupZCorrect")
FreeCADGui.addModule("PathScripts.PathUtils")
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")')
FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ObjectDressup(obj)')
FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name)
FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)')
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False')
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_DressupZCorrect', CommandPathDressup())
FreeCAD.Console.PrintLog("Loading PathDressup... done\n")

View File

@@ -99,6 +99,8 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
obj.PeckEnabled = self.form.peckEnabled.isChecked()
if obj.ExtraOffset != str(self.form.ExtraOffset.currentText()):
obj.ExtraOffset = str(self.form.ExtraOffset.currentText())
if obj.EnableRotation != str(self.form.enableRotation.currentText()):
obj.EnableRotation = str(self.form.enableRotation.currentText())
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
@@ -122,6 +124,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.EnableRotation, self.form.enableRotation)
def getSignalsForUpdate(self, obj):
@@ -137,6 +140,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.ExtraOffset.currentIndexChanged)
signals.append(self.form.enableRotation.currentIndexChanged)
return signals

View File

@@ -313,6 +313,9 @@ def edgeForCmd(cmd, startPoint):
"""edgeForCmd(cmd, startPoint).
Returns an Edge representing the given command, assuming a given startPoint."""
PathLog.debug("cmd: {}".format(cmd))
PathLog.debug("startpoint {}".format(startPoint))
endPoint = commandEndPoint(cmd, startPoint)
if (cmd.Name in CmdMoveStraight) or (cmd.Name in CmdMoveRapid):
if pointsCoincide(startPoint, endPoint):
@@ -343,6 +346,10 @@ def edgeForCmd(cmd, startPoint):
if isRoughly(startPoint.z, endPoint.z):
midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R
PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y))
PathLog.debug("StartPoint:{}".format(startPoint))
PathLog.debug("MidPoint:{}".format(midPoint))
PathLog.debug("EndPoint:{}".format(endPoint))
return Part.Edge(Part.Arc(startPoint, midPoint, endPoint))
# It's a Helix

View File

@@ -35,6 +35,7 @@ else:
Processed = False
def Startup():
global Processed # pylint: disable=global-statement
if not Processed:
@@ -51,6 +52,7 @@ def Startup():
from PathScripts import PathDressupPathBoundaryGui
from PathScripts import PathDressupTagGui
from PathScripts import PathDressupLeadInOut
from PathScripts import PathDressupZCorrect
from PathScripts import PathDrillingGui
from PathScripts import PathEngraveGui
from PathScripts import PathFixture
@@ -61,6 +63,7 @@ def Startup():
from PathScripts import PathPocketGui
from PathScripts import PathPocketShapeGui
from PathScripts import PathPost
from PathScripts import PathProbeGui
from PathScripts import PathProfileContourGui
from PathScripts import PathProfileEdgesGui
from PathScripts import PathProfileFacesGui
@@ -69,12 +72,13 @@ def Startup():
from PathScripts import PathSimpleCopy
from PathScripts import PathSimulatorGui
from PathScripts import PathStop
# from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency
from PathScripts import PathToolController
from PathScripts import PathToolControllerGui
from PathScripts import PathToolLibraryManager
from PathScripts import PathToolLibraryEditor
from PathScripts import PathUtilsGui
# from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency
Processed = True
else:
PathLog.debug('Skipping PathGui initialisation')

View File

@@ -99,7 +99,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
'''areaOpShapes(obj) ... return top face'''
# Facing is done either against base objects
holeShape = None
if obj.Base:
PathLog.debug("obj.Base: {}".format(obj.Base))
faces = []
@@ -147,17 +147,17 @@ class ObjectFace(PathPocketBase.ObjectPocket):
# Find the correct shape depending on Boundary shape.
PathLog.debug("Boundary Shape: {}".format(obj.BoundaryShape))
bb = planeshape.BoundBox
# Apply offset for clearing edges
offset = 0;
if obj.ClearEdges == True:
offset = self.radius + 0.1
bb.XMin = bb.XMin - offset
bb.YMin = bb.YMin - offset
bb.XMax = bb.XMax + offset
bb.YMax = bb.YMax + offset
if obj.BoundaryShape == 'Boundbox':
bbperim = Part.makeBox(bb.XLength, bb.YLength, 1, FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Vector(0, 0, 1))
env = PathUtils.getEnvelope(partshape=bbperim, depthparams=self.depthparams)
@@ -170,7 +170,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
elif obj.BoundaryShape == 'Stock':
stock = PathUtils.findParentJob(obj).Stock.Shape
env = stock
if obj.ExcludeRaisedAreas is True and oneBase[1] is True:
includedFaces = self.getAllIncludedFaces(oneBase[0], stock, faceZ=minHeight)
if len(includedFaces) > 0:
@@ -269,7 +269,6 @@ def SetupProperties():
setup.append("BoundaryShape")
setup.append("ExcludeRaisedAreas")
setup.append("ClearEdges")
return setup
@@ -278,5 +277,4 @@ def Create(name, obj=None):
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectFace(obj, name)
return obj

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * Copyright (c) 2020 Schildkroet *
# * *
# * This program is free software; you can redistribute it and/or modify *
@@ -42,7 +41,7 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Class and implementation of shape based Pocket operation."
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
@@ -435,7 +434,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if obj.Base:
PathLog.debug('Processing... obj.Base')
self.removalshapes = [] # pylint: disable=attribute-defined-outside-init
# ----------------------------------------------------------------------
if obj.EnableRotation == 'Off':
stock = PathUtils.findParentJob(obj).Stock
for (base, subList) in obj.Base:
@@ -450,11 +449,11 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
(isLoop, norm, surf) = self.checkForFacesLoop(base, subsList)
if isLoop is True:
PathLog.info("Common Surface.Axis or normalAt() value found for loop faces.")
PathLog.debug("Common Surface.Axis or normalAt() value found for loop faces.")
rtn = False
subCount += 1
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.info("angle: {}; axis: {}".format(angle, axis))
PathLog.debug("angle: {}; axis: {}".format(angle, axis))
if rtn is True:
faceNums = ""
@@ -471,15 +470,17 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
rtn = False
PathLog.warning(translate("PathPocketShape", "Face appears to NOT be horizontal AFTER rotation applied."))
break
if rtn is False:
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
PathLog.debug("Applying the inverse angle.")
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if angle < -180.0:
if angle < 0.0:
angle += 360.0
tup = clnBase, subsList, angle, axis, clnStock
@@ -518,6 +519,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
(norm, surf) = self.getFaceNormAndSurf(face)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("initial {}".format(praInfo))
if rtn is True:
faceNum = sub.replace('Face', '')
@@ -525,20 +527,31 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = clnBase.Shape.getElement(sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
(rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("follow-up {}".format(praInfo2))
if abs(praAngle) == 180.0:
rtn = False
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp is False')
angle -= 180.0
if rtn is True:
PathLog.debug("Face not aligned after initial rotation.")
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
PathLog.debug("Applying the inverse angle.")
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
PathLog.warning(translate("Path", "Consider toggling the InverseAngle property and recomputing the operation."))
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp is False')
angle += 180.0
else:
PathLog.debug("Face appears to be oriented correctly.")
if angle < -180.0:
if angle < 0.0:
angle += 360.0
tup = clnBase, [sub], angle, axis, clnStock
@@ -650,8 +663,9 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
if shpZMin > obj.FinalDepth.Value:
afD = shpZMin
if sD <= afD:
PathLog.error('Start Depth is lower than face depth.')
sD = afD + 1.0
msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to ')
PathLog.warning(msg + ' {} mm.'.format(sD))
else:
face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - shpZMin))

View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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 *
# * *
# ***************************************************************************
from __future__ import print_function
import FreeCAD
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
from PySide import QtCore
__title__ = "Path Probing Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Path Probing operation."
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class ObjectProbing(PathOp.ObjectOp):
'''Proxy object for Probing operation.'''
def opFeatures(self, obj):
'''opFeatures(obj) ... Probing works on the stock object.'''
return PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureTool
def initOperation(self, obj):
obj.addProperty("App::PropertyLength", "Xoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe"))
obj.addProperty("App::PropertyLength", "Yoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe"))
obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction"))
obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction"))
obj.addProperty("App::PropertyFile", "OutputFileName", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The output location for the probe data to be written"))
def nextpoint(self, startpoint=0.0, endpoint=0.0, count=3):
curstep = 0
dist = (endpoint - startpoint) / (count - 1)
while curstep <= count-1:
yield startpoint + (curstep * dist)
curstep += 1
def opExecute(self, obj):
'''opExecute(obj) ... generate probe locations.'''
PathLog.track()
self.commandlist.append(Path.Command("(Begin Probing)"))
stock = PathUtils.findParentJob(obj).Stock
bb = stock.Shape.BoundBox
openstring = '(PROBEOPEN {})'.format(obj.OutputFileName)
self.commandlist.append(Path.Command(openstring))
self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY):
for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX):
self.commandlist.append(Path.Command("G0", {"X": x + obj.Xoffset.Value, "Y": y + obj.Yoffset.Value, "Z": obj.SafeHeight.Value}))
self.commandlist.append(Path.Command("G38.2", {"Z": obj.FinalDepth.Value, "F": obj.ToolController.VertFeed.Value}))
self.commandlist.append(Path.Command("G0", {"Z": obj.SafeHeight.Value}))
self.commandlist.append(Path.Command("(PROBECLOSE)"))
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj, job) ... set default value for RetractHeight'''
def SetupProperties():
setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY', 'OutputFileName']
return setup
def Create(name, obj=None):
'''Create(name) ... Creates and returns a Probing operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
proxy = ObjectProbing(obj, name)
return obj

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * 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
import PathScripts.PathProbe as PathProbe
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathGui as PathGui
from PySide import QtCore, QtGui
__title__ = "Path Probing Operation UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Probing operation page controller and command implementation."
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Probing operation.'''
def getForm(self):
'''getForm() ... returns UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpProbeEdit.ui")
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
self.updateToolController(obj, self.form.toolController)
PathGui.updateInputField(obj, 'Xoffset', self.form.Xoffset)
PathGui.updateInputField(obj, 'Yoffset', self.form.Yoffset)
obj.PointCountX = self.form.PointCountX.value()
obj.PointCountY = self.form.PointCountY.value()
obj.OutputFileName = str(self.form.OutputFileName.text())
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
self.setupToolController(obj, self.form.toolController)
self.form.Xoffset.setText(FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString)
self.form.Yoffset.setText(FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString)
self.form.OutputFileName.setText(obj.OutputFileName)
self.form.PointCountX.setValue(obj.PointCountX)
self.form.PointCountY.setValue(obj.PointCountY)
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.PointCountX.valueChanged)
signals.append(self.form.PointCountY.valueChanged)
signals.append(self.form.OutputFileName.editingFinished)
signals.append(self.form.Xoffset.valueChanged)
signals.append(self.form.Yoffset.valueChanged)
self.form.SetOutputFileName.clicked.connect(self.SetOutputFileName)
return signals
def SetOutputFileName(self):
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)"))
if filename and filename[0]:
self.obj.OutputFileName = str(filename[0])
self.setFields(self.obj)
Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, TaskPanelOpPage,
'Path-Probe',
QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"),
QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"),
PathProbe.SetupProperties)
FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n")

View File

@@ -112,18 +112,23 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
else:
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
else:
cutWireObjs = False
(origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value)
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
if cutShp is not False:
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
if cutWireObjs is not False:
for cW in cutWireObjs:
shapes.append((cW, False))
self.profileEdgesIsOpen = True
if self.JOB.GeometryTolerance.Value == 0.0:
msg = self.JOB.Label + '.GeometryTolerance = 0.0.'
msg += translate('PathProfileEdges', 'Please set to an acceptable value greater than zero.')
PathLog.error(msg)
else:
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
cutWireObjs = False
(origWire, flatWire) = self._flattenWire(obj, wire, obj.FinalDepth.Value)
cutShp = self._getCutAreaCrossSection(obj, base, origWire, flatWire)
if cutShp is not False:
cutWireObjs = self._extractPathWire(obj, base, flatWire, cutShp)
if cutWireObjs is not False:
for cW in cutWireObjs:
shapes.append((cW, False))
self.profileEdgesIsOpen = True
else:
PathLog.error(translate('PathProfileEdges', 'The selected edge(s) are inaccessible.'))
# Delete the temporary objects
if PathLog.getLevel(PathLog.thisModule()) != 4:
@@ -179,13 +184,13 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
return (OW, FW)
# Open-edges methods
def _getCutAreaCrossSection(self, obj, base, origWire, flatWireObj):
PathLog.debug('_getCutAreaCrossSection()')
tmpGrp = self.tmpGrp
FCAD = FreeCAD.ActiveDocument
tolerance = self.JOB.GeometryTolerance.Value
# toolDiam = float(obj.ToolController.Tool.Diameter)
toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathprofileBase modules
toolDiam = 2 * self.radius # self.radius defined in PathAreaOp or PathProfileBase modules
minBfr = toolDiam * 1.25
bbBfr = (self.ofstRadius * 2) * 1.25
if bbBfr < minBfr:
@@ -243,34 +248,33 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
# Cut model(selected edges) from extended edges boundbox
cutArea = extBndboxEXT.Shape.cut(base.Shape)
CA = FCAD.addObject('Part::Feature', 'tmpBndboxCutByBase')
CA.Shape = cutArea
CA.purgeTouched()
tmpGrp.addObject(CA)
# Get top and bottom faces of cut area (CA), and combine faces when necessary
topFc = list()
botFc = list()
bbZMax = CA.Shape.BoundBox.ZMax
bbZMin = CA.Shape.BoundBox.ZMin
for f in range(0, len(CA.Shape.Faces)):
Fc = CA.Shape.Faces[f]
if abs(Fc.BoundBox.ZMax - bbZMax) < tolerance and abs(Fc.BoundBox.ZMin - bbZMax) < tolerance:
bbZMax = cutArea.BoundBox.ZMax
bbZMin = cutArea.BoundBox.ZMin
for f in range(0, len(cutArea.Faces)):
FcBB = cutArea.Faces[f].BoundBox
if abs(FcBB.ZMax - bbZMax) < tolerance and abs(FcBB.ZMin - bbZMax) < tolerance:
topFc.append(f)
if abs(Fc.BoundBox.ZMax - bbZMin) < tolerance and abs(Fc.BoundBox.ZMin - bbZMin) < tolerance:
if abs(FcBB.ZMax - bbZMin) < tolerance and abs(FcBB.ZMin - bbZMin) < tolerance:
botFc.append(f)
topComp = Part.makeCompound([CA.Shape.Faces[f] for f in topFc])
if len(topFc) == 0:
PathLog.error('Failed to identify top faces of cut area.')
return False
topComp = Part.makeCompound([cutArea.Faces[f] for f in topFc])
topComp.translate(FreeCAD.Vector(0, 0, fdv - topComp.BoundBox.ZMin)) # Translate face to final depth
if len(botFc) > 1:
PathLog.debug('len(botFc) > 1')
bndboxFace = Part.Face(extBndbox.Shape.Wires[0])
tmpFace = Part.Face(extBndbox.Shape.Wires[0])
for f in botFc:
Q = tmpFace.cut(CA.Shape.Faces[f])
Q = tmpFace.cut(cutArea.Faces[f])
tmpFace = Q
botComp = bndboxFace.cut(tmpFace)
else:
botComp = Part.makeCompound([CA.Shape.Faces[f] for f in botFc])
botComp = Part.makeCompound([cutArea.Faces[f] for f in botFc]) # Part.makeCompound([CA.Shape.Faces[f] for f in botFc])
botComp.translate(FreeCAD.Vector(0, 0, fdv - botComp.BoundBox.ZMin)) # Translate face to final depth
# Convert compound shapes to FC objects for use in multicommon operation

View File

@@ -3,7 +3,6 @@
# ***************************************************************************
# * *
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2020 russ4262 (Russell Johnson) *
# * Copyright (c) 2020 Schildkroet *
# * *
# * This program is free software; you can redistribute it and/or modify *
@@ -43,6 +42,7 @@ __doc__ = "Path Profile operation based on faces."
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
@@ -132,22 +132,42 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
rtn = False
(norm, surf) = self.getFaceNormAndSurf(shape)
(rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("initial faceRotationAnalysis: {}".format(praInfo))
if rtn is True:
(clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis(obj, base, angle, axis, subCount)
# Verify faces are correctly oriented - InverseAngle might be necessary
faceIA = getattr(clnBase.Shape, sub)
(norm, surf) = self.getFaceNormAndSurf(faceIA)
(rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
(rtn, praAngle, praAxis, praInfo2) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable
PathLog.debug("follow-up faceRotationAnalysis: {}".format(praInfo2))
if abs(praAngle) == 180.0:
rtn = False
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp 1 is False')
angle -= 180.0
if rtn is True:
PathLog.error(translate("Path", "Face appears misaligned after initial rotation."))
if obj.AttemptInverseAngle is True and obj.InverseAngle is False:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
PathLog.debug(translate("Path", "Face appears misaligned after initial rotation."))
if obj.InverseAngle is False:
if obj.AttemptInverseAngle is True:
(clnBase, clnStock, angle) = self.applyInverseAngle(obj, clnBase, clnStock, axis, angle)
else:
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.warning(msg)
if self.isFaceUp(clnBase, faceIA) is False:
PathLog.debug('isFaceUp 2 is False')
angle += 180.0
else:
msg = translate("Path", "Consider toggling the 'InverseAngle' property and recomputing.")
PathLog.error(msg)
PathLog.debug(' isFaceUp')
else:
PathLog.debug("Face appears to be oriented correctly.")
if angle < 0.0:
angle += 360.0
tup = clnBase, sub, tag, angle, axis, clnStock
else:
if self.warnDisabledAxis(obj, axis) is False:
@@ -157,21 +177,21 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
tag = base.Name + '_' + axis + str(angle).replace('.', '_')
stock = PathUtils.findParentJob(obj).Stock
tup = base, sub, tag, angle, axis, stock
allTuples.append(tup)
if subCount > 1:
msg = translate('Path', "Multiple faces in Base Geometry.") + " "
msg += translate('Path', "Depth settings will be applied to all faces.")
PathLog.warning(msg)
(Tags, Grps) = self.sortTuplesByIndex(allTuples, 2) # return (TagList, GroupList)
subList = []
for o in range(0, len(Tags)):
subList = []
for (base, sub, tag, angle, axis, stock) in Grps[o]:
subList.append(sub)
pair = base, subList, angle, axis, stock
baseSubsTuples.append(pair)
# Efor
@@ -196,7 +216,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
for wire in shape.Wires[1:]:
holes.append((base.Shape, wire))
# Add face depth to list
faceDepths.append(shape.BoundBox.ZMin)
else:
@@ -205,13 +225,12 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
PathLog.error(msg)
FreeCAD.Console.PrintWarning(msg)
# Set initial Start and Final Depths and recalculate depthparams
finDep = obj.FinalDepth.Value
strDep = obj.StartDepth.Value
if strDep > stock.Shape.BoundBox.ZMax:
strDep = stock.Shape.BoundBox.ZMax
startDepths.append(strDep)
self.depthparams = self._customDepthParams(obj, strDep, finDep)
@@ -230,31 +249,34 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
if obj.processPerimeter:
if obj.HandleMultipleFeatures == 'Collectively':
custDepthparams = self.depthparams
if obj.LimitDepthToFace is True and obj.EnableRotation != 'Off':
if profileshape.BoundBox.ZMin > obj.FinalDepth.Value:
finDep = profileshape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep, finDep - 0.5) # only an envelope
envDepthparams = self._customDepthParams(obj, strDep + 0.5, finDep) # only an envelope
try:
env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=custDepthparams)
# env = PathUtils.getEnvelope(base.Shape, subshape=profileshape, depthparams=envDepthparams)
env = PathUtils.getEnvelope(profileshape, depthparams=envDepthparams)
except Exception: # pylint: disable=broad-except
# PathUtils.getEnvelope() failed to return an object.
PathLog.error(translate('Path', 'Unable to create path for face(s).'))
else:
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finDep
shapes.append(tup)
elif obj.HandleMultipleFeatures == 'Individually':
for shape in faces:
profShape = Part.makeCompound([shape])
# profShape = Part.makeCompound([shape])
finalDep = obj.FinalDepth.Value
custDepthparams = self.depthparams
if obj.Side == 'Inside':
if finalDep < shape.BoundBox.ZMin:
# Recalculate depthparams
finalDep = shape.BoundBox.ZMin
custDepthparams = self._customDepthParams(obj, strDep, finalDep - 0.5)
env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
custDepthparams = self._customDepthParams(obj, strDep + 0.5, finalDep)
# env = PathUtils.getEnvelope(base.Shape, subshape=profShape, depthparams=custDepthparams)
env = PathUtils.getEnvelope(shape, depthparams=custDepthparams)
tup = env, False, 'pathProfileFaces', angle, axis, strDep, finalDep
shapes.append(tup)
@@ -262,11 +284,11 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
startDepth = max(startDepths)
if obj.StartDepth.Value > startDepth:
obj.StartDepth.Value = startDepth
else: # Try to build targets from the job base
if 1 == len(self.model):
if hasattr(self.model[0], "Proxy"):
PathLog.info("hasattr() Proxy")
PathLog.debug("hasattr() Proxy")
if isinstance(self.model[0].Proxy, ArchPanel.PanelSheet): # process the sheet
if obj.processCircles or obj.processHoles:
for shape in self.model[0].Proxy.getHoles(self.model[0], transform=True):
@@ -302,7 +324,7 @@ class ObjectProfile(PathProfileBase.ObjectProfile):
obj.InverseAngle = False
obj.AttemptInverseAngle = True
obj.LimitDepthToFace = True
obj.HandleMultipleFeatures = 'Collectively'
obj.HandleMultipleFeatures = 'Individually'
def SetupProperties():
@@ -321,6 +343,5 @@ def Create(name, obj=None):
'''Create(name) ... Creates and returns a Profile based on faces operation.'''
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectProfile(obj, name)
return obj

View File

@@ -30,12 +30,14 @@ import PathScripts.PathUtils as PathUtils
import math
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
class PathBaseGate(object):
# pylint: disable=no-init
pass
class EGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
return sub and sub[0:4] == 'Edge'
@@ -66,6 +68,7 @@ class ENGRAVEGate(PathBaseGate):
return False
class CHAMFERGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
try:
@@ -94,7 +97,7 @@ class DRILLGate(PathBaseGate):
if hasattr(obj, "Shape") and sub:
shape = obj.Shape
subobj = shape.getElement(sub)
return PathUtils.isDrillable(shape, subobj, includePartials = True)
return PathUtils.isDrillable(shape, subobj, includePartials=True)
else:
return False
@@ -159,6 +162,7 @@ class POCKETGate(PathBaseGate):
return pocketable
class ADAPTIVEGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
@@ -167,45 +171,58 @@ class ADAPTIVEGate(PathBaseGate):
obj = obj.Shape
except Exception: # pylint: disable=broad-except
return False
return adaptive
class CONTOURGate(PathBaseGate):
def allow(self, doc, obj, sub): # pylint: disable=unused-argument
pass
class PROBEGate:
def allow(self, doc, obj, sub):
pass
def contourselect():
FreeCADGui.Selection.addSelectionGate(CONTOURGate())
FreeCAD.Console.PrintWarning("Contour Select Mode\n")
def eselect():
FreeCADGui.Selection.addSelectionGate(EGate())
FreeCAD.Console.PrintWarning("Edge Select Mode\n")
def drillselect():
FreeCADGui.Selection.addSelectionGate(DRILLGate())
FreeCAD.Console.PrintWarning("Drilling Select Mode\n")
def engraveselect():
FreeCADGui.Selection.addSelectionGate(ENGRAVEGate())
FreeCAD.Console.PrintWarning("Engraving Select Mode\n")
def chamferselect():
FreeCADGui.Selection.addSelectionGate(CHAMFERGate())
FreeCAD.Console.PrintWarning("Deburr Select Mode\n")
def profileselect():
FreeCADGui.Selection.addSelectionGate(PROFILEGate())
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
def pocketselect():
FreeCADGui.Selection.addSelectionGate(POCKETGate())
FreeCAD.Console.PrintWarning("Pocketing Select Mode\n")
def adaptiveselect():
FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate())
FreeCAD.Console.PrintWarning("Adaptive Select Mode\n")
def surfaceselect():
if(MESHGate() is True or PROFILEGate() is True):
FreeCADGui.Selection.addSelectionGate(True)
@@ -215,6 +232,10 @@ def surfaceselect():
# FreeCADGui.Selection.addSelectionGate(PROFILEGate()) # Added for face selection
FreeCAD.Console.PrintWarning("Surfacing Select Mode\n")
def probeselect():
FreeCADGui.Selection.addSelectionGate(PROBEGate())
FreeCAD.Console.PrintWarning("Probe Select Mode\n")
def select(op):
opsel = {}
opsel['Contour'] = contourselect
@@ -229,9 +250,12 @@ def select(op):
opsel['Profile Edges'] = eselect
opsel['Profile Faces'] = profileselect
opsel['Surface'] = surfaceselect
opsel['Waterline'] = surfaceselect
opsel['Adaptive'] = adaptiveselect
opsel['Probe'] = probeselect
return opsel[op]
def clear():
FreeCADGui.Selection.removeSelectionGate()
FreeCAD.Console.PrintWarning("Free Select\n")

View File

@@ -149,6 +149,7 @@ class OpPrototype(object):
'App::PropertyBool': PropertyBool,
'App::PropertyDistance': PropertyDistance,
'App::PropertyEnumeration': PropertyEnumeration,
'App::PropertyFile': PropertyString,
'App::PropertyFloat': PropertyFloat,
'App::PropertyFloatConstraint': Property,
'App::PropertyFloatList': Property,

View File

@@ -188,6 +188,18 @@ class _PropertyFloatEditor(_PropertyEditor):
def setModelData(self, widget):
self.prop.setValue(widget.value())
class _PropertyFileEditor(_PropertyEditor):
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
text = '' if self.prop.getValue() is None else self.prop.getValue()
widget.setText(text)
def setModelData(self, widget):
self.prop.setValue(widget.text())
_EditorFactory = {
PathSetupSheetOpPrototype.Property: None,
PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor,

File diff suppressed because it is too large Load Diff

View File

@@ -39,89 +39,120 @@ __doc__ = "Surface operation page controller and command implementation."
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Surface operation.'''
def initPage(self, obj):
self.setTitle("3D Surface")
self.updateVisibility()
def getForm(self):
'''getForm() ... returns UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpSurfaceEdit.ui")
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
if obj.Algorithm != str(self.form.algorithmSelect.currentText()):
obj.Algorithm = str(self.form.algorithmSelect.currentText())
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()):
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
if obj.ScanType != str(self.form.scanType.currentText()):
obj.ScanType = str(self.form.scanType.currentText())
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
if obj.LayerMode != str(self.form.layerMode.currentText()):
obj.LayerMode = str(self.form.layerMode.currentText())
if obj.CutPattern != str(self.form.cutPattern.currentText()):
obj.CutPattern = str(self.form.cutPattern.currentText())
obj.DropCutterExtraOffset.x = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetX.text()).Value
obj.DropCutterExtraOffset.y = FreeCAD.Units.Quantity(self.form.boundBoxExtraOffsetY.text()).Value
if obj.DropCutterDir != str(self.form.dropCutterDirSelect.currentText()):
obj.DropCutterDir = str(self.form.dropCutterDirSelect.currentText())
PathGui.updateInputField(obj, 'DepthOffset', self.form.depthOffset)
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
if obj.UseStartPoint != self.form.useStartPoint.isChecked():
obj.UseStartPoint = self.form.useStartPoint.isChecked()
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
if obj.OptimizeStepOverTransitions != self.form.optimizeStepOverTransitions.isChecked():
obj.OptimizeStepOverTransitions = self.form.optimizeStepOverTransitions.isChecked()
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect)
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
self.selectInComboBox(obj.ScanType, self.form.scanType)
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
self.form.boundBoxExtraOffsetX.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.x, FreeCAD.Units.Length).UserString)
self.form.boundBoxExtraOffsetY.setText(FreeCAD.Units.Quantity(obj.DropCutterExtraOffset.y, FreeCAD.Units.Length).UserString)
self.selectInComboBox(obj.DropCutterDir, self.form.dropCutterDirSelect)
self.form.boundBoxExtraOffsetX.setText(str(obj.DropCutterExtraOffset.x))
self.form.boundBoxExtraOffsetY.setText(str(obj.DropCutterExtraOffset.y))
self.form.depthOffset.setText(FreeCAD.Units.Quantity(obj.DepthOffset.Value, FreeCAD.Units.Length).UserString)
self.form.sampleInterval.setText(str(obj.SampleInterval))
self.form.stepOver.setValue(obj.StepOver)
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
if obj.UseStartPoint:
self.form.useStartPoint.setCheckState(QtCore.Qt.Checked)
else:
self.form.useStartPoint.setCheckState(QtCore.Qt.Unchecked)
if obj.OptimizeLinearPaths:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
else:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked)
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
if obj.OptimizeStepOverTransitions:
self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked)
else:
self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked)
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.algorithmSelect.currentIndexChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.boundBoxSelect.currentIndexChanged)
signals.append(self.form.dropCutterDirSelect.currentIndexChanged)
signals.append(self.form.scanType.currentIndexChanged)
signals.append(self.form.layerMode.currentIndexChanged)
signals.append(self.form.cutPattern.currentIndexChanged)
signals.append(self.form.boundBoxExtraOffsetX.editingFinished)
signals.append(self.form.boundBoxExtraOffsetY.editingFinished)
signals.append(self.form.sampleInterval.editingFinished)
signals.append(self.form.stepOver.editingFinished)
signals.append(self.form.dropCutterDirSelect.currentIndexChanged)
signals.append(self.form.depthOffset.editingFinished)
signals.append(self.form.stepOver.editingFinished)
signals.append(self.form.sampleInterval.editingFinished)
signals.append(self.form.useStartPoint.stateChanged)
signals.append(self.form.optimizeEnabled.stateChanged)
signals.append(self.form.coolantController.currentIndexChanged)
signals.append(self.form.optimizeStepOverTransitions.stateChanged)
return signals
def updateVisibility(self):
if self.form.algorithmSelect.currentText() == "OCL Dropcutter":
self.form.boundBoxExtraOffsetX.setEnabled(True)
self.form.boundBoxExtraOffsetY.setEnabled(True)
self.form.boundBoxSelect.setEnabled(True)
self.form.dropCutterDirSelect.setEnabled(True)
self.form.stepOver.setEnabled(True)
else:
if self.form.scanType.currentText() == "Planar":
self.form.cutPattern.setEnabled(True)
self.form.boundBoxExtraOffsetX.setEnabled(False)
self.form.boundBoxExtraOffsetY.setEnabled(False)
self.form.boundBoxSelect.setEnabled(False)
self.form.dropCutterDirSelect.setEnabled(False)
self.form.stepOver.setEnabled(False)
else:
self.form.cutPattern.setEnabled(False)
self.form.boundBoxExtraOffsetX.setEnabled(True)
self.form.boundBoxExtraOffsetY.setEnabled(True)
self.form.dropCutterDirSelect.setEnabled(True)
def registerSignalHandlers(self, obj):
self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility)
self.form.scanType.currentIndexChanged.connect(self.updateVisibility)
Command = PathOpGui.SetupOperation('Surface',

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
# * Copyright (c) 2020 russ4262 <russ4262@gmail.com> *
# * *
# * 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
import PathScripts.PathWaterline as PathWaterline
import PathScripts.PathGui as PathGui
import PathScripts.PathOpGui as PathOpGui
from PySide import QtCore
__title__ = "Path Waterline Operation UI"
__author__ = "sliptonic (Brad Collette), russ4262 (Russell Johnson)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Waterline operation page controller and command implementation."
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Waterline operation.'''
def initPage(self, obj):
# self.setTitle("Waterline")
self.updateVisibility()
def getForm(self):
'''getForm() ... returns UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpWaterlineEdit.ui")
def getFields(self, obj):
'''getFields(obj) ... transfers values from UI to obj's proprties'''
self.updateToolController(obj, self.form.toolController)
if obj.Algorithm != str(self.form.algorithmSelect.currentText()):
obj.Algorithm = str(self.form.algorithmSelect.currentText())
if obj.BoundBox != str(self.form.boundBoxSelect.currentText()):
obj.BoundBox = str(self.form.boundBoxSelect.currentText())
if obj.LayerMode != str(self.form.layerMode.currentText()):
obj.LayerMode = str(self.form.layerMode.currentText())
if obj.CutPattern != str(self.form.cutPattern.currentText()):
obj.CutPattern = str(self.form.cutPattern.currentText())
PathGui.updateInputField(obj, 'BoundaryAdjustment', self.form.boundaryAdjustment)
if obj.StepOver != self.form.stepOver.value():
obj.StepOver = self.form.stepOver.value()
PathGui.updateInputField(obj, 'SampleInterval', self.form.sampleInterval)
if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked():
obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked()
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
self.setupToolController(obj, self.form.toolController)
self.selectInComboBox(obj.Algorithm, self.form.algorithmSelect)
self.selectInComboBox(obj.BoundBox, self.form.boundBoxSelect)
self.selectInComboBox(obj.LayerMode, self.form.layerMode)
self.selectInComboBox(obj.CutPattern, self.form.cutPattern)
self.form.boundaryAdjustment.setText(FreeCAD.Units.Quantity(obj.BoundaryAdjustment.Value, FreeCAD.Units.Length).UserString)
self.form.stepOver.setValue(obj.StepOver)
self.form.sampleInterval.setText(FreeCAD.Units.Quantity(obj.SampleInterval.Value, FreeCAD.Units.Length).UserString)
if obj.OptimizeLinearPaths:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Checked)
else:
self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked)
def getSignalsForUpdate(self, obj):
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
signals = []
signals.append(self.form.toolController.currentIndexChanged)
signals.append(self.form.algorithmSelect.currentIndexChanged)
signals.append(self.form.boundBoxSelect.currentIndexChanged)
signals.append(self.form.layerMode.currentIndexChanged)
signals.append(self.form.cutPattern.currentIndexChanged)
signals.append(self.form.boundaryAdjustment.editingFinished)
signals.append(self.form.stepOver.editingFinished)
signals.append(self.form.sampleInterval.editingFinished)
signals.append(self.form.optimizeEnabled.stateChanged)
return signals
def updateVisibility(self):
if self.form.algorithmSelect.currentText() == 'OCL Dropcutter':
self.form.cutPattern.setEnabled(False)
self.form.boundaryAdjustment.setEnabled(False)
self.form.stepOver.setEnabled(False)
self.form.sampleInterval.setEnabled(True)
self.form.optimizeEnabled.setEnabled(True)
else:
self.form.cutPattern.setEnabled(True)
self.form.boundaryAdjustment.setEnabled(True)
if self.form.cutPattern.currentText() == 'None':
self.form.stepOver.setEnabled(False)
else:
self.form.stepOver.setEnabled(True)
self.form.sampleInterval.setEnabled(False)
self.form.optimizeEnabled.setEnabled(False)
def registerSignalHandlers(self, obj):
self.form.algorithmSelect.currentIndexChanged.connect(self.updateVisibility)
self.form.cutPattern.currentIndexChanged.connect(self.updateVisibility)
Command = PathOpGui.SetupOperation('Waterline',
PathWaterline.Create,
TaskPanelOpPage,
'Path-Waterline',
QtCore.QT_TRANSLATE_NOOP("Waterline", "Waterline"),
QtCore.QT_TRANSLATE_NOOP("Waterline", "Create a Waterline Operation from a model"),
PathWaterline.SetupProperties)
FreeCAD.Console.PrintLog("Loading PathWaterlineGui... done\n")

View File

@@ -73,13 +73,11 @@ def insert(filename, docname):
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print("preprocessing...")
print(inputstring)
PathLog.track(inputstring)
# split the input by line
lines = inputstring.split("\n")
output = ""
lastcommand = None
print(lines)
output = [] #""
lastcommand = None
for lin in lines:
# remove any leftover trailing and preceding spaces
@@ -91,7 +89,7 @@ def parse(inputstring):
# remove line numbers
lin = lin.split(" ", 1)
if len(lin) >= 1:
lin = lin[1]
lin = lin[1].strip()
else:
continue
@@ -100,7 +98,8 @@ def parse(inputstring):
continue
if lin[0].upper() in ["G", "M"]:
# found a G or M command: we store it
output += lin + "\n"
#output += lin + "\n"
output.append(Path.Command(str(lin))) # + "\n"
last = lin[0].upper()
for c in lin[1:]:
if not c.isdigit():
@@ -110,7 +109,7 @@ def parse(inputstring):
lastcommand = last
elif lastcommand:
# no G or M command: we repeat the last one
output += lastcommand + " " + lin + "\n"
output.append(Path.Command(str(lastcommand + " " + lin))) # + "\n"
print("done preprocessing.")
return output

View File

@@ -0,0 +1,129 @@
# ***************************************************************************
# * (c) Yorik van Havre (yorik@uncreated.net) 2014 *
# * *
# * 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 *
# * *
# ***************************************************************************/
'''
This is an example preprocessor file for the Path workbench. Its aim is to
open a gcode file, parse its contents, and create the appropriate objects
in FreeCAD.
Read the Path Workbench documentation to know how to create Path objects
from GCode.
'''
import os
import Path
import FreeCAD
import PathScripts.PathUtils
import PathScripts.PathLog as PathLog
import re
# LEVEL = PathLog.Level.DEBUG
LEVEL = PathLog.Level.INFO
PathLog.setLevel(LEVEL, PathLog.thisModule())
if LEVEL == PathLog.Level.DEBUG:
PathLog.trackModule(PathLog.thisModule())
# to distinguish python built-in open function from the one declared below
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
def open(filename):
"called when freecad opens a file."
PathLog.track(filename)
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
insert(filename, doc.Name)
def insert(filename, docname):
"called when freecad imports a file"
PathLog.track(filename)
gfile = pythonopen(filename)
gcode = gfile.read()
gfile.close()
# split on tool changes
paths = re.split('(?=[mM]+\s?0?6)', gcode)
# if there are any tool changes combine the preamble with the default tool
if len(paths) > 1:
paths = ["\n".join(paths[0:2])] + paths[2:]
for path in paths:
gcode = parse(path)
doc = FreeCAD.getDocument(docname)
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Custom")
PathScripts.PathCustom.ObjectCustom(obj)
obj.ViewObject.Proxy = 0
obj.Gcode = gcode
PathScripts.PathUtils.addToJob(obj)
obj.ToolController = PathScripts.PathUtils.findToolController(obj)
FreeCAD.ActiveDocument.recompute()
def parse(inputstring):
"parse(inputstring): returns a parsed output string"
print("preprocessing...")
PathLog.track(inputstring)
# split the input by line
lines = inputstring.split("\n")
output = [] #""
lastcommand = None
for lin in lines:
# remove any leftover trailing and preceding spaces
lin = lin.strip()
if not lin:
# discard empty lines
continue
if lin[0].upper() in ["N"]:
# remove line numbers
lin = lin.split(" ", 1)
if len(lin) >= 1:
lin = lin[1].strip()
else:
continue
if lin[0] in ["(", "%", "#", ";"]:
# discard comment and other non strictly gcode lines
continue
if lin[0].upper() in ["G", "M"]:
# found a G or M command: we store it
#output += lin + "\n"
output.append(lin) # + "\n"
last = lin[0].upper()
for c in lin[1:]:
if not c.isdigit():
break
else:
last += c
lastcommand = last
elif lastcommand:
# no G or M command: we repeat the last one
output.append(lastcommand + " " + lin) # + "\n"
print("done preprocessing.")
return output
print(__name__ + " gcode preprocessor loaded.")

View File

@@ -261,7 +261,7 @@ def export(objectslist, filename, argstring):
return
# Skip inactive operations
if not PathUtil.opProperty(obj, 'Active'):
if PathUtil.opProperty(obj, 'Active') is False:
continue
# do the pre_op