diff --git a/src/Mod/CAM/App/Area.cpp b/src/Mod/CAM/App/Area.cpp index 309c5ad84f..273f0fb3b6 100644 --- a/src/Mod/CAM/App/Area.cpp +++ b/src/Mod/CAM/App/Area.cpp @@ -1639,7 +1639,9 @@ std::vector > Area::makeSections( if (hitMin) continue; hitMin = true; double zNew = zMin + myParams.SectionTolerance; - AREA_WARN("hit bottom " << z << ',' << zMin << ',' << zNew); + //Silence the warning if _heights is not empty + if (_heights.empty() && FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) + AREA_WARN("hit bottom " << z << ',' << zMin << ',' << zNew); z = zNew; } else if (zMax - z < myParams.SectionTolerance) { diff --git a/src/Mod/CAM/Path/Main/Sanity/Sanity.py b/src/Mod/CAM/Path/Main/Sanity/Sanity.py index 046bedcb5c..7749be2ddc 100644 --- a/src/Mod/CAM/Path/Main/Sanity/Sanity.py +++ b/src/Mod/CAM/Path/Main/Sanity/Sanity.py @@ -37,6 +37,7 @@ import Path.Main.Sanity.ImageBuilder as ImageBuilder import Path.Main.Sanity.ReportGenerator as ReportGenerator import os import tempfile +import Path.Dressup.Utils as PathDressup translate = FreeCAD.Qt.translate @@ -454,11 +455,12 @@ class CAMSanity: used = False for op in obj.Operations.Group: - if hasattr(op, "ToolController") and op.ToolController is TC: + base_op = PathDressup.baseOp(op) + if hasattr(base_op, "ToolController") and base_op.ToolController is TC: used = True tooldata.setdefault("ops", []).append( { - "Operation": op.Label, + "Operation": base_op.Label, "ToolController": TC.Label, "Feed": str(TC.HorizFeed), "Speed": str(TC.SpindleSpeed), diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 6eb2d7f3e0..420f025744 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -678,7 +678,11 @@ def Execute(op, obj): # Get list of working edges for adaptive algorithm pathArray = op.pathArray if not pathArray: - Path.Log.error("No wire data returned.") + msg = translate( + "CAM", + "Adaptive operation couldn't determine the boundary wire. Did you select base geometry?", + ) + FreeCAD.Console.PrintUserWarning(msg) return path2d = convertTo2d(pathArray) diff --git a/src/Mod/CAM/Path/Op/Area.py b/src/Mod/CAM/Path/Op/Area.py index 541fc19d6f..559f454248 100644 --- a/src/Mod/CAM/Path/Op/Area.py +++ b/src/Mod/CAM/Path/Op/Area.py @@ -223,7 +223,9 @@ class ObjectOp(PathOp.ObjectOp): area.add(baseobject) areaParams = self.areaOpAreaParams(obj, isHole) - areaParams["SectionTolerance"] = FreeCAD.Base.Precision.confusion() * 10 # basically 1e-06 + areaParams["SectionTolerance"] = ( + FreeCAD.Base.Precision.confusion() * 10 + ) # basically 1e-06 heights = [i for i in self.depthparams] Path.Log.debug("depths: {}".format(heights)) @@ -238,7 +240,9 @@ class ObjectOp(PathOp.ObjectOp): Path.Log.debug("sections = %s" % sections) # Rest machining - self.sectionShapes = self.sectionShapes + [section.toTopoShape() for section in sections] + self.sectionShapes = self.sectionShapes + [ + section.toTopoShape() for section in sections + ] if hasattr(obj, "UseRestMachining") and obj.UseRestMachining: restSections = [] for section in sections: @@ -246,15 +250,36 @@ class ObjectOp(PathOp.ObjectOp): z = bbox.ZMin sectionClearedAreas = [] for op in self.job.Operations.Group: - if self in [x.Proxy for x in [op] + op.OutListRecursive if hasattr(x, "Proxy")]: + if self in [ + x.Proxy + for x in [op] + op.OutListRecursive + if hasattr(x, "Proxy") + ]: break if hasattr(op, "Active") and op.Active and op.Path: - tool = op.Proxy.tool if hasattr(op.Proxy, "tool") else op.ToolController.Proxy.getTool(op.ToolController) + tool = ( + op.Proxy.tool + if hasattr(op.Proxy, "tool") + else op.ToolController.Proxy.getTool(op.ToolController) + ) diameter = tool.Diameter.getValueAs("mm") - dz = 0 if not hasattr(tool, "TipAngle") else -PathUtils.drillTipLength(tool) # for drills, dz translates to the full width part of the tool - sectionClearedAreas.append(section.getClearedArea(op.Path, diameter, z+dz+self.job.GeometryTolerance.getValueAs("mm"), bbox)) - restSection = section.getRestArea(sectionClearedAreas, self.tool.Diameter.getValueAs("mm")) - if (restSection is not None): + dz = ( + 0 + if not hasattr(tool, "TipAngle") + else -PathUtils.drillTipLength(tool) + ) # for drills, dz translates to the full width part of the tool + sectionClearedAreas.append( + section.getClearedArea( + op.Path, + diameter, + z + dz + self.job.GeometryTolerance.getValueAs("mm"), + bbox, + ) + ) + restSection = section.getRestArea( + sectionClearedAreas, self.tool.Diameter.getValueAs("mm") + ) + if restSection is not None: restSections.append(restSection) sections = restSections @@ -273,7 +298,12 @@ class ObjectOp(PathOp.ObjectOp): pathParams["preamble"] = False # disable path sorting for offset and zigzag-offset paths - if hasattr(obj, "OffsetPattern") and obj.OffsetPattern in ["ZigZagOffset", "Offset"] and hasattr(obj, "MinTravel") and not obj.MinTravel: + if ( + hasattr(obj, "OffsetPattern") + and obj.OffsetPattern in ["ZigZagOffset", "Offset"] + and hasattr(obj, "MinTravel") + and not obj.MinTravel + ): pathParams["sort_mode"] = 0 if not self.areaOpRetractTool(obj): diff --git a/src/Mod/CAM/Path/Op/Base.py b/src/Mod/CAM/Path/Op/Base.py index 2600d4edda..64f7a824e0 100644 --- a/src/Mod/CAM/Path/Op/Base.py +++ b/src/Mod/CAM/Path/Op/Base.py @@ -912,10 +912,7 @@ class ObjectOp(object): obj.Base = baselist else: Path.Log.notice( - ( - translate("CAM", "Base object %s.%s rejected by operation") - + "\n" - ) + (translate("CAM", "Base object %s.%s rejected by operation") + "\n") % (base.Label, sub) ) diff --git a/src/Mod/CAM/Path/Op/Deburr.py b/src/Mod/CAM/Path/Op/Deburr.py index 68860da4d2..14bf268915 100644 --- a/src/Mod/CAM/Path/Op/Deburr.py +++ b/src/Mod/CAM/Path/Op/Deburr.py @@ -157,9 +157,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): "App::PropertyInteger", "EntryPoint", "Deburr", - QT_TRANSLATE_NOOP( - "App::Property", "The segment where the toolpath starts" - ), + QT_TRANSLATE_NOOP("App::Property", "The segment where the toolpath starts"), ) ENUMS = self.propertyEnumerations() @@ -210,6 +208,10 @@ class ObjectDeburr(PathEngraveBase.ObjectOp): def opExecute(self, obj): Path.Log.track(obj.Label) + + if not obj.Base: + return + if not hasattr(self, "printInfo"): self.printInfo = True try: diff --git a/src/Mod/CAM/Path/Op/Gui/Base.py b/src/Mod/CAM/Path/Op/Gui/Base.py index 2793b6fcbe..2e82e3908e 100644 --- a/src/Mod/CAM/Path/Op/Gui/Base.py +++ b/src/Mod/CAM/Path/Op/Gui/Base.py @@ -213,6 +213,14 @@ class TaskPanelPage(object): if self._installTCUpdate(): PathJob.Notification.updateTC.connect(self.resetToolController) + def show_error_message(self, title, message): + msg_box = QtGui.QMessageBox() + msg_box.setIcon(QtGui.QMessageBox.Critical) + msg_box.setWindowTitle(title) + msg_box.setText(message) + msg_box.setStandardButtons(QtGui.QMessageBox.Ok) + msg_box.exec_() + def _installTCUpdate(self): return hasattr(self.form, "toolController") @@ -545,7 +553,6 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): "PathOp", "Please select %s from a single solid" % self.featureName(), ) - FreeCAD.Console.PrintError(msg + "\n") Path.Log.debug(msg) return False sel = selection[0] @@ -554,32 +561,19 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): not self.supportsVertexes() and selection[0].SubObjects[0].ShapeType == "Vertex" ): - if not ignoreErrors: - Path.Log.error(translate("PathOp", "Vertexes are not supported")) return False if ( not self.supportsEdges() and selection[0].SubObjects[0].ShapeType == "Edge" ): - if not ignoreErrors: - Path.Log.error(translate("PathOp", "Edges are not supported")) return False if ( not self.supportsFaces() and selection[0].SubObjects[0].ShapeType == "Face" ): - if not ignoreErrors: - Path.Log.error(translate("PathOp", "Faces are not supported")) return False else: if not self.supportsPanels() or "Panel" not in sel.Object.Name: - if not ignoreErrors: - Path.Log.error( - translate( - "PathOp", - "Please select %s of a solid" % self.featureName(), - ) - ) return False return True @@ -622,9 +616,6 @@ class TaskPanelBaseGeometryPage(TaskPanelPage): Path.Log.debug("Setting new base: %s -> %s" % (self.obj.Base, newlist)) self.obj.Base = newlist - # self.obj.Proxy.execute(self.obj) - # FreeCAD.ActiveDocument.recompute() - def clearBase(self): self.obj.Base = [] self.setDirty() @@ -1274,7 +1265,11 @@ class TaskPanel(object): def getStandardButtons(self): """getStandardButtons() ... returns the Buttons for the task panel.""" - return QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel + return ( + QtGui.QDialogButtonBox.Ok + | QtGui.QDialogButtonBox.Apply + | QtGui.QDialogButtonBox.Cancel + ) def setupUi(self): """setupUi() ... internal function to initialise all pages.""" diff --git a/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py b/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py index 81f184d5f6..75cb156ad0 100644 --- a/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py +++ b/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py @@ -227,7 +227,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): def cleanupPage(self, obj): try: self.obj.ViewObject.RootNode.removeChild(self.switch) - except ReferenceError: + except (ReferenceError, RuntimeError) as e: Path.Log.debug("obj already destroyed - no cleanup required") def getForm(self): diff --git a/src/Mod/CAM/Path/Op/Gui/Probe.py b/src/Mod/CAM/Path/Op/Gui/Probe.py index c7b748fb05..618ad00067 100644 --- a/src/Mod/CAM/Path/Op/Gui/Probe.py +++ b/src/Mod/CAM/Path/Op/Gui/Probe.py @@ -27,6 +27,7 @@ import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui import Path.Op.Probe as PathProbe import PathGui +import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP from PySide import QtCore, QtGui @@ -55,13 +56,23 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's properties""" - self.updateToolController(obj, self.form.toolController) PathGuiUtil.updateInputField(obj, "Xoffset", self.form.Xoffset) PathGuiUtil.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()) + try: + self.updateToolController(obj, self.form.toolController) + except PathUtils.PathNoTCExistsException: + title = translate("CAM", "No valid toolcontroller") + message = translate( + "CAM", + "This operation requires a tool controller with a probe tool", + ) + + self.show_error_message(title, message) + def setFields(self, obj): """setFields(obj) ... transfers obj's property values to UI""" self.setupToolController(obj, self.form.toolController) diff --git a/src/Mod/CAM/Path/Op/Gui/ThreadMilling.py b/src/Mod/CAM/Path/Op/Gui/ThreadMilling.py index 8acda6cfd2..9f99e72e05 100644 --- a/src/Mod/CAM/Path/Op/Gui/ThreadMilling.py +++ b/src/Mod/CAM/Path/Op/Gui/ThreadMilling.py @@ -28,12 +28,13 @@ import Path.Op.Gui.Base as PathOpGui import Path.Op.Gui.CircularHoleBase as PathCircularHoleBaseGui import Path.Op.ThreadMilling as PathThreadMilling import PathGui +import PathScripts.PathUtils as PathUtils import csv from PySide.QtCore import QT_TRANSLATE_NOOP +from PySide import QtCore, QtGui -from PySide import QtCore __title__ = "CAM Thread Milling Operation UI." __author__ = "sliptonic (Brad Collette)" @@ -112,7 +113,16 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage): obj.LeadInOut = self.form.leadInOut.checkState() == QtCore.Qt.Checked obj.TPI = self.form.threadTPI.value() - self.updateToolController(obj, self.form.toolController) + try: + self.updateToolController(obj, self.form.toolController) + except PathUtils.PathNoTCExistsException: + title = translate("CAM", "No valid toolcontroller") + message = translate( + "CAM", + "This operation requires a tool controller with a threadmilling tool", + ) + + self.show_error_message(title, message) def setFields(self, obj): """setFields(obj) ... update UI with obj properties' values""" diff --git a/src/Mod/CAM/Path/Op/Gui/Vcarve.py b/src/Mod/CAM/Path/Op/Gui/Vcarve.py index ef6e8818d1..662f32d774 100644 --- a/src/Mod/CAM/Path/Op/Gui/Vcarve.py +++ b/src/Mod/CAM/Path/Op/Gui/Vcarve.py @@ -170,13 +170,23 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): obj.FinishingPass = self.form.finishingPassEnabled.isChecked() if obj.OptimizeMovements != self.form.optimizeMovementsEnabled.isChecked(): - obj.OptimizeMovements = self.form.optimizeMovementsEnabled.isChecked() + obj.OptimizeMovements = self.form.optimizeMovementsEnabled.isChecked() self.finishingPassZOffsetSpinBox.updateProperty() - self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) + try: + self.updateToolController(obj, self.form.toolController) + except PathUtils.PathNoTCExistsException: + title = translate("CAM", "No valid toolcontroller") + message = translate( + "CAM", + "This operation requires a tool controller with a v-bit tool", + ) + + self.show_error_message(title, message) + def setFields(self, obj): """setFields(obj) ... transfers obj's property values to UI""" self.form.discretize.setValue(obj.Discretize) @@ -201,7 +211,6 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage): signals.append(self.form.optimizeMovementsEnabled.stateChanged) - signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.coolantController.currentIndexChanged) return signals diff --git a/src/Mod/CAM/Path/Op/PocketBase.py b/src/Mod/CAM/Path/Op/PocketBase.py index e22ef75c89..681cdcd936 100644 --- a/src/Mod/CAM/Path/Op/PocketBase.py +++ b/src/Mod/CAM/Path/Op/PocketBase.py @@ -104,6 +104,11 @@ class ObjectPocket(PathAreaOp.ObjectOp): Can safely be overwritten by subclass.""" pass + def opExecute(self, obj): + if len(obj.Base) == 0: + return + super().opExecute(obj) + def areaOpSetDefaultValues(self, obj, job): obj.PocketLastStepOver = 0 diff --git a/src/Mod/CAM/Path/Op/Probe.py b/src/Mod/CAM/Path/Op/Probe.py index 11d4b44e5f..05536327ff 100644 --- a/src/Mod/CAM/Path/Op/Probe.py +++ b/src/Mod/CAM/Path/Op/Probe.py @@ -95,6 +95,10 @@ class ObjectProbing(PathOp.ObjectOp): def opExecute(self, obj): """opExecute(obj) ... generate probe locations.""" Path.Log.track() + if not self.isToolSupported(obj, self.tool): + Path.Log.warning("No suitable probe tool found") + return + self.commandlist.append(Path.Command("(Begin Probing)")) stock = PathUtils.findParentJob(obj).Stock @@ -132,6 +136,11 @@ class ObjectProbing(PathOp.ObjectOp): def opSetDefaultValues(self, obj, job): """opSetDefaultValues(obj, job) ... set default value for RetractHeight""" + def isToolSupported(self, obj, tool): + """Probe operation requires a probe tool""" + support = hasattr(tool, "ShapeName") and (tool.ShapeName == "probe") + Path.Log.track(tool.Label, support) + return support def SetupProperties(): setup = ["Xoffset", "Yoffset", "PointCountX", "PointCountY", "OutputFileName"] diff --git a/src/Mod/CAM/Path/Op/Slot.py b/src/Mod/CAM/Path/Op/Slot.py index b1585c45b2..10d879748a 100644 --- a/src/Mod/CAM/Path/Op/Slot.py +++ b/src/Mod/CAM/Path/Op/Slot.py @@ -601,7 +601,7 @@ class ObjectSlot(PathOp.ObjectOp): if not hasattr(obj, "Base"): msg = translate("CAM_Slot", "No Base Geometry object in the operation.") - FreeCAD.Console.PrintError(msg + "\n") + FreeCAD.Console.PrintUserWarning(msg + "\n") return False if not obj.Base: @@ -609,15 +609,15 @@ class ObjectSlot(PathOp.ObjectOp): p1 = obj.CustomPoint1 p2 = obj.CustomPoint2 if p1 == p2: - msg = translate("CAM_Slot", "Custom points are identical.") - FreeCAD.Console.PrintError(msg + "\n") + msg = translate("CAM_Slot", "Custom points are identical. No slot path will be generated") + FreeCAD.Console.PrintUserWarning(msg + "\n") return False elif p1.z == p2.z: pnts = (p1, p2) featureCount = 2 else: - msg = translate("CAM_Slot", "Custom points not at same Z height.") - FreeCAD.Console.PrintError(msg + "\n") + msg = translate("CAM_Slot", "Custom points not at same Z height. No slot path will be generated") + FreeCAD.Console.PrintUserWarning(msg + "\n") return False else: baseGeom = obj.Base[0] diff --git a/src/Mod/CAM/Path/Op/Vcarve.py b/src/Mod/CAM/Path/Op/Vcarve.py index f1b1230002..8a32139775 100644 --- a/src/Mod/CAM/Path/Op/Vcarve.py +++ b/src/Mod/CAM/Path/Op/Vcarve.py @@ -451,7 +451,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): if not currentPosition: return False - + # get vertex position on X/Y plane only v0 = FreeCAD.Base.Vector(currentPosition.x, currentPosition.y) v1 = FreeCAD.Base.Vector(newPosition.x, newPosition.y) @@ -537,16 +537,20 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): self.voronoiDebugCache = None + if obj.ToolController is None: + return + if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"): - Path.Log.error( + Path.Log.info( translate( "CAM_Vcarve", "VCarve requires an engraving cutter with a cutting edge angle", ) ) + return if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0: - Path.Log.error( + Path.Log.info( translate( "CAM_Vcarve", "Engraver cutting edge angle must be < 180 degrees." ) @@ -583,7 +587,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp): ) except Exception as e: - Path.Log.error( + Path.Log.warning( "Error processing Base object. Engraving operation will produce no output." ) import traceback