# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * Copyright (c) 2017 sliptonic * # * 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) * # * 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 Path import Path.Op.Area as PathAreaOp import Path.Op.Base as PathOp from PySide.QtCore import QT_TRANSLATE_NOOP __title__ = "Base CAM Pocket Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecad.org" __doc__ = "Base class and implementation for pocket operations." if False: Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) Path.Log.trackModule(Path.Log.thisModule()) else: Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) translate = FreeCAD.Qt.translate class ObjectPocket(PathAreaOp.ObjectOp): """Base class for proxy objects of all pocket operations.""" @classmethod def pocketPropertyEnumerations(cls, dataType="data"): """pocketPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType. Args: dataType = 'data', 'raw', 'translated' Notes: 'data' is list of internal string literals used in code 'raw' is list of (translated_text, data_string) tuples 'translated' is list of translated string literals """ enums = { "CutMode": [ (translate("CAM_Pocket", "Climb"), "Climb"), (translate("CAM_Pocket", "Conventional"), "Conventional"), ], # this is the direction that the profile runs "StartAt": [ (translate("CAM_Pocket", "Center"), "Center"), (translate("CAM_Pocket", "Edge"), "Edge"), ], "ClearingPattern": [ (translate("CAM_Pocket", "ZigZag"), "ZigZag"), (translate("CAM_Pocket", "Offset"), "Offset"), (translate("CAM_Pocket", "ZigZagOffset"), "ZigZagOffset"), (translate("CAM_Pocket", "Line"), "Line"), (translate("CAM_Pocket", "Grid"), "Grid"), ], # Fill Pattern } if dataType == "raw": return enums data = list() idx = 0 if dataType == "translated" else 1 Path.Log.debug(enums) for k, v in enumerate(enums): data.append((v, [tup[idx] for tup in enums[v]])) Path.Log.debug(data) return data def areaOpFeatures(self, obj): """areaOpFeatures(obj) ... Pockets have a FinishDepth and work on Faces""" return PathOp.FeatureBaseFaces | PathOp.FeatureFinishDepth | self.pocketOpFeatures(obj) def pocketOpFeatures(self, obj): return 0 def initPocketOp(self, obj): """initPocketOp(obj) ... overwrite to initialize subclass. 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 def pocketInvertExtraOffset(self): """pocketInvertExtraOffset() ... return True if ExtraOffset's direction is inward. Can safely be overwritten by subclass.""" return False def initAreaOp(self, obj): """initAreaOp(obj) ... create pocket specific properties. Do not overwrite, implement initPocketOp(obj) instead.""" Path.Log.track() # Pocket Properties obj.addProperty( "App::PropertyEnumeration", "CutMode", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "The direction that the toolpath should go around the part ClockWise (CW) or CounterClockWise (CCW)", ), ) obj.addProperty( "App::PropertyDistance", "ExtraOffset", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Extra offset to apply to the operation. Direction is operation dependent.", ), ) obj.addProperty( "App::PropertyEnumeration", "StartAt", "Pocket", QT_TRANSLATE_NOOP("App::Property", "Start pocketing at center or boundary"), ) obj.addProperty( "App::PropertyPercent", "StepOver", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Percent of cutter diameter to step over on each pass" ), ) obj.addProperty( "App::PropertyFloat", "ZigZagAngle", "Pocket", QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern"), ) obj.addProperty( "App::PropertyEnumeration", "ClearingPattern", "Pocket", QT_TRANSLATE_NOOP("App::Property", "Clearing pattern to use"), ) obj.addProperty( "App::PropertyBool", "MinTravel", "Pocket", QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path"), ) obj.addProperty( "App::PropertyBool", "KeepToolDown", "Pocket", QT_TRANSLATE_NOOP("App::Property", "Attempts to avoid unnecessary retractions."), ) obj.addProperty( "App::PropertyPercent", "PocketLastStepOver", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Last Stepover Radius. If 0, 50% of cutter is used. Tuning this can be used to improve stepover for some shapes", ), ) obj.addProperty( "App::PropertyBool", "UseRestMachining", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Skips machining regions that have already been cleared by previous operations.", ), ) for n in self.pocketPropertyEnumerations(): setattr(obj, n[0], n[1]) self.initPocketOp(obj) def areaOpRetractTool(self, obj): Path.Log.debug("retracting tool: %d" % (not obj.KeepToolDown)) return not obj.KeepToolDown def areaOpUseProjection(self, obj): """areaOpUseProjection(obj) ... return False""" return False def areaOpAreaParams(self, obj, isHole): """areaOpAreaParams(obj, isHole) ... return dictionary with pocket's area parameters""" Path.Log.track() params = {} params["Fill"] = 0 params["Coplanar"] = 0 params["PocketMode"] = 1 params["SectionCount"] = -1 params["Angle"] = obj.ZigZagAngle params["FromCenter"] = obj.StartAt == "Center" params["PocketStepover"] = (self.radius * 2) * (float(obj.StepOver) / 100) extraOffset = obj.ExtraOffset.Value if self.pocketInvertExtraOffset(): extraOffset = 0 - extraOffset params["PocketExtraOffset"] = extraOffset params["ToolRadius"] = self.radius params["PocketLastStepover"] = obj.PocketLastStepOver Pattern = { "ZigZag": 1, "Offset": 2, "ZigZagOffset": 4, "Line": 5, "Grid": 6, } params["PocketMode"] = Pattern.get(obj.ClearingPattern, 1) if obj.SplitArcs: params["Explode"] = True params["FitArcs"] = False return params def opOnDocumentRestored(self, obj): super().opOnDocumentRestored(obj) if not hasattr(obj, "PocketLastStepOver"): obj.addProperty( "App::PropertyPercent", "PocketLastStepOver", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Last Stepover Radius. If 0, 50% of cutter is used. Tuning this can be used to improve stepover for some shapes", ), ) obj.PocketLastStepOver = 0 if not hasattr(obj, "UseRestMachining"): obj.addProperty( "App::PropertyBool", "UseRestMachining", "Pocket", QT_TRANSLATE_NOOP( "App::Property", "Skips machining regions that have already been cleared by previous operations.", ), ) if hasattr(obj, "OffsetPattern"): obj.setGroupOfProperty("OffsetPattern", "Pocket") obj.renameProperty("OffsetPattern", "ClearingPattern") if hasattr(obj, "RestMachiningRegions"): obj.removeProperty("RestMachiningRegions") if hasattr(obj, "RestMachiningRegionsNeedRecompute"): obj.removeProperty("RestMachiningRegionsNeedRecompute") Path.Log.track() def areaOpPathParams(self, obj, isHole): """areaOpAreaParams(obj, isHole) ... return dictionary with pocket's path parameters""" params = {} CutMode = ["Conventional", "Climb"] params["orientation"] = CutMode.index(obj.CutMode) # if MinTravel is turned on, set path sorting to 3DSort # 3DSort shouldn't be used without a valid start point. Can cause # tool crash without it. # # ml: experimental feature, turning off for now (see https://forum.freecad.org/viewtopic.php?f=15&t=24422&start=30#p192458) # realthunder: I've fixed it with a new sorting algorithm, which I # tested fine, but of course need more test. Please let know if there is # any problem # if obj.MinTravel and obj.UseStartPoint and obj.StartPoint is not None: params["sort_mode"] = 3 params["threshold"] = self.radius * 2 return params def SetupProperties(): setup = PathAreaOp.SetupProperties() setup.append("CutMode") setup.append("ExtraOffset") setup.append("StepOver") setup.append("ZigZagAngle") setup.append("ClearingPattern") setup.append("StartAt") setup.append("MinTravel") setup.append("KeepToolDown") return setup