Fixing 'red ink' bugs in CAM
fixes: https://github.com/Ondsel-Development/FreeCAD/issues/93 fixes https://github.com/Ondsel-Development/FreeCAD/issues/87 fixes https://github.com/Ondsel-Development/FreeCAD/issues/88 fix threadmilling error if not proper tool fix vcarve error if no proper tool fix probe error if no proper tool fix deburr error if no base geometry Fix adaptive op error if no base geometry
This commit is contained in:
@@ -1639,7 +1639,9 @@ std::vector<shared_ptr<Area> > 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) {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user