diff --git a/src/Mod/CAM/App/Area.cpp b/src/Mod/CAM/App/Area.cpp index 273f0fb3b668..698a4dbd5800 100644 --- a/src/Mod/CAM/App/Area.cpp +++ b/src/Mod/CAM/App/Area.cpp @@ -1639,9 +1639,7 @@ std::vector > Area::makeSections( if (hitMin) continue; hitMin = true; double zNew = zMin + myParams.SectionTolerance; - //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); + //AREA_WARN("hit bottom " << z << ',' << zMin << ',' << zNew); z = zNew; } else if (zMax - z < myParams.SectionTolerance) { @@ -2282,13 +2280,19 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG, AREA_PARAMS_POCK pm = SpiralPocketMode; break; case Area::PocketModeOffset: { - PARAM_DECLARE_INIT(PARAM_FNAME, AREA_PARAMS_OFFSET); - Offset = -tool_radius - extra_offset - shift; - ExtraPass = -1; - Stepover = -stepover; - LastStepover = -last_stepover; - // make offset and make sure the loop is CW (i.e. inner wires) - return makeOffset(index, PARAM_FIELDS(PARAM_FNAME, AREA_PARAMS_OFFSET), -1, from_center); + if (finishing_offset != true) { + PARAM_DECLARE_INIT(PARAM_FNAME, AREA_PARAMS_OFFSET); + Offset = -tool_radius - extra_offset - shift; + ExtraPass = -1; + Stepover = -stepover; + LastStepover = -last_stepover; + // make offset and make sure the loop is CW (i.e. inner wires) + return makeOffset(index, PARAM_FIELDS(PARAM_FNAME, AREA_PARAMS_OFFSET), -1, from_center); + } + else { + pm = SingleOffsetPocketMode; + break; + } }case Area::PocketModeZigZagOffset: pm = ZigZagThenSingleOffsetPocketMode; break; @@ -2354,7 +2358,7 @@ TopoDS_Shape Area::makePocket(int index, PARAM_ARGS(PARAM_FARG, AREA_PARAMS_POCK if (!done) { CAreaPocketParams params( - tool_radius, extra_offset, stepover, from_center, pm, angle); + tool_radius, extra_offset, stepover, from_center, finishing_offset, pm, angle); CArea in(*myArea); // MakePocketToolPath internally uses libarea Offset which somehow demands // reorder before input, otherwise nothing is shown. diff --git a/src/Mod/CAM/App/AreaParams.h b/src/Mod/CAM/App/AreaParams.h index 6740e6efb5b4..125ca0ca4669 100644 --- a/src/Mod/CAM/App/AreaParams.h +++ b/src/Mod/CAM/App/AreaParams.h @@ -109,6 +109,7 @@ "Cutter diameter to step over for the last loop when using offset pocket.\n"\ "If =0, use 0.5*ToolRadius.", App::PropertyLength))\ ((bool,from_center,FromCenter,false,"Start pocketing from center"))\ + ((bool,finishing_offset,UseFinishingOffset,false,"Finishing Offset Pass Only"))\ ((double,angle,Angle,45,"Pattern angle in degree",App::PropertyAngle))\ ((double,angle_shift,AngleShift,0.0,"Pattern angle shift for each section", App::PropertyAngle))\ ((double,shift,Shift,0.0,"Pattern shift distance for each section.\n"\ diff --git a/src/Mod/CAM/App/AreaPyImp.cpp b/src/Mod/CAM/App/AreaPyImp.cpp index bbfb1b508842..8fbc43485fb3 100644 --- a/src/Mod/CAM/App/AreaPyImp.cpp +++ b/src/Mod/CAM/App/AreaPyImp.cpp @@ -47,7 +47,7 @@ static PyObject * areaAbort(PyObject *, PyObject *args, PyObject *kwd) { static PyObject * areaSetParams(PyObject *, PyObject *args, PyObject *kwd) { - static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_STATIC_CONF),nullptr}; + static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_STATIC_CONF),nullptr}; if(args && PySequence_Size(args)>0) PyErr_SetString(PyExc_ValueError,"Non-keyword argument is not supported"); @@ -335,7 +335,7 @@ PyObject* AreaPy::makeOffset(PyObject *args, PyObject *keywds) PyObject* AreaPy::makePocket(PyObject *args, PyObject *keywds) { - static const std::array kwlist {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), nullptr}; + static const std::array kwlist {"index",PARAM_FIELD_STRINGS(ARG,AREA_PARAMS_POCKET), nullptr}; short index = -1; PARAM_PY_DECLARE_INIT(PARAM_FARG,AREA_PARAMS_POCKET) @@ -477,7 +477,7 @@ PyObject* AreaPy::setDefaultParams(PyObject *, PyObject *) PyObject* AreaPy::setParams(PyObject *args, PyObject *keywds) { - static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),nullptr}; + static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),nullptr}; //Declare variables defined in the NAME field of the CONF parameter list PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); diff --git a/src/Mod/CAM/App/FeatureAreaPyImp.cpp b/src/Mod/CAM/App/FeatureAreaPyImp.cpp index a4a49f3990e7..64d3bc510451 100644 --- a/src/Mod/CAM/App/FeatureAreaPyImp.cpp +++ b/src/Mod/CAM/App/FeatureAreaPyImp.cpp @@ -51,7 +51,7 @@ PyObject* FeatureAreaPy::getArea(PyObject *args) PyObject* FeatureAreaPy::setParams(PyObject *args, PyObject *keywds) { - static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),nullptr}; + static const std::array kwlist {PARAM_FIELD_STRINGS(NAME,AREA_PARAMS_CONF),nullptr}; //Declare variables defined in the NAME field of the CONF parameter list PARAM_PY_DECLARE(PARAM_FNAME,AREA_PARAMS_CONF); diff --git a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui index 5edf0b83faa8..4ce884f5cd7a 100644 --- a/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/DressUpLeadInOutEdit.ui @@ -67,7 +67,7 @@ length or radius of the Lead-in - + 0.100000000000000 @@ -92,6 +92,13 @@ + + + + Reverse Direction + + + @@ -141,7 +148,7 @@ length or radius of the Lead-out - + 0.100000000000000 diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketExtEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketExtEdit.ui index 74b8d2a40f7e..37fb104e7f45 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketExtEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketExtEdit.ui @@ -68,14 +68,20 @@ Extend Corners - true + false - + + + Extend Edges Independently. + - Default Length + Extend Edges Ind + + + true diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui index 8d81b94766fe..2f1f9738a805 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpPocketFullEdit.ui @@ -14,6 +14,179 @@ Form + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Cut Mode + + + + + + + The cutting mode assumes that the cut on one side of the tool bit represents the resulting part and the other side is either already milled away or will be removed later on. Climb mode is when the tool bit is moved into the cut on each rotation, whereas in conventional mode the tool bit's rotation and the tool's lateral movement are in the same direction + + + + Climb + + + + + Conventional + + + + + + + + Pattern + + + + + + + Pattern the tool bit is moved in to clear the material + + + ZigZag + + + + ZigZag + + + + + Offset + + + + + Spiral + + + + + ZigZagOffset + + + + + Line + + + + + Grid + + + + + Triangle + + + + + + + + Angle + + + + + + + Angle in which the pattern is applied + + + 5 + + + + + + + Step Over Percent + + + + + + + The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter. A step over of 100% results in no overlap between two different cycles + + + 1 + + + 100 + + + 5 + + + 100 + + + + + + + Material Allowance + + + + + + + The amount of material that should be left by this operation in relation to the target shape + + + + + + + + + + + Boundary Shape + + + + + + + Specify if the facing should be restricted by the actual shape of the selected face (or the part if no face is selected), or if the bounding box should be faced off. + +The latter can be used to face of the entire stock area to ensure uniform heights for the following operations + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -54,229 +227,73 @@ - - - - - - - Boundary Shape - - - - - - - Specify if the facing should be restricted by the actual shape of the selected face (or the part if no face is selected), or if the bounding box should be faced off. - -The latter can be used to face of the entire stock area to ensure uniform heights for the following operations - - - - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - Cut Mode - - - - - - - The cutting mode assumes that the cut on one side of the tool bit represents the resulting part and the other side is either already milled away or will be removed later on. Climb mode is when the tool bit is moved into the cut on each rotation, whereas in conventional mode the tool bit's rotation and the tool's lateral movement are in the same direction - - - - Climb - - - - - Conventional - - - - - - - - Pattern - - - - - - - Pattern the tool bit is moved in to clear the material - - - ZigZag - - - - ZigZag - - - - - Offset - - - - - Spiral - - - - - ZigZagOffset - - - - - Line - - - - - Grid - - - - - Triangle - - - - - - - - Angle - - - - - - - Angle in which the pattern is applied - - - - - - - Step Over Percent - - - - - - - The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter. A step over of 100% results in no overlap between two different cycles - - - 1 - - - 100 - - - 10 - - - 100 - - - - - - - Material Allowance - - - - - - - The amount of material that should be left by this operation in relation to the target shape - - - - - - - - - - - - Specify if this operation uses a starting point - - - Use Start Point - - - - - - - If selected the operation uses the outline of the selected base geometry and ignores all holes and islands - - - Use Outline - - - - - - - Clear Edges - - - - - - - Min Travel - - - - - - - Check to skip machining regions that have already been cleared by previous operations - - - Use Rest Machining - - - - - + + + + + Specify if this operation uses a starting point + + + Use Start Point + + + + + + + If selected the operation uses the outline of the selected base geometry and ignores all holes and islands + + + Use Outline + + + + + + + Clear Edges + + + + + + + Min Travel + + + + + + + Check to skip machining regions that have already been cleared by previous operations + + + Use Rest Machining + + + + + + + Finishing Pass only for Offset Pattern + + + Finishing Pass Only + + + + - - - - Qt::Vertical + + + + Prevents Path Reverse on each Step Down if Start Point is used + + + No Path Reverse - - - 20 - 40 - - - + diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui index fb066cb2ce88..4f37d1ca34bc 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpProfileFullEdit.ui @@ -55,126 +55,196 @@ - - - - + + + + + Cut Side + + + + + + + Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default) + + - Cut Side + PLACEHOLDER - - - - - - Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default) - - - - PLACEHOLDER - - - - - - + + + + + + + Direction + + + + + + + The direction in which the profile is performed, clockwise or counterclockwise + + - Direction + PLACEHOLDER - - - - - - The direction in which the profile is performed, clockwise or counterclockwise - - - - PLACEHOLDER - - - - - - - - Extra Offset - - - + + + + + + + Extra Offset + + + - - - - 0 - 0 - - - - The amount of extra material left by this operation in relation to the target shape - - - - - + + + + 0 + 0 + + + + The amount of extra material left by this operation in relation to the target shape + + + + - - - - - - Check if this operation should use a starting point - - - Use Start Point - - - - - - - Check if this profile operation should also process holes in the base geometry. Found holes are automatically offset on the opposite cut side and performed in the opposite direction as perimeters. Note that this does not include cylindrical holes, the assumption being that they will get drilled - - - Process Holes - - - - - - - If checked the profile operation is offset by the tool radius. The offset direction is determined by the Cut Side - - - Use Compensation - - - - - - - Check if you want this profile operation to also be applied to cylindrical holes, which normally get drilled. This can be useful if no drill of adequate size is available or the number of holes don't warrant a tool change. Note that the cut side and direction is reversed in respect to the specified values - - - Process Circles - - - - - - - Check if this profile operation should also process the outside perimeter of the base geometry shapes - - - Process Perimeter - - - - - + + + + + Check if this operation should use a starting point + + + Use Start Point + + + + + + + Check if this profile operation should also process holes in the base geometry. Found holes are automatically offset on the opposite cut side and performed in the opposite direction as perimeters. Note that this does not include cylindrical holes, the assumption being that they will get drilled + + + Process Holes + + + + + + + If checked the profile operation is offset by the tool radius. The offset direction is determined by the Cut Side + + + Use Compensation + + + + + + + Check if you want this profile operation to also be applied to cylindrical holes, which normally get drilled. This can be useful if no drill of adequate size is available or the number of holes don't warrant a tool change. Note that the cut side and direction is reversed in respect to the specified values + + + Process Circles + + + + + + + <html><head/><body><p>Multiple Profile Op with Step Over</p></body></html> + + + Multi Profile Op + + + + + + + Check if this profile operation should also process the outside perimeter of the base geometry shapes + + + Process Perimeter + + + + + + + + + Multi Profile Width + + + Extend Width + + + + + + + Step Over % + + + Step Over Percent + + + + + + + + 0 + 0 + + + + The Width of the Multi Profile Op + + + + + + + 100 + + + 5 + + + 20 + + + + + + + + + + + Starting Profile from Outside in or Inside out + + + Cut OutsideIn + + + + + + Qt::Vertical diff --git a/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui b/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui index e69f98187e08..b20e03cae84f 100644 --- a/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui +++ b/src/Mod/CAM/Gui/Resources/panels/PageOpSurfaceEdit.ui @@ -7,7 +7,7 @@ 0 0 498 - 541 + 566 @@ -55,244 +55,262 @@ - - - - - - Bounding Box - - - - - - - Select the overall boundary for the operation. - - - - - - - Scan Type - - - - - - - Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan. - - - - - - - Layer Mode - - - - - - - Complete the operation in a single pass at depth, or multiple passes to final depth. - - - - - - - Cut Pattern - - - - - - - Set the geometric clearing pattern to use for the operation. - - - - - - - Profile Edges - - - - - - - Profile the edges of the selection. - - - - - - - Avoid Last X Faces - - - - - - - Avoid cutting the last 'N' faces in the Base Geometry list of selected faces. - - - - - - - Bounding box extra offset X, Y - - - - - - - - - - 0 - 0 - - - - Additional offset to the selected bounding box along the X axis. - - - mm - - - - - - - Additional offset to the selected bounding box along the Y axis. - - - mm - - - - - - - - - Drop Cutter Direction - - - - - - - Dropcutter lines are created parallel to this axis. - - - - - - - Depth offset - - - - - - - Set the Z-axis depth offset from the target surface. - - - mm - - - - - - - Step over - - - - - - - The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter. + + + + + Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-code output. + + + Optimize Linear Paths + + + + + + + Avoid Last X Faces + + + + + + + Bounding Box + + + + + + + If true, the cutter will remain inside the boundaries of the model or selected face(s) + + + Boundary Enforcement + + + true + + + + + + + Scan Type + + + + + + + Profile the edges of the selection. + + + + + + + + + + 0 + 0 + + + + Additional offset to the selected bounding box along the X axis. + + + mm + + + + + + + Additional offset to the selected bounding box along the Y axis. + + + mm + + + + + + + + + Cut Pattern + + + + + + + Enable separate optimization of transitions between, and breaks within, each step over path. + + + Optimize StepOver Transitions + + + + + + + Sample interval + + + + + + + Avoid cutting the last 'N' faces in the Base Geometry list of selected faces. + + + + + + + The amount by which the tool is laterally displaced on each cycle of the pattern, specified in percent of the tool diameter. A step over of 100% results in no overlap between two different cycles. - - - 1 - - - 100 - - - - - - - Sample interval - - - - - - - Set the sampling resolution. Smaller values quickly increase processing time. - - - mm - - - - - - - Make True, if specifying a Start Point - - - Use Start Point - - - - - - - Enable optimization of linear paths (co-linear points). Removes unnecessary co-linear points from G-code output. - - - Optimize Linear Paths - - - - - - - If true, the cutter will remain inside the boundaries of the model or selected face(s) - - - Boundary Enforcement - - - true - - - - - - - Enable separate optimization of transitions between, and breaks within, each step over path. - - - Optimize StepOver Transitions - - - - - + + + 1 + + + 100 + + + 5 + + + + + + + Depth offset + + + + + + + Bounding box extra offset X, Y + + + + + + + Make True, if specifying a Start Point + + + Use Start Point + + + + + + + Set the Z-axis depth offset from the target surface. + + + mm + + + + + + + Dropcutter lines are created parallel to this axis. + + + + + + + Complete the operation in a single pass at depth, or multiple passes to final depth. + + + + + + + Step over + + + + + + + Layer Mode + + + + + + + Planar: Flat, 3D surface scan. Rotational: 4th-axis rotational scan. + + + + + + + Set the geometric clearing pattern to use for the operation. + + + + + + + Select the overall boundary for the operation. + + + + + + + Profile Edges + + + + + + + Drop Cutter Direction + + + + + + + Set the sampling resolution. Smaller values quickly increase processing time. + + + mm + + + + + + + You might have a chance to use the LeadInOut Dressup (avoid Spiral cut pattern). + + + Optimize for LeadInOut + + + + + + + Reverse Cut Pattern + + + + diff --git a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py index ba7f389d3de2..ed8cb7215a0f 100644 --- a/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py +++ b/src/Mod/CAM/Path/Dressup/Gui/LeadInOut.py @@ -130,6 +130,14 @@ def __init__(self, obj): "App::Property", "Apply LeadInOut to layers within an operation" ), ) + obj.addProperty( + "App::PropertyBool", + "ReverseDirection", + "Path", + QT_TRANSLATE_NOOP( + "App::Property", "Reverse direction of LeadInOut" + ), + ) obj.Proxy = self self.wire = None @@ -153,6 +161,7 @@ def setup(self, obj): obj.ExtendLeadOut = 0 obj.RapidPlunge = False obj.IncludeLayers = True + obj.ReverseDirection = False def execute(self, obj): if not obj.Base: @@ -184,10 +193,16 @@ def getDirectionOfPath(self, obj): side = op.Side if hasattr(op, "Side") else "Inside" direction = op.Direction if hasattr(op, "Direction") else "Conventional" + if obj.ReverseDirection: + if side == "Inside": + side = "Outside" + else: + side = "Inside" + if side == "Outside": return "left" if direction == "Climb" else "right" - else: - return "right" if direction == "Climb" else "left" + # else: + # return "right" if direction == "Climb" else "left" def getArcDirection(self, obj): direction = self.getDirectionOfPath(obj) @@ -359,31 +374,57 @@ def generateLeadInOutCurve(self, obj): # we just use a flag and set it to false afterwards. To find the last # cutting move we need to search the list in reverse order. first = True + Sur = "(Surface)" lastCuttingMoveIndex = self.findLastCuttingMoveIndex(obj, source) - for i, instr in enumerate(source): - if not self.isCuttingMove(obj, instr): - # non-move instructions get added verbatim - if not instr.isMove(): - maneuver.addInstruction(instr) - - # skip travel and plunge moves, travel moves will be added in - # getLeadStart and getLeadEnd - continue - - if first or not self.isCuttingMove(obj, source[i - 1]): - # add lead start and travel moves - maneuver.addInstructions(self.getLeadStart(obj, instr, first)) - first = False - - # add current move - maneuver.addInstruction(instr) - - last = i == lastCuttingMoveIndex - if last or not self.isCuttingMove(obj, source[i + 1]): - # add lead end and travel moves - maneuver.addInstructions(self.getLeadEnd(obj, instr, last)) - + if Sur in source[0].cmd.title(): + LeadS = "--Start" + LeadE = "Break" + for i, instr in enumerate(source): + if LeadE in source[i-2].cmd.title() and obj.KeepToolDown: + pass + else: + if LeadS in source[i-3].cmd.title(): + # add lead start and travel moves + maneuver.addInstructions(self.getLeadStart(obj, instr, first)) + first = False + + elif LeadE in source[i-1].cmd.title(): + last = i == lastCuttingMoveIndex + # add lead end and travel moves + maneuver.addInstructions(self.getLeadEnd(obj, instr, last)) + elif LeadS in source[i-2].cmd.title(): + pass + + else: + # add current move + maneuver.addInstruction(instr) + + else: + for i, instr in enumerate(source): + + if not self.isCuttingMove(obj, instr): + # non-move instructions get added verbatim + if not instr.isMove(): + maneuver.addInstruction(instr) + + # skip travel and plunge moves, travel moves will be added in + # getLeadStart and getLeadEnd + continue + + if first or not self.isCuttingMove(obj, source[i - 1]): + # add lead start and travel moves + maneuver.addInstructions(self.getLeadStart(obj, instr, first)) + first = False + + # add current move + maneuver.addInstruction(instr) + + last = i == lastCuttingMoveIndex + if last or not self.isCuttingMove(obj, source[i + 1]): + # add lead end and travel moves + maneuver.addInstructions(self.getLeadEnd(obj, instr, last)) + return maneuver.toPath() @@ -403,6 +444,7 @@ def setupUi(self): self.connectWidget("RapidPlunge", self.form.chkRapidPlunge) self.connectWidget("IncludeLayers", self.form.chkLayers) self.connectWidget("KeepToolDown", self.form.chkKeepToolDown) + self.connectWidget("ReverseDirection", self.form.chkReverseDir) self.setFields() @@ -464,7 +506,8 @@ def GetResources(self): "MenuText": QT_TRANSLATE_NOOP("CAM_DressupLeadInOut", "LeadInOut"), "ToolTip": QT_TRANSLATE_NOOP( "CAM_DressupLeadInOut", - "Creates a Cutter Radius Compensation G41/G42 Entry Dressup object from a selected path", + "Creates a Cutter Radius Compensation G41/G42 Entry Dressup object" + " from a selected path", ), } diff --git a/src/Mod/CAM/Path/Dressup/Tags.py b/src/Mod/CAM/Path/Dressup/Tags.py index f9b24ec2d2d9..84c21af6ae10 100644 --- a/src/Mod/CAM/Path/Dressup/Tags.py +++ b/src/Mod/CAM/Path/Dressup/Tags.py @@ -50,13 +50,14 @@ def debugEdge(edge, prefix, force=False): pf = edge.valueAt(edge.FirstParameter) pl = edge.valueAt(edge.LastParameter) if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: - print( + pass + """print( "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % (prefix, type(edge.Curve), pf.x, pf.y, pf.z, pl.x, pl.y, pl.z) - ) + )""" else: pm = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2) - print( + """print( "%s %s((%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f))" % ( prefix, @@ -71,7 +72,7 @@ def debugEdge(edge, prefix, force=False): pl.y, pl.z, ) - ) + )""" def debugMarker(vector, label, color=None, radius=0.5): diff --git a/src/Mod/CAM/Path/Op/Adaptive.py b/src/Mod/CAM/Path/Op/Adaptive.py index 420f025744f4..f43cd2cdb7f1 100644 --- a/src/Mod/CAM/Path/Op/Adaptive.py +++ b/src/Mod/CAM/Path/Op/Adaptive.py @@ -678,11 +678,7 @@ def Execute(op, obj): # Get list of working edges for adaptive algorithm pathArray = op.pathArray if not pathArray: - msg = translate( - "CAM", - "Adaptive operation couldn't determine the boundary wire. Did you select base geometry?", - ) - FreeCAD.Console.PrintUserWarning(msg) + Path.Log.error("No wire data returned.") return path2d = convertTo2d(pathArray) @@ -830,7 +826,19 @@ def _get_working_edges(op, obj): # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) + subtractExt = [] + includeExt = [] for e in extensions: + if e.IndependentExtensionsList: + eNms = e._getEdgeNamesOp() + for ind in e.IndependentExtensionsList: + if eNms[0] in ind: + if float(ind.split(eNms[0]+":",1)[1]) < 0: + subtractExt.append(e) + else: + includeExt.append(e) + else: + includeExt = extensions if e.avoid: avoidFeatures.append(e.feature) @@ -862,17 +870,31 @@ def _get_working_edges(op, obj): edge_list.append([discretize(e)]) # Apply regular Extensions - op.exts = [] - for ext in extensions: - if not ext.avoid: - wire = ext.getWire() + op.exts = [] + if includeExt: + for ext in includeExt: + if not ext.avoid: + wire = ext.getWire() + if wire: + faces = ext.getExtensionFaces(wire) + for f in faces: + all_regions.append(f) + op.exts.append(f) + + op.subexts = [] + if subtractExt: + for sub in subtractExt: + wire = sub.getWire() if wire: - for f in ext.getExtensionFaces(wire): - op.exts.append(f) - all_regions.append(f) + faces = sub.getExtensionFaces(wire) + for f in faces: + op.subexts.append(f) # Second face-combining method attempted horizontal = Path.Geom.combineHorizontalFaces(all_regions) + if op.subexts: + op.cut = Path.Geom.combineHorizontalFaces(op.subexts) + horizontal[0] = horizontal[0].cut(op.cut) if horizontal: obj.removalshape = Part.makeCompound(horizontal) for f in horizontal: diff --git a/src/Mod/CAM/Path/Op/Area.py b/src/Mod/CAM/Path/Op/Area.py index 559f45424855..a48f9597b25c 100644 --- a/src/Mod/CAM/Path/Op/Area.py +++ b/src/Mod/CAM/Path/Op/Area.py @@ -223,26 +223,46 @@ def _buildPathArea(self, obj, baseobject, isHole, start, getsim): 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)) area.setParams(**areaParams) obj.AreaParams = str(area.getParams()) + + if hasattr(obj, "NoPathReverse") and obj.NoPathReverse: + heightsOr = heights + heights = heights[-1] Path.Log.debug("Area with params: {}".format(area.getParams())) sections = area.makeSections( mode=0, project=self.areaOpUseProjection(obj), heights=heights ) + if hasattr(obj, "multiProfile") and self.multiProfile: + sections = [] + restoreOff = obj.OffsetExtra.Value + + if self.multiCutOutsideIn: + heightsOr = heights + heights = heights[0] + + for ts in self.multiProfileSteps: + obj.OffsetExtra.Value = obj.OffsetExtra.Value + ts + areaParams = self.areaOpAreaParams(obj, isHole) + area.setParams(**areaParams) + MultiProfileSections = area.makeSections( + mode=0, project=self.areaOpUseProjection(obj), heights=heights + ) + obj.OffsetExtra.Value = restoreOff + + for w in MultiProfileSections: + sections.append(w) + 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: @@ -250,36 +270,15 @@ def _buildPathArea(self, obj, baseobject, isHole, start, getsim): 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 @@ -298,13 +297,16 @@ def _buildPathArea(self, obj, baseobject, isHole, start, getsim): 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 + + ReverseMulti = False + if hasattr(obj, "multiProfile") and self.multiProfile: + if self.multiCutOutsideIn: + pathParams["sort_mode"] = 1 + ReverseMulti = True + else: + pathParams["sort_mode"] = 0 if not self.areaOpRetractTool(obj): pathParams["threshold"] = 2.001 * self.radius @@ -335,6 +337,22 @@ def _buildPathArea(self, obj, baseobject, isHole, start, getsim): -1 ].getShape() simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax)) + + if ReverseMulti or (hasattr(obj, "NoPathReverse") and obj.NoPathReverse): + listR = [] + incHeight = 0 + countHeights = len(heightsOr) + + while countHeights > 0: + for c in pp.Commands: + if c.Z == heights: + c.Z = heightsOr[incHeight] + listR.append(c) + incHeight = incHeight + 1 + countHeights = countHeights - 1 + pp.Commands = listR + + return pp, simobj return pp, simobj @@ -446,6 +464,9 @@ def opExecute(self, obj, getsim=False): locations = PathUtils.sort_locations(locations, ["x", "y"]) shapes = [j["shape"] for j in locations] + + if hasattr(obj, "multiProfile") and self.multiCutOutsideIn: + shapes.reverse() sims = [] self.sectionShapes = [] @@ -489,12 +510,13 @@ def opExecute(self, obj, getsim=False): and self.endVector is not None and len(self.commandlist) > 1 ): - self.endVector[2] = obj.ClearanceHeight.Value - self.commandlist.append( - Path.Command( - "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} + if not hasattr(obj, "multiProfile"): + self.endVector[2] = obj.ClearanceHeight.Value + self.commandlist.append( + Path.Command( + "G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid} + ) ) - ) Path.Log.debug("obj.Name: " + str(obj.Name) + "\n\n") return sims diff --git a/src/Mod/CAM/Path/Op/FeatureExtension.py b/src/Mod/CAM/Path/Op/FeatureExtension.py index 7664db36abee..34b7c85500e8 100644 --- a/src/Mod/CAM/Path/Op/FeatureExtension.py +++ b/src/Mod/CAM/Path/Op/FeatureExtension.py @@ -139,6 +139,7 @@ def createExtension(obj, extObj, extFeature, extSub): extFeature, extSub, obj.ExtensionLengthDefault, + obj.IndependentExtensionsList, Extension.DirectionNormal, ) @@ -154,6 +155,12 @@ def readObjExtensionFeature(obj): extensions.append((extObj.Name, extFeature, extSub)) return extensions +def getIndependent(obj): + if not obj.IndependentExtensionsList: + obj.IndependentExtensionsList = [] + return obj.IndependentExtensionsList +def setIndependent(obj, inde): + obj.IndependentExtensionsList = inde def getExtensions(obj): Path.Log.debug("getExtenstions()") @@ -216,7 +223,7 @@ class Extension(object): DirectionX = 1 DirectionY = 2 - def __init__(self, op, obj, feature, sub, length, direction): + def __init__(self, op, obj, feature, sub, length, IndependentExtensionsList, direction): Path.Log.debug( "Extension(%s, %s, %s, %.2f, %s" % (obj.Label, feature, sub, length, direction) @@ -229,6 +236,7 @@ def __init__(self, op, obj, feature, sub, length, direction): self.direction = direction self.extFaces = None self.isDebug = True if Path.Log.getLevel(Path.Log.thisModule()) == 4 else False + self.IndependentExtensionsList = IndependentExtensionsList self.avoid = False if sub.startswith("Avoid_"): @@ -239,11 +247,12 @@ def __init__(self, op, obj, feature, sub, length, direction): def getSubLink(self): return "%s:%s" % (self.feature, self.sub) - def _extendEdge(self, feature, e0, direction): + def _extendEdge(self, feature, e0, direction, length): Path.Log.track(feature, e0, direction) if isinstance(e0.Curve, Part.Line) or isinstance(e0.Curve, Part.LineSegment): e2 = e0.copy() - off = self.length.Value * direction + #off = self.length.Value * direction + off = length * direction e2.translate(off) e2 = Path.Geom.flipEdge(e2) e1 = Part.Edge( @@ -261,6 +270,17 @@ def _extendEdge(self, feature, e0, direction): return wire return extendWire(feature, Part.Wire([e0]), self.length.Value) + + def _getEdgeNumbersOp(self): + if "Wire" in self.sub: + numbers = [nr for nr in self.sub[5:-1].split(",")] + else: + numbers = [self.sub[4:]] + Path.Log.debug("_getEdgeNumbers() -> %s" % numbers) + return numbers + + def _getEdgeNamesOp(self): + return ["Edge%s" % nr for nr in self._getEdgeNumbersOp()] def _getEdgeNumbers(self): if "Wire" in self.sub: @@ -269,7 +289,7 @@ def _getEdgeNumbers(self): numbers = [self.sub[4:]] Path.Log.debug("_getEdgeNumbers() -> %s" % numbers) - return numbers + return numbers def _getEdgeNames(self): return ["Edge%s" % nr for nr in self._getEdgeNumbers()] @@ -331,6 +351,15 @@ def _getRegularWire(self): feature = self.obj.Shape.getElement(self.feature) edges = self._getEdges() + edgesName = self._getEdgeNames() + + if self.IndependentExtensionsList: + for ind in self.IndependentExtensionsList: + if edgesName[0] in ind: + newlength = float(ind.split(edgesName[0]+":",1)[1]) + if newlength != 0.0: + length = newlength + break sub = Part.Wire(Part.sortEdges(edges)[0]) if 1 == len(edges): @@ -418,7 +447,7 @@ def _getRegularWire(self): if direction is None: return None - return self._extendEdge(feature, edges[0], direction) + return self._extendEdge(feature, edges[0], direction, length) elif sub.isClosed(): Path.Log.debug("Extending multi-edge closed wire") @@ -499,20 +528,41 @@ def initialize_properties(obj): "When enabled connected extension edges are combined to wires.", ), ) - obj.ExtensionCorners = True + obj.ExtensionCorners = False + + if not hasattr(obj, "IndependentExtensions"): + obj.addProperty( + "App::PropertyBool", + "IndependentExtensions", + "Extension", + QT_TRANSLATE_NOOP( + "App::Property", + "Extend Edges Independently.", + ), + ) + if not hasattr(obj, "IndependentExtensionsList"): + obj.addProperty( + "App::PropertyStringList", + "IndependentExtensionsList", + "Extension", + QT_TRANSLATE_NOOP( + "App::Property", + "List of Edges to Extend Independently.", + ), + ) obj.setEditorMode("ExtensionFeature", 2) def set_default_property_values(obj, job): """set_default_property_values(obj, job) ... set default values for feature properties""" - obj.ExtensionCorners = True + obj.ExtensionCorners = False obj.setExpression("ExtensionLengthDefault", "OpToolDiameter / 2.0") def SetupProperties(): """SetupProperties()... Returns list of feature property names""" - setup = ["ExtensionLengthDefault", "ExtensionFeature", "ExtensionCorners"] + setup = ["ExtensionLengthDefault", "ExtensionFeature", "ExtensionCorners", "IndependentExtensions", "IndependentExtensionsList"] return setup diff --git a/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py b/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py index 75cb156ad0ec..e596505b9410 100644 --- a/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py +++ b/src/Mod/CAM/Path/Op/Gui/FeatureExtension.py @@ -28,6 +28,7 @@ import Path.Base.Gui.Util as PathGuiUtil import Path.Op.FeatureExtension as FeatureExtensions import Path.Op.Gui.Base as PathOpGui +import operator as op # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader @@ -174,6 +175,8 @@ def deselect(self): class TaskPanelExtensionPage(PathOpGui.TaskPanelPage): DataObject = QtCore.Qt.ItemDataRole.UserRole DataSwitch = QtCore.Qt.ItemDataRole.UserRole + 2 + DataInde = QtCore.Qt.ItemDataRole.UserRole + 4 + DataLabel = QtCore.Qt.ItemDataRole.UserRole + 8 Direction = { FeatureExtensions.Extension.DirectionNormal: translate("CAM_Pocket", "Normal"), @@ -194,8 +197,11 @@ def initPage(self, obj): self.extensionsReady = False self.enabled = True self.lastDefaultLength = "" + self.lostInLoop = False self.extensions = list() + + self.independentExtensions = obj.IndependentExtensionsList self.defaultLength = PathGuiUtil.QuantitySpinBox( self.form.defaultLength, obj, "ExtensionLengthDefault" @@ -227,7 +233,7 @@ def initPage(self, obj): def cleanupPage(self, obj): try: self.obj.ViewObject.RootNode.removeChild(self.switch) - except (ReferenceError, RuntimeError) as e: + except: Path.Log.debug("obj already destroyed - no cleanup required") def getForm(self): @@ -235,6 +241,7 @@ def getForm(self): return form def forAllItemsCall(self, cb): + tempList = [] for modelRow in range(self.model.rowCount()): model = self.model.item(modelRow, 0) for featureRow in range(model.rowCount()): @@ -243,6 +250,16 @@ def forAllItemsCall(self, cb): item = feature.child(edgeRow, 0) ext = item.data(self.DataObject) cb(item, ext) + if self.form.independentExtensions.isChecked(): + for ind in range(item.rowCount()): + item0 = item.child(ind, 0) + indeExt = str(item0.data(QtCore.Qt.EditRole)) + indeLabel = item0.data(self.DataLabel) + Div = ":" + StringList = indeLabel+Div+indeExt + tempList.append(StringList) + if tempList != self.independentExtensions and self.lostInLoop: + self.independentExtensions = tempList def currentExtensions(self): Path.Log.debug("currentExtensions()") @@ -261,6 +278,8 @@ def updateProxyExtensions(self, obj): Path.Log.debug("updateProxyExtensions()") self.extensions = self.currentExtensions() FeatureExtensions.setExtensions(obj, self.extensions) + FeatureExtensions.setIndependent(obj, self.independentExtensions) + self.independentExtensions = [] def getFields(self, obj): Path.Log.track(obj.Label, self.model.rowCount(), self.model.columnCount()) @@ -268,6 +287,10 @@ def getFields(self, obj): if obj.ExtensionCorners != self.form.extendCorners.isChecked(): obj.ExtensionCorners = self.form.extendCorners.isChecked() + + if obj.IndependentExtensions != self.form.independentExtensions.isChecked(): + obj.IndependentExtensions = self.form.independentExtensions.isChecked() + self.defaultLength.updateProperty() self.updateProxyExtensions(obj) @@ -279,6 +302,9 @@ def setFields(self, obj): if obj.ExtensionCorners != self.form.extendCorners.isChecked(): self.form.extendCorners.toggle() + + if obj.IndependentExtensions != self.form.independentExtensions.isChecked(): + obj.IndependentExtensions = self.form.independentExtensions.isChecked() self._autoEnableExtensions() # Check edge count for auto-disable Extensions on initial Task Panel loading self._initializeExtensions(obj) # Efficiently initialize Extensions @@ -292,13 +318,18 @@ def _initializeExtensions(self, obj): Subroutine called inside `setFields()` to initialize Extensions efficiently.""" if self.enabled: self.extensions = FeatureExtensions.getExtensions(obj) + self.independentExtensions = FeatureExtensions.getIndependent(obj) + self.lostInLoop = False elif len(obj.ExtensionFeature) > 0: self.extensions = FeatureExtensions.getExtensions(obj) + self.independentExtensions = FeatureExtensions.getIndependent(obj) + self.lostInLoop = False self.form.enableExtensions.setChecked(True) self._includeEdgesAndWires() else: self.form.extensionEdit.setDisabled(True) self.setExtensions(self.extensions) + self.lostInLoop = True def _applyDefaultLengthChange(self, index=None): """_applyDefaultLengthChange(index=None)... @@ -331,7 +362,29 @@ def createSubItem(label, ext0): item0.setCheckState(QtCore.Qt.Checked) ext0.enable() break - item.appendRow([item0]) + + indExt = 0.0 + f = False + if label[:4] == "Edge" and self.form.independentExtensions.isChecked(): + for ind in self.independentExtensions: + if label in ind: + indExt = float(ind.split(":",1)[1]) + f = True + break + if not f: + Div = ":0.0" + temP = label+Div + self.independentExtensions.append(temP) + + item2 = QtGui.QStandardItem() + item2.isEditable() + item2.setData(indExt, QtCore.Qt.EditRole) + item2.setData(label, self.DataLabel) + self.DataInde = item2.data(QtCore.Qt.EditRole) + + item0.appendRow([item2]) + + item.appendRow([item0]) # ext = self._cachedExtension(self.obj, base, sub, None) ext = None @@ -414,10 +467,13 @@ def setExtensions(self, extensions): # remember current visual state if hasattr(self, "selectionModel"): - selectedExtensions = [ - self.model.itemFromIndex(index).data(self.DataObject).ext - for index in self.selectionModel.selectedIndexes() - ] + try: + selectedExtensions = [ + self.model.itemFromIndex(index).data(self.DataObject).ext + for index in self.selectionModel.selectedIndexes() + ] + except: + selectedExtensions = [] else: selectedExtensions = [] collapsedModels = [] @@ -464,8 +520,10 @@ def removeItemSwitch(item, ext): self.model.appendRow(baseItem) self.form.extensionTree.setModel(self.model) - self.form.extensionTree.expandAll() + #self.form.extensionTree.expandAll() self.form.extensionTree.resizeColumnToContents(0) + self.form.extensionTree.setEditTriggers(QtGui.QAbstractItemView.DoubleClicked) + self.form.extensionTree.expandToDepth(1) # restore previous state - at least the parts that are still valid for modelRow in range(self.model.rowCount()): @@ -583,12 +641,19 @@ def extensionsEnable(self): def updateItemEnabled(self, item): Path.Log.track(item) ext = item.data(self.DataObject) - if item.checkState() == QtCore.Qt.Checked: - ext.enable() - else: - ext.disable() + if ext: + if item.checkState() == QtCore.Qt.Checked: + ext.enable() + else: + ext.disable() self.updateProxyExtensions(self.obj) self.setDirty() + if not ext: + self._resetCachedExtensions() + self.form.extensionEdit.setEnabled(True) + self.extensions = FeatureExtensions.getExtensions(self.obj) + self.independentExtensions = FeatureExtensions.getIndependent(self.obj) + self.setExtensions(self.extensions) def showHideExtension(self): if self.form.showExtensions.isChecked(): @@ -605,10 +670,28 @@ def disableExtensionEdit(item, ext): self.forAllItemsCall(disableExtensionEdit) # self.setDirty() + + def showHideIndependent(self): + if self.form.independentExtensions.isChecked(): + self.form.extendCorners.setChecked(False) + self.form.extendCorners.setDisabled(True) + else: + self.form.extendCorners.setEnabled(True) + + self.extensionsReady = False + extensions = FeatureExtensions.getExtensions(self.obj) + self.setExtensions(extensions) + self.selectionChanged() + self.setDirty() def toggleExtensionCorners(self): Path.Log.debug("toggleExtensionCorners()") Path.Log.track() + if self.form.extendCorners.isChecked(): + self.form.independentExtensions.setChecked(False) + self.form.independentExtensions.setDisabled(True) + else: + self.form.independentExtensions.setEnabled(True) self.extensionsReady = False extensions = FeatureExtensions.getExtensions(self.obj) self.setExtensions(extensions) @@ -625,6 +708,7 @@ def getSignalsForUpdate(self, obj): def registerSignalHandlers(self, obj): self.form.showExtensions.clicked.connect(self.showHideExtension) self.form.extendCorners.clicked.connect(self.toggleExtensionCorners) + self.form.independentExtensions.clicked.connect(self.showHideIndependent) self.form.buttonClear.clicked.connect(self.extensionsClear) self.form.buttonDisable.clicked.connect(self.extensionsDisable) self.form.buttonEnable.clicked.connect(self.extensionsEnable) @@ -657,6 +741,10 @@ def _getUseOutlineState(self): ): Path.Log.debug("Found useOutline checkbox") self.useOutlineCheckbox = page.form.useOutline + if hasattr(page.form, "clearEdges") and not page.form.clearEdges.isHidden(): + page.form.useOutline.hide() + self.form.extendCorners.setChecked(False) + self.form.extendCorners.setDisabled(True) if page.form.useOutline.isChecked(): self.useOutline = 1 return @@ -699,6 +787,7 @@ def _enableExtensions(self): self.extensionsReady = False self.form.extensionEdit.setEnabled(True) self.extensions = FeatureExtensions.getExtensions(self.obj) + self.independentExtensions = FeatureExtensions.getIndependent(self.obj) self.setExtensions(self.extensions) else: self.form.extensionEdit.setDisabled(True) diff --git a/src/Mod/CAM/Path/Op/Gui/MillFace.py b/src/Mod/CAM/Path/Op/Gui/MillFace.py index 011525d8e611..6657e1ce3f36 100644 --- a/src/Mod/CAM/Path/Op/Gui/MillFace.py +++ b/src/Mod/CAM/Path/Op/Gui/MillFace.py @@ -24,6 +24,7 @@ import FreeCAD import Path import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.FeatureExtension as PathFeatureExtensionsGui import Path.Op.Gui.PocketBase as PathPocketBaseGui import Path.Op.MillFace as PathMillFace import Path.Op.PocketShape as PathPocketShape @@ -66,7 +67,13 @@ def getForm(self): def pocketFeatures(self): """pocketFeatures() ... return FeatureFacing (see PathPocketBaseGui)""" return PathPocketBaseGui.FeatureFacing - + + def taskPanelBaseLocationPage(self, obj, features): + if not hasattr(self, "extensionsPanel"): + self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage( + obj, features + ) + return self.extensionsPanel Command = PathOpGui.SetupOperation( "MillFace", diff --git a/src/Mod/CAM/Path/Op/Gui/Pocket.py b/src/Mod/CAM/Path/Op/Gui/Pocket.py index 18ba4750cc58..205b1cbc8225 100644 --- a/src/Mod/CAM/Path/Op/Gui/Pocket.py +++ b/src/Mod/CAM/Path/Op/Gui/Pocket.py @@ -23,6 +23,7 @@ import FreeCAD import Path import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.FeatureExtension as PathFeatureExtensionsGui import Path.Op.Gui.PocketBase as PathPocketBaseGui import Path.Op.Pocket as PathPocket @@ -48,6 +49,13 @@ def pocketFeatures(self): """pocketFeatures() ... return FeaturePocket (see PathPocketBaseGui)""" return PathPocketBaseGui.FeaturePocket | PathPocketBaseGui.FeatureRestMachining + def taskPanelBaseLocationPage(self, obj, features): + if not hasattr(self, "extensionsPanel"): + self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage( + obj, features + ) + + return self.extensionsPanel Command = PathOpGui.SetupOperation( "Pocket3D", diff --git a/src/Mod/CAM/Path/Op/Gui/PocketBase.py b/src/Mod/CAM/Path/Op/Gui/PocketBase.py index f83fc8dbc22b..9f4b83e88b7e 100644 --- a/src/Mod/CAM/Path/Op/Gui/PocketBase.py +++ b/src/Mod/CAM/Path/Op/Gui/PocketBase.py @@ -68,14 +68,14 @@ def getForm(self): comboToPropertyMap = [ ("cutMode", "CutMode"), - ("offsetPattern", "OffsetPattern"), + ("offsetPattern", "OffsetPattern") ] enumTups = PathPocket.ObjectPocket.pocketPropertyEnumerations(dataType="raw") self.populateCombobox(form, enumTups, comboToPropertyMap) if not FeatureFacing & self.pocketFeatures(): - form.facingWidget.hide() + #form.facingWidget.hide() form.clearEdges.hide() if FeaturePocket & self.pocketFeatures(): @@ -109,6 +109,32 @@ def updateZigZagAngle(self, obj, setModel=True): if setModel: PathGuiUtil.updateInputField(obj, "ZigZagAngle", self.form.zigZagAngle) + def updateFinishingOffset(self, obj, setModel=True): + if obj.OffsetPattern in ["Offset"] and hasattr(obj, "HandleMultipleFeatures"): + self.form.useFinishingOffset.show() + else: + self.form.useFinishingOffset.hide() + + def updateNoPathReverse(self, obj, setModel=True): + if self.form.useStartPoint.isChecked() and not hasattr(obj, "HandleMultipleFeatures"): + self.form.NoPathReverse.show() + else: + self.form.NoPathReverse.hide() + self.form.NoPathReverse.setChecked(False) + + def updateRestMachining(self, obj, setModel=True): + if self.form.useFinishingOffset.isChecked() and obj.OffsetPattern in ["Offset"]: + self.form.useRestMachining.setChecked(False) + self.form.useRestMachining.setEnabled(False) + else: + self.form.useRestMachining.setEnabled(True) + + def updatestepOverPercent(self, obj, setModel=True): + if self.form.useFinishingOffset.isChecked() and obj.OffsetPattern in ["Offset"]: + self.form.stepOverPercent.setEnabled(False) + else: + self.form.stepOverPercent.setEnabled(True) + def getFields(self, obj): """getFields(obj) ... transfers values from UI to obj's properties""" if obj.CutMode != str(self.form.cutMode.currentData()): @@ -116,15 +142,24 @@ def getFields(self, obj): if obj.StepOver != self.form.stepOverPercent.value(): obj.StepOver = self.form.stepOverPercent.value() if obj.OffsetPattern != str(self.form.offsetPattern.currentData()): - obj.OffsetPattern = str(self.form.offsetPattern.currentData()) + obj.OffsetPattern = str(self.form.offsetPattern.currentData()) PathGuiUtil.updateInputField(obj, "ExtraOffset", self.form.extraOffset) self.updateToolController(obj, self.form.toolController) self.updateCoolant(obj, self.form.coolantController) self.updateZigZagAngle(obj) + self.updateFinishingOffset(obj) + self.updatestepOverPercent(obj) + self.updateRestMachining(obj) if obj.UseStartPoint != self.form.useStartPoint.isChecked(): obj.UseStartPoint = self.form.useStartPoint.isChecked() + + if obj.UseFinishingOffset != self.form.useFinishingOffset.isChecked(): + obj.UseFinishingOffset = self.form.useFinishingOffset.isChecked() + + if obj.NoPathReverse != self.form.NoPathReverse.isChecked(): + obj.NoPathReverse = self.form.NoPathReverse.isChecked() if obj.UseRestMachining != self.form.useRestMachining.isChecked(): obj.UseRestMachining = self.form.useRestMachining.isChecked() @@ -133,12 +168,10 @@ def getFields(self, obj): if obj.UseOutline != self.form.useOutline.isChecked(): obj.UseOutline = self.form.useOutline.isChecked() - self.updateMinTravel(obj) + self.updateMinTravel(obj) + self.updateNoPathReverse(obj) if FeatureFacing & self.pocketFeatures(): - print(obj.BoundaryShape) - print(self.form.boundaryShape.currentText()) - print(self.form.boundaryShape.currentData()) if obj.BoundaryShape != str(self.form.boundaryShape.currentData()): obj.BoundaryShape = str(self.form.boundaryShape.currentData()) if obj.ClearEdges != self.form.clearEdges.isChecked(): @@ -153,6 +186,8 @@ def setFields(self, obj): ).UserString ) self.form.useStartPoint.setChecked(obj.UseStartPoint) + self.form.useFinishingOffset.setChecked(obj.UseFinishingOffset) + self.form.NoPathReverse.setChecked(obj.NoPathReverse) self.form.useRestMachining.setChecked(obj.UseRestMachining) if FeatureOutline & self.pocketFeatures(): self.form.useOutline.setChecked(obj.UseOutline) @@ -164,8 +199,12 @@ def setFields(self, obj): self.form.minTravel.setChecked(obj.MinTravel) self.updateMinTravel(obj, False) + self.updateFinishingOffset(obj) + self.updatestepOverPercent(obj) + self.updateRestMachining(obj) - self.selectInComboBox(obj.OffsetPattern, self.form.offsetPattern) + self.selectInComboBox(obj.OffsetPattern, self.form.offsetPattern) + self.updateNoPathReverse(obj) self.selectInComboBox(obj.CutMode, self.form.cutMode) self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) @@ -179,12 +218,14 @@ def getSignalsForUpdate(self, obj): signals = [] signals.append(self.form.cutMode.currentIndexChanged) - signals.append(self.form.offsetPattern.currentIndexChanged) + signals.append(self.form.offsetPattern.currentIndexChanged) signals.append(self.form.stepOverPercent.editingFinished) signals.append(self.form.zigZagAngle.editingFinished) signals.append(self.form.toolController.currentIndexChanged) signals.append(self.form.extraOffset.editingFinished) signals.append(self.form.useStartPoint.clicked) + signals.append(self.form.useFinishingOffset.clicked) + signals.append(self.form.NoPathReverse.clicked) signals.append(self.form.useRestMachining.clicked) signals.append(self.form.useOutline.clicked) signals.append(self.form.minTravel.clicked) diff --git a/src/Mod/CAM/Path/Op/Gui/Profile.py b/src/Mod/CAM/Path/Op/Gui/Profile.py index b161bbcde164..327d95b4020c 100644 --- a/src/Mod/CAM/Path/Op/Gui/Profile.py +++ b/src/Mod/CAM/Path/Op/Gui/Profile.py @@ -77,6 +77,16 @@ def getFields(self, obj): if obj.Direction != str(self.form.direction.currentData()): obj.Direction = str(self.form.direction.currentData()) PathGuiUtil.updateInputField(obj, "OffsetExtra", self.form.extraOffset) + + PathGuiUtil.updateInputField(obj, "multiProfileWidth", self.form.multiProfileWidth) + if obj.multiStepOver != self.form.multiStepOver.value(): + obj.multiStepOver = self.form.multiStepOver.value() + + if obj.multiProfile != self.form.multiProfile.isChecked(): + obj.multiProfile = self.form.multiProfile.isChecked() + + if obj.cutOutsideIn != self.form.cutOutsideIn.isChecked(): + obj.cutOutsideIn = self.form.cutOutsideIn.isChecked() if obj.UseComp != self.form.useCompensation.isChecked(): obj.UseComp = self.form.useCompensation.isChecked() @@ -89,9 +99,12 @@ def getFields(self, obj): obj.processPerimeter = self.form.processPerimeter.isChecked() if obj.processCircles != self.form.processCircles.isChecked(): obj.processCircles = self.form.processCircles.isChecked() + + self.updatemultiProfile(obj) def setFields(self, obj): """setFields(obj) ... transfers obj's property values to UI""" + self.form.multiStepOver.setValue(obj.multiStepOver) self.setupToolController(obj, self.form.toolController) self.setupCoolant(obj, self.form.coolantController) @@ -102,12 +115,22 @@ def setFields(self, obj): obj.OffsetExtra.Value, FreeCAD.Units.Length ).UserString ) + + self.form.multiProfileWidth.setText( + FreeCAD.Units.Quantity( + obj.multiProfileWidth.Value, FreeCAD.Units.Length + ).UserString + ) self.form.useCompensation.setChecked(obj.UseComp) self.form.useStartPoint.setChecked(obj.UseStartPoint) self.form.processHoles.setChecked(obj.processHoles) self.form.processPerimeter.setChecked(obj.processPerimeter) self.form.processCircles.setChecked(obj.processCircles) + self.form.multiProfile.setChecked(obj.multiProfile) + self.form.cutOutsideIn.setChecked(obj.cutOutsideIn) + + self.updatemultiProfile(obj) self.updateVisibility() @@ -124,6 +147,10 @@ def getSignalsForUpdate(self, obj): signals.append(self.form.processHoles.stateChanged) signals.append(self.form.processPerimeter.stateChanged) signals.append(self.form.processCircles.stateChanged) + signals.append(self.form.multiProfile.stateChanged) + signals.append(self.form.multiProfileWidth.editingFinished) + signals.append(self.form.multiStepOver.editingFinished) + signals.append(self.form.cutOutsideIn.stateChanged) return signals @@ -153,6 +180,23 @@ def updateVisibility(self): def registerSignalHandlers(self, obj): self.form.useCompensation.stateChanged.connect(self.updateVisibility) + def updatemultiProfile(self, obj): + + if hasattr(obj, "multiProfile") and not obj.multiProfile: + self.form.multiProfileWidthLabel.hide() + self.form.cutOutsideIn.hide() + self.form.stepOverLabel.hide() + self.form.multiProfileWidth.hide() + self.form.multiStepOver.hide() + else: + self.form.multiProfileWidthLabel.show() + self.form.cutOutsideIn.show() + self.form.stepOverLabel.show() + self.form.multiProfileWidth.show() + self.form.multiStepOver.show() + + def registerSignalHandlers(self, obj): + self.form.useCompensation.stateChanged.connect(self.updateVisibility) # Eclass diff --git a/src/Mod/CAM/Path/Op/Gui/Surface.py b/src/Mod/CAM/Path/Op/Gui/Surface.py index d7c513c7ccf5..0934bd726590 100644 --- a/src/Mod/CAM/Path/Op/Gui/Surface.py +++ b/src/Mod/CAM/Path/Op/Gui/Surface.py @@ -26,6 +26,7 @@ import Path import Path.Base.Gui.Util as PathGuiUtil import Path.Op.Gui.Base as PathOpGui +import Path.Op.Gui.FeatureExtension as PathFeatureExtensionsGui import Path.Op.Surface as PathSurface import PathGui @@ -135,6 +136,12 @@ def getFields(self, obj): if obj.OptimizeLinearPaths != self.form.optimizeEnabled.isChecked(): obj.OptimizeLinearPaths = self.form.optimizeEnabled.isChecked() + + if obj.OptimizeLeadInOut != self.form.optimizeLeadInOut.isChecked(): + obj.OptimizeLeadInOut = self.form.optimizeLeadInOut.isChecked() + + if obj.CutPatternReversed != self.form.cutPatternReversed.isChecked(): + obj.CutPatternReversed = self.form.cutPatternReversed.isChecked() if ( obj.OptimizeStepOverTransitions @@ -210,6 +217,16 @@ def setFields(self, obj): self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Checked) else: self.form.optimizeStepOverTransitions.setCheckState(QtCore.Qt.Unchecked) + + if obj.OptimizeLeadInOut: + self.form.optimizeLeadInOut.setCheckState(QtCore.Qt.Checked) + else: + self.form.optimizeLeadInOut.setCheckState(QtCore.Qt.Unchecked) + + if obj.CutPatternReversed: + self.form.cutPatternReversed.setCheckState(QtCore.Qt.Checked) + else: + self.form.cutPatternReversed.setCheckState(QtCore.Qt.Unchecked) self.updateVisibility() @@ -234,6 +251,8 @@ def getSignalsForUpdate(self, obj): signals.append(self.form.boundaryEnforcement.stateChanged) signals.append(self.form.optimizeEnabled.stateChanged) signals.append(self.form.optimizeStepOverTransitions.stateChanged) + signals.append(self.form.optimizeLeadInOut.stateChanged) + signals.append(self.form.cutPatternReversed.stateChanged) return signals @@ -254,6 +273,13 @@ def updateVisibility(self, sentObj=None): self.form.boundBoxExtraOffset_label.hide() self.form.dropCutterDirSelect.hide() self.form.dropCutterDirSelect_label.hide() + if self.form.optimizeStepOverTransitions.isChecked(): + self.form.optimizeLeadInOut.show() + else: + self.form.optimizeLeadInOut.setCheckState(QtCore.Qt.Unchecked) + self.form.optimizeLeadInOut.hide() + if self.form.optimizeLeadInOut.isChecked(): + self.form.optimizeEnabled.setCheckState(QtCore.Qt.Unchecked) elif self.form.scanType.currentText() == "Rotational": self.form.cutPattern.hide() self.form.cutPattern_label.hide() @@ -272,7 +298,15 @@ def updateVisibility(self, sentObj=None): def registerSignalHandlers(self, obj): self.form.scanType.currentIndexChanged.connect(self.updateVisibility) + self.form.optimizeStepOverTransitions.stateChanged.connect(self.updateVisibility) + self.form.optimizeLeadInOut.stateChanged.connect(self.updateVisibility) + def taskPanelBaseLocationPage(self, obj, features): + if not hasattr(self, "extensionsPanel"): + self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage( + obj, features + ) + return self.extensionsPanel Command = PathOpGui.SetupOperation( "Surface", diff --git a/src/Mod/CAM/Path/Op/MillFace.py b/src/Mod/CAM/Path/Op/MillFace.py index 5925d1960cb7..0f4136d3b0aa 100644 --- a/src/Mod/CAM/Path/Op/MillFace.py +++ b/src/Mod/CAM/Path/Op/MillFace.py @@ -23,6 +23,7 @@ import FreeCAD import Path +import Path.Op.Base as PathOp import Path.Op.PocketBase as PathPocketBase import PathScripts.PathUtils as PathUtils from PySide.QtCore import QT_TRANSLATE_NOOP @@ -33,6 +34,10 @@ Part = LazyLoader("Part", globals(), "Part") +FeatureExtensions = LazyLoader( + "Path.Op.FeatureExtension", globals(), "Path.Op.FeatureExtension" +) + __title__ = "CAM Mill Face Operation" __author__ = "sliptonic (Brad Collette)" __url__ = "https://www.freecad.org" @@ -85,6 +90,9 @@ def propertyEnumerations(self, dataType="data"): Path.Log.debug(data) return data + + def areaOpFeatures(self, obj): + return super(self.__class__, self).areaOpFeatures(obj) | PathOp.FeatureLocations def initPocketOp(self, obj): Path.Log.track() @@ -115,6 +123,8 @@ def initPocketOp(self, obj): for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) + + FeatureExtensions.initialize_properties(obj) def pocketInvertExtraOffset(self): return True @@ -152,21 +162,70 @@ def areaOpShapes(self, obj): # Facing is done either against base objects self.removalshapes = [] holeShape = None + negativeExt = [] Path.Log.debug("depthparams: {}".format([i for i in self.depthparams])) + # Get extensions and identify faces to avoid + extensions = FeatureExtensions.getExtensions(obj) + exts = [] + subexts = [] + + subtractExt = [] + includeExt = [] + for e in extensions: + if not e.avoid: + if e.IndependentExtensionsList: + eNms = e._getEdgeNamesOp() + for ind in e.IndependentExtensionsList: + if eNms[0] in ind: + if float(ind.split(eNms[0]+":",1)[1]) < 0: + subtractExt.append(e) + else: + includeExt.append(e) + else: + includeExt.append(e) + else: + includeExt = extensions + + for exx in includeExt: + wire = exx.getWire() + if wire: + faces = exx.getExtensionFaces(wire) + for f in faces: + if isinstance(f, Part.Face): + exts.append(f) + for xe in subtractExt: + wire = xe.getWire() + if wire: + faces = xe.getExtensionFaces(wire) + for f in faces: + if isinstance(f, Part.Face): + subexts.append(f) + if obj.Base: Path.Log.debug("obj.Base: {}".format(obj.Base)) faces = [] holes = [] holeEnvs = [] + exEnvs = [] oneBase = [obj.Base[0][0], True] sub0 = getattr(obj.Base[0][0].Shape, obj.Base[0][1][0]) minHeight = sub0.BoundBox.ZMax + beenH = False for b in obj.Base: - for sub in b[1]: - shape = getattr(b[0].Shape, sub) + bb = list(b[1]) + if beenH is False: + for ex in exts: + bb.append(ex) + beenH = True + for sub in bb: + # Check if Face or Exten + if isinstance(sub, str): + shape = getattr(b[0].Shape, sub) + else: + shape = sub # extension Face if isinstance(shape, Part.Face): faces.append(shape) if shape.BoundBox.ZMin < minHeight: @@ -192,6 +251,14 @@ def areaOpShapes(self, obj): ) ) + if subexts: + for su in subexts: + env = PathUtils.getEnvelope( + shape, subshape=su, depthparams=self.depthparams + ) + exEnvs.append(env) + negativeExt.append(Part.makeCompound(exEnvs)) + if obj.ExcludeRaisedAreas and len(holes) > 0: for shape, wire in holes: f = Part.makeFace(wire, "Part::FaceMakerSimple") @@ -315,6 +382,18 @@ def areaOpShapes(self, obj): else: Path.Log.debug("Processing solid face ...") tup = env, False, "pathMillFace" + + if negativeExt: + cutOut = [] + Path.Log.debug("Processing negative extensions ...") + for hs in negativeExt: + extEnv = PathUtils.getEnvelope( + partshape=hs, depthparams=self.depthparams + ) + cutOut.append(extEnv) + for cc in cutOut: + neEnv = env.cut(cc) + tup = neEnv, False, "pathMillFace" self.removalshapes.append(tup) obj.removalshape = self.removalshapes[0][0] # save removal shape @@ -323,11 +402,11 @@ def areaOpShapes(self, obj): def areaOpSetDefaultValues(self, obj, job): """areaOpSetDefaultValues(obj, job) ... initialize mill facing properties""" - obj.StepOver = 50 - obj.ZigZagAngle = 45.0 + obj.StepOver = 85 + obj.ZigZagAngle = 0.0 obj.ExcludeRaisedAreas = False obj.ClearEdges = False - + FeatureExtensions.set_default_property_values(obj, job) # need to overwrite the default depth calculations for facing if job and len(job.Model.Group) > 0: obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax @@ -396,6 +475,9 @@ def isOverlap(faceMin, faceMax, envMin, envMax): def SetupProperties(): setup = PathPocketBase.SetupProperties() + setup.extend( + FeatureExtensions.SetupProperties() + ) setup.append("BoundaryShape") setup.append("ExcludeRaisedAreas") setup.append("ClearEdges") diff --git a/src/Mod/CAM/Path/Op/Pocket.py b/src/Mod/CAM/Path/Op/Pocket.py index a961f53ffb1d..c96439892d9e 100644 --- a/src/Mod/CAM/Path/Op/Pocket.py +++ b/src/Mod/CAM/Path/Op/Pocket.py @@ -31,6 +31,11 @@ # lazily loaded modules from lazy_loader.lazy_loader import LazyLoader +Part = LazyLoader("Part", globals(), "Part") +FeatureExtensions = LazyLoader( + "Path.Op.FeatureExtension", globals(), "Path.Op.FeatureExtension" +) + __title__ = "CAM 3D Pocket Operation" __author__ = "Yorik van Havre " __url__ = "https://www.freecad.org" @@ -51,7 +56,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket): """Proxy object for Pocket operation.""" def pocketOpFeatures(self, obj): - return PathOp.FeatureNoFinalDepth + return PathOp.FeatureNoFinalDepth | PathOp.FeatureLocations def initPocketOp(self, obj): """initPocketOp(obj) ... setup receiver""" @@ -100,6 +105,8 @@ def initPocketOp(self, obj): # populate the property enumerations for n in self.propertyEnumerations(): setattr(obj, n[0], n[1]) + + FeatureExtensions.initialize_properties(obj) @classmethod def propertyEnumerations(self, dataType="data"): @@ -157,9 +164,46 @@ def areaOpShapes(self, obj): subObjTups = [] removalshapes = [] + + extensions = FeatureExtensions.getExtensions(obj) + subtractExt = [] + includeExt = [] + + for e in extensions: + if e.IndependentExtensionsList: + eNms = e._getEdgeNamesOp() + for ind in e.IndependentExtensionsList: + if eNms[0] in ind: + if float(ind.split(eNms[0]+":",1)[1]) < 0: + subtractExt.append(e) + else: + includeExt.append(e) + else: + includeExt = extensions + if e.avoid: + avoidFeatures.append(e.feature) + + exts = [] + if includeExt: + for ext in includeExt: + if not ext.avoid: + wire = ext.getWire() + if wire: + faces = ext.getExtensionFaces(wire) + for f in faces: + exts.append(f) + subexts = [] + if subtractExt: + for sub in subtractExt: + wire = sub.getWire() + if wire: + faces = sub.getExtensionFaces(wire) + for f in faces: + subexts.append(f) if obj.Base: Path.Log.debug("base items exist. Processing... ") + Suppress = False for base in obj.Base: Path.Log.debug("obj.Base item: {}".format(base)) @@ -183,13 +227,14 @@ def areaOpShapes(self, obj): and obj.HandleMultipleFeatures == "Collectively" ): (fzmin, fzmax) = self.getMinMaxOfFaces(Faces) - if obj.FinalDepth.Value < fzmin: + if obj.FinalDepth.Value < fzmin and Suppress is False: Path.Log.warning( translate( "CAM", "Final depth set below ZMin of face(s) selected.", ) ) + Suppress = True if ( obj.AdaptivePocketStart is True @@ -201,8 +246,37 @@ def areaOpShapes(self, obj): removalshapes.append(pocketTup) # (shape, isHole, detail) else: shape = Part.makeCompound(Faces) + if len(sub) > 1 and (exts or subexts): + Path.Log.warning( + translate( + "CAM", + "Independent Extensions : More than one Base Geometry Features is selected! Consider Handle Multiple Features Individually.", + ) + ) + szmin = shape.BoundBox.ZMin + if exts: + for e in exts: + extsshape = Part.makeCompound(e) + ezmin = e.BoundBox.ZMin + if ezmin == szmin: + shape = shape.fuse(e) + depthpar = PathUtils.depth_params( + clearance_height=self.depthparams.clearance_height, + safe_height=self.depthparams.safe_height, + start_depth=self.depthparams.start_depth, + step_down=self.depthparams.step_down, + z_finish_step=0, + final_depth=ezmin, + ) + else: + depthpar = self.depthparams + + if subexts: + subshape = Part.makeCompound(subexts) + shape = shape.cut(subshape) + env = PathUtils.getEnvelope( - base[0].Shape, subshape=shape, depthparams=self.depthparams + base[0].Shape, subshape=shape, depthparams=depthpar ) rawRemovalShape = env.cut(base[0].Shape) faceExtrusions = [ @@ -218,12 +292,33 @@ def areaOpShapes(self, obj): for sub in base[1]: if "Face" in sub: shape = Part.makeCompound([getattr(base[0].Shape, sub)]) + szmin = shape.BoundBox.ZMin + if exts: + for e in exts: + extsshape = Part.makeCompound(e) + ezmin = e.BoundBox.ZMin + if ezmin == szmin: + shape = shape.fuse(e) + depthpar = PathUtils.depth_params( + clearance_height=self.depthparams.clearance_height, + safe_height=self.depthparams.safe_height, + start_depth=self.depthparams.start_depth, + step_down=self.depthparams.step_down, + z_finish_step=0, + final_depth=ezmin, + ) + else: + depthpar = self.depthparams else: edges = [getattr(base[0].Shape, sub) for sub in base[1]] shape = Part.makeFace(edges, "Part::FaceMakerSimple") + + if subexts: + subshape = Part.makeCompound(subexts) + shape = shape.cut(subshape) env = PathUtils.getEnvelope( - base[0].Shape, subshape=shape, depthparams=self.depthparams + base[0].Shape, subshape=shape, depthparams=depthpar ) rawRemovalShape = env.cut(base[0].Shape) faceExtrusions = [shape.extrude(FreeCAD.Vector(0.0, 0.0, 1.0))] @@ -275,12 +370,14 @@ def areaOpShapes(self, obj): def areaOpSetDefaultValues(self, obj, job): """areaOpSetDefaultValues(obj, job) ... set default values""" - obj.StepOver = 100 + obj.StepOver = 85 + obj.PocketLastStepOver = 3 obj.ZigZagAngle = 45 obj.HandleMultipleFeatures = "Collectively" obj.AdaptivePocketStart = False obj.AdaptivePocketFinish = False obj.ProcessStockArea = False + FeatureExtensions.set_default_property_values(obj, job) # methods for eliminating air milling with some pockets: adaptive start and finish def calculateAdaptivePocket(self, obj, base, subObjTups): @@ -311,16 +408,22 @@ def calculateAdaptivePocket(self, obj, base, subObjTups): # find connected edges and map to edge names of base (connectedEdges, touching) = self.findSharedEdges(subObjTups) (low, high) = self.identifyUnconnectedEdges(subObjTups, touching) + + allEdges = [] + for (sub, face, ei) in high: + allEdges.append(face.Edges[ei]) + + (hzmin, hzmax) = self.getMinMaxOfFaces(allEdges) if len(high) > 0 and obj.AdaptivePocketStart is True: # attempt planar face with top edges of pocket - allEdges = [] + #allEdges = [] makeHighFace = 0 tryNonPlanar = False - for (sub, face, ei) in high: - allEdges.append(face.Edges[ei]) + #for (sub, face, ei) in high: + # allEdges.append(face.Edges[ei]) - (hzmin, hzmax) = self.getMinMaxOfFaces(allEdges) + #(hzmin, hzmax) = self.getMinMaxOfFaces(allEdges) try: highFaceShape = Part.Face(Part.Wire(Part.__sortEdges__(allEdges))) @@ -499,7 +602,7 @@ def calculateAdaptivePocket(self, obj, base, subObjTups): for rn in removeList: FreeCAD.ActiveDocument.getObject(rn).purgeTouched() - self.tempObjectNames.append(rn) + #self.tempObjectNames.append(rn) return pocket def orderFacesAroundCenterOfMass(self, subObjTups): @@ -917,7 +1020,7 @@ def _extrudeBaseDown(base): def SetupProperties(): - return PathPocketBase.SetupProperties() + ["HandleMultipleFeatures"] + return PathPocketBase.SetupProperties() + ["HandleMultipleFeatures"] + FeatureExtensions.SetupProperties() def Create(name, obj=None, parentJob=None): diff --git a/src/Mod/CAM/Path/Op/PocketBase.py b/src/Mod/CAM/Path/Op/PocketBase.py index 681cdcd93652..d4ba5d472ceb 100644 --- a/src/Mod/CAM/Path/Op/PocketBase.py +++ b/src/Mod/CAM/Path/Op/PocketBase.py @@ -104,13 +104,8 @@ def initPocketOp(self, obj): 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 + obj.PocketLastStepOver = 3 def pocketInvertExtraOffset(self): """pocketInvertExtraOffset() ... return True if ExtraOffset's direction is inward. @@ -190,6 +185,15 @@ def initAreaOp(self, obj): "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", + "NoPathReverse", + "Pocket", + QT_TRANSLATE_NOOP( + "App::Property", + "Prevents Path Reverse on each Step Down if Start Point is used.", + ), + ) obj.addProperty( "App::PropertyBool", "UseRestMachining", @@ -199,6 +203,15 @@ def initAreaOp(self, obj): "Skips machining regions that have already been cleared by previous operations.", ), ) + obj.addProperty( + "App::PropertyBool", + "UseFinishingOffset", + "Pocket", + QT_TRANSLATE_NOOP( + "App::Property", + "Finishing Pass only for Offset Pattern.", + ), + ) for n in self.pocketPropertyEnumerations(): setattr(obj, n[0], n[1]) @@ -230,6 +243,8 @@ def areaOpAreaParams(self, obj, isHole): params["PocketExtraOffset"] = extraOffset params["ToolRadius"] = self.radius params["PocketLastStepover"] = obj.PocketLastStepOver + finishingOffset = obj.UseFinishingOffset + params["UseFinishingOffset"] = finishingOffset Pattern = { "ZigZag": 1, @@ -259,7 +274,7 @@ def opOnDocumentRestored(self, obj): "Last Stepover Radius. If 0, 50% of cutter is used. Tuning this can be used to improve stepover for some shapes", ), ) - obj.PocketLastStepOver = 0 + obj.PocketLastStepOver = 3 if not hasattr(obj, "UseRestMachining"): obj.addProperty( @@ -271,6 +286,27 @@ def opOnDocumentRestored(self, obj): "Skips machining regions that have already been cleared by previous operations.", ), ) + if not hasattr(obj, "NoPathReverse"): + obj.addProperty( + "App::PropertyBool", + "NoPathReverse", + "Pocket", + QT_TRANSLATE_NOOP( + "App::Property", + "Prevents Path Reverse on each Step Down if Start Point is used.", + ), + ) + + if not hasattr(obj, "UseFinishingOffset"): + obj.addProperty( + "App::PropertyBool", + "UseFinishingOffset", + "Pocket", + QT_TRANSLATE_NOOP( + "App::Property", + "Finishing Pass only for Offset Pattern.", + ), + ) if hasattr(obj, "RestMachiningRegions"): obj.removeProperty("RestMachiningRegions") @@ -311,4 +347,5 @@ def SetupProperties(): setup.append("StartAt") setup.append("MinTravel") setup.append("KeepToolDown") + setup.append("UseFinishingOffset") return setup diff --git a/src/Mod/CAM/Path/Op/PocketShape.py b/src/Mod/CAM/Path/Op/PocketShape.py index d95d521c3761..3449e47d4ef3 100644 --- a/src/Mod/CAM/Path/Op/PocketShape.py +++ b/src/Mod/CAM/Path/Op/PocketShape.py @@ -81,9 +81,10 @@ def pocketInvertExtraOffset(self): def areaOpSetDefaultValues(self, obj, job): """areaOpSetDefaultValues(obj, job) ... set default values""" - obj.StepOver = 100 + obj.StepOver = 85 obj.ZigZagAngle = 45 obj.UseOutline = False + obj.PocketLastStepOver = 3 FeatureExtensions.set_default_property_values(obj, job) def areaOpShapes(self, obj): @@ -97,7 +98,20 @@ def areaOpShapes(self, obj): # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) + subtractExt = [] + includeExt = [] + for e in extensions: + if e.IndependentExtensionsList: + eNms = e._getEdgeNamesOp() + for ind in e.IndependentExtensionsList: + if eNms[0] in ind: + if float(ind.split(eNms[0]+":",1)[1]) < 0: + subtractExt.append(e) + else: + includeExt.append(e) + else: + includeExt = extensions if e.avoid: avoidFeatures.append(e.feature) @@ -137,19 +151,33 @@ def areaOpShapes(self, obj): self.horiz.append(face) # Add faces for extensions - self.exts = [] - for ext in extensions: - if not ext.avoid: - wire = ext.getWire() + self.exts = [] + if includeExt: + for ext in includeExt: + if not ext.avoid: + wire = ext.getWire() + if wire: + faces = ext.getExtensionFaces(wire) + for f in faces: + self.horiz.append(f) + self.exts.append(f) + + self.subexts = [] + if subtractExt: + for sub in subtractExt: + wire = sub.getWire() if wire: - faces = ext.getExtensionFaces(wire) + faces = sub.getExtensionFaces(wire) for f in faces: - self.horiz.append(f) - self.exts.append(f) + self.subexts.append(f) - # check all faces and see if they are touching/overlapping and combine and simplify + # check all faces and see if they are touching/overlapping and combine and simplify self.horizontal = Path.Geom.combineHorizontalFaces(self.horiz) + if self.subexts: + self.cut = Path.Geom.combineHorizontalFaces(self.subexts) + self.horizontal[0] = self.horizontal[0].cut(self.cut) + # Move all faces to final depth less buffer before extrusion # Small negative buffer is applied to compensate for internal significant digits/rounding issue if self.job.GeometryTolerance.Value == 0.0: diff --git a/src/Mod/CAM/Path/Op/Profile.py b/src/Mod/CAM/Path/Op/Profile.py index 5b7c0fc86c02..bc0e032c3380 100644 --- a/src/Mod/CAM/Path/Op/Profile.py +++ b/src/Mod/CAM/Path/Op/Profile.py @@ -176,6 +176,40 @@ def areaOpProperties(self): "App::Property", "Make True, if using Cutter Radius Compensation" ), ), + ( + "App::PropertyBool", + "multiProfile", + "Profile", + QT_TRANSLATE_NOOP( + "App::Property", "Multiple Profile Operations with Offset" + ), + ), + ( + "App::PropertyDistance", + "multiProfileWidth", + "Profile", + QT_TRANSLATE_NOOP( + "App::Property", + "Width of the Multi Profile Operations", + ), + ), + ( + "App::PropertyPercent", + "multiStepOver", + "Profile", + QT_TRANSLATE_NOOP( + "App::Property", + "Step Over of Multi Profile Operations on each step in % of tool diameter", + ), + ), + ( + "App::PropertyBool", + "cutOutsideIn", + "Profile", + QT_TRANSLATE_NOOP( + "App::Property", "Cut OutsideIn" + ), + ), ] @classmethod @@ -383,6 +417,39 @@ def areaOpShapes(self, obj): "The selected edge(s) are inaccessible. If multiple, re-ordering selection might work.", ) self.offsetExtra = obj.OffsetExtra.Value + + self.multiProfile = True if obj.multiProfile else False + self.multiProfileWidth = float(obj.multiProfileWidth) + self.multiStepOver = obj.multiStepOver + self.multiCutOutsideIn = obj.cutOutsideIn + + self.multiProfileSteps = [] + if self.multiProfile: + + multiStepOver = (self.radius * 2) * (self.multiStepOver / 100) + nbrOfSteps = int(self.multiProfileWidth / multiStepOver) + + checkD = nbrOfSteps * multiStepOver + decLeft = self.multiProfileWidth - checkD + + Inc = 0 + incStepOver = multiStepOver + if decLeft > 0: + while Inc < nbrOfSteps: + self.multiProfileSteps.append(incStepOver + decLeft) + incStepOver = incStepOver + multiStepOver + Inc = Inc + 1 + self.multiProfileSteps.insert(0, decLeft) + else: + while Inc < nbrOfSteps: + self.multiProfileSteps.append(incStepOver) + incStepOver = incStepOver + multiStepOver + Inc = Inc + 1 + + self.multiProfileSteps.insert(0, 0) + + else: + self.multiProfileSteps.append(0) if self.isDebug: for grpNm in ["tmpDebugGrp", "tmpDebugGrp001"]: @@ -474,6 +541,7 @@ def areaOpShapes(self, obj): shapes.append(tup) if faces and obj.processPerimeter: + cont = False if obj.HandleMultipleFeatures == "Collectively": custDepthparams = self.depthparams cont = True @@ -604,42 +672,52 @@ def _processEdges(self, obj, remainingObjBaseFeatures): else: flattened = self._flattenWire(obj, wire, obj.FinalDepth.Value) zDiff = math.fabs(wire.BoundBox.ZMin - obj.FinalDepth.Value) - if flattened and zDiff >= self.JOB.GeometryTolerance.Value: - cutWireObjs = False - openEdges = [] - passOffsets = [self.ofstRadius] - (origWire, flatWire) = flattened - - self._addDebugObject("FlatWire", flatWire) - - for po in passOffsets: - self.ofstRadius = po - cutShp = self._getCutAreaCrossSection( - obj, base, origWire, flatWire - ) - if cutShp: - cutWireObjs = self._extractPathWire( - obj, base, flatWire, cutShp - ) - - if cutWireObjs: - for cW in cutWireObjs: - openEdges.append(cW) + if flattened and zDiff >= self.JOB.GeometryTolerance.Value: + suppressErrMs = False + storeRadius = self.ofstRadius + for st in self.multiProfileSteps: + self.ofstRadius = self.ofstRadius + st + if flattened: + cutWireObjs = False + openEdges = [] + passOffsets = [self.ofstRadius] + (origWire, flatWire) = flattened + + self._addDebugObject("FlatWire", flatWire) + + for po in passOffsets: + self.ofstRadius = po + cutShp = self._getCutAreaCrossSection( + obj, base, origWire, flatWire + ) + if cutShp: + cutWireObjs = self._extractPathWire( + obj, base, flatWire, cutShp + ) + if cutWireObjs: + for cW in cutWireObjs: + openEdges.append(cW) + else: + Path.Log.error(self.inaccessibleMsg) + + if openEdges: + tup = openEdges, False, "OpenEdge" + shapes.append(tup) + else: - Path.Log.error(self.inaccessibleMsg) - - if openEdges: - tup = openEdges, False, "OpenEdge" - shapes.append(tup) - else: - if zDiff < self.JOB.GeometryTolerance.Value: - msg = translate( - "PathProfile", - "Check edge selection and Final Depth requirements for profiling open edge(s).", - ) - Path.Log.error(msg) - else: - Path.Log.error(self.inaccessibleMsg) + if zDiff < self.JOB.GeometryTolerance.Value and not suppressErrMs: + if not suppressErrMs: + msg = translate( + "PathProfile", + "Check edge selection and Final Depth requirements for profiling open edge(s).", + ) + Path.Log.error(msg) + if self.multiProfileSteps: + suppressErrMs = True + else: + Path.Log.error(self.inaccessibleMsg) + + self.ofstRadius = storeRadius return shapes @@ -779,7 +857,7 @@ def _getCutAreaCrossSection(self, obj, base, origWire, flatWire): ) # Translate face to final depth # Make common of the two - comFC = topComp.common(botComp) + comFC = topComp.common(botComp, 1e-4) # Determine with which set of intersection tags the model intersects (cmnIntArea, cmnExtArea) = self._checkTagIntersection(iTAG, eTAG, "QRY", comFC) diff --git a/src/Mod/CAM/Path/Op/Surface.py b/src/Mod/CAM/Path/Op/Surface.py index d61144de5417..08e467439031 100644 --- a/src/Mod/CAM/Path/Op/Surface.py +++ b/src/Mod/CAM/Path/Op/Surface.py @@ -58,6 +58,9 @@ from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader("Part", globals(), "Part") +FeatureExtensions = LazyLoader( + "Path.Op.FeatureExtension", globals(), "Path.Op.FeatureExtension" +) if FreeCAD.GuiUp: import FreeCADGui @@ -82,6 +85,7 @@ def opFeatures(self, obj): | PathOp.FeatureStepDown | PathOp.FeatureCoolant | PathOp.FeatureBaseFaces + | PathOp.FeatureLocations ) def initOperation(self, obj): @@ -97,6 +101,8 @@ def initOperation(self, obj): if not hasattr(obj, "DoNotSetDefaultValues"): self.setEditorProperties(obj) + + FeatureExtensions.initialize_properties(obj) def initOpProperties(self, obj, warn=False): """initOpProperties(obj) ... create operation specific properties""" @@ -386,6 +392,14 @@ def opPropertyDefinitions(self): "Enable separate optimization of transitions between, and breaks within, each step over path.", ), ), + ( + "App::PropertyBool", + "OptimizeLeadInOut", + "Optimization", + QT_TRANSLATE_NOOP( + "App::Property", "Optimize for LeadInOut Dressup." + ), + ), ( "App::PropertyBool", "CircularUseG2G3", @@ -528,7 +542,7 @@ def opPropertyDefaults(self, obj, job): "HandleMultipleFeatures": "Collectively", "PatternCenterAt": "CenterOfMass", "GapSizes": "No gaps identified.", - "StepOver": 100.0, + "StepOver": 85.0, "CutPatternAngle": 0.0, "CutterTilt": 0.0, "StartIndex": 0.0, @@ -541,7 +555,7 @@ def opPropertyDefaults(self, obj, job): "GapThreshold": 0.005, "AngularDeflection": 0.25, # AngularDeflection is unused # Reasonable compromise between speed & precision - "LinearDeflection": 0.001, + "LinearDeflection": 0.01, # For debugging "ShowTempObjects": False, } @@ -554,7 +568,7 @@ def opPropertyDefaults(self, obj, job): # avoid false collisions with the model mesh, so make sure we # default to tessellating with greater precision than the target # GeometryTolerance. - defaults["LinearDeflection"] = job.GeometryTolerance.Value / 4 + defaults["LinearDeflection"] = job.GeometryTolerance.Value / 2 if warn: msg = translate("PathSurface", "The GeometryTolerance for this Job is 0.0.") msg += translate( @@ -617,6 +631,7 @@ def opOnDocumentRestored(self, obj): setattr(obj, prop, val) self.setEditorProperties(obj) + FeatureExtensions.initialize_properties(obj) def opApplyPropertyDefaults(self, obj, job, propList): # Set standard property defaults @@ -658,6 +673,8 @@ def opSetDefaultValues(self, obj, job): obj.OpFinalDepth.Value = -10 obj.OpStartDepth.Value = 10 + FeatureExtensions.set_default_property_values(obj, job) + Path.Log.debug("Default OpFinalDepth: {}".format(obj.OpFinalDepth.Value)) Path.Log.debug("Default OpStartDepth: {}".format(obj.OpStartDepth.Value)) @@ -917,9 +934,48 @@ def opExecute(self, obj): self.boundBoxes.append(JOB.Stock.Shape.BoundBox) # ###### MAIN COMMANDS FOR OPERATION ###### + + # Get extensions and identify faces to avoid Jim add Extensions + avoidFeatures = list() + extensions = FeatureExtensions.getExtensions(obj) + subtractExt = [] + includeExt = [] + for e in extensions: + if e.IndependentExtensionsList: + eNms = e._getEdgeNamesOp() + for ind in e.IndependentExtensionsList: + if eNms[0] in ind: + if float(ind.split(eNms[0]+":",1)[1]) < 0: + subtractExt.append(e) + else: + includeExt.append(e) + else: + includeExt = extensions + + if e.avoid: + avoidFeatures.append(e.feature) + + exts = [] + if includeExt: + for ext in includeExt: + if not ext.avoid: + wire = ext.getWire() + if wire: + faces = ext.getExtensionFaces(wire) + for f in faces: + exts.append(f) + + subexts = [] + if subtractExt: + for sub in subtractExt: + wire = sub.getWire() + if wire: + faces = sub.getExtensionFaces(wire) + for f in faces: + subexts.append(f) # Begin processing obj.Base data and creating GCode - PSF = PathSurfaceSupport.ProcessSelectedFaces(JOB, obj) + PSF = PathSurfaceSupport.ProcessSelectedFaces(JOB, obj, exts, subexts) PSF.setShowDebugObjects(tempGroup, self.showDebugObjects) PSF.radius = self.radius PSF.depthParams = self.depthParams @@ -1516,6 +1572,7 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): Path.Log.debug("Multi-pass lyrDep: {}".format(round(lyrDep, 4))) # Cycle through step-over sections (line segments or arcs) + passBreak = False for so in range(0, len(SCANDATA)): SO = SCANDATA[so] lenSO = len(SO) @@ -1523,8 +1580,10 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): # Pre-process step-over parts for layer depth and holds ADJPRTS = [] LMAX = [] + AddBrk = [] soHasPnts = False brkFlg = False + cAD = -1 for i in range(0, lenSO): prt = SO[i] lenPrt = len(prt) @@ -1533,8 +1592,9 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): ADJPRTS.append(prt) LMAX.append(prt) brkFlg = False + cAD = cAD + 1 else: - (PTS, lMax) = self._planarMultipassPreProcess( + (PTS, lMax, Brk) = self._planarMultipassPreProcess( obj, prt, prevDepth, lyrDep ) if len(PTS) > 0: @@ -1542,6 +1602,36 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): soHasPnts = True brkFlg = True LMAX.append(lMax) + cAD = cAD + 1 + if Brk is True: + AddBrk.append(cAD) + if len(AddBrk): + AddBrk.sort(reverse=True) + for ti in AddBrk: + NewADJP = [] + CntIn = 1 + KRBin = False + for ri in ADJPRTS[ti]: + if ri == 'KRB': + if CntIn > 1: + CntIn = CntIn + 1 + ADJPRTS.insert((ti+CntIn), ("KRB")) + ADJPRTS.insert((ti+CntIn), NewADJP) + NewADJP = [] + CntIn = CntIn + 1 + KRBin = True + else: + NewADJP.append(ri) + if obj.CutPattern == "Spiral" and KRBin is False: + ADJPRTS.insert((ti+1), ("KRB")) + NewADJP = [] + if len(NewADJP) > 0: + if KRBin is False: + CntIn = 0 + ADJPRTS.insert((ti+(CntIn+1)), NewADJP) + ADJPRTS.pop(ti) + NewADJP = [] + # Efor lenAdjPrts = len(ADJPRTS) @@ -1580,10 +1670,14 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): for i in range(0, lenAdjPrts): prt = ADJPRTS[i] lenPrt = len(prt) - if prt == "BRK" and prtsHasCmds: + if (prt == "BRK" or prt == "KRB") and prtsHasCmds: if i + 1 < lenAdjPrts: nxtStart = ADJPRTS[i + 1][0] - prtsCmds.append(Path.Command("N (--Break)", {})) + if prt == "BRK": + prtsCmds.append(Path.Command("N (--Break)", {})) + else: + prtsCmds.append(Path.Command("N (--PassBreak)", {})) + passBreak = True else: # Transition straight up to Safe Height if no more parts nxtStart = FreeCAD.Vector( @@ -1596,6 +1690,15 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): ) else: segCmds = False + if passBreak is False: + prtsCmds.append( + Path.Command("N (--Start)", {}) # Jim Surface LeadInOut fix + ) + else: + prtsCmds.append( + Path.Command("N (--passStart)", {}) # Jim Surface LeadInOut fix + ) + passBreak = False prtsCmds.append( Path.Command("N (part {})".format(i + 1), {}) ) @@ -1683,19 +1786,65 @@ def _planarDropCutMulti(self, JOB, obj, pdc, safePDC, depthparams, SCANDATA): def _planarMultipassPreProcess(self, obj, LN, prvDep, layDep): ALL = [] PTS = [] + Brk = False # Jim + BrkFl = False optLinTrans = obj.OptimizeStepOverTransitions - safe = math.ceil(obj.SafeHeight.Value) + testlayDep = obj.OpStartDepth.Value - obj.StepDown.Value # Jim + SAFE = False - if optLinTrans is True: + if optLinTrans is True and layDep != testlayDep: for P in LN: ALL.append(P) # Handle layer depth AND hold points if P.z <= layDep: + ALL.append(P) PTS.append(FreeCAD.Vector(P.x, P.y, layDep)) + BrkFl = False elif P.z > prvDep: - PTS.append(FreeCAD.Vector(P.x, P.y, safe)) + if BrkFl is False: + PTS.append('KRB') + BrkFl = True + SAFE = True else: + ALL.append(P) PTS.append(FreeCAD.Vector(P.x, P.y, P.z)) + BrkFl = False + + if SAFE is True: + if obj.CutPattern in "Offset": + Cnt = 0 + for i in range(0, len(PTS)): + if PTS[i] == 'KRB': + PTS.pop(i-Cnt) + Cnt = Cnt + 1 + else: + Brk = True + break + for i in range(len(PTS)-1, 0, -1): + if PTS[i] == 'KRB': + PTS.pop(i) + else: + Brk = True + break + + if obj.CutPattern in ["Line", "ZigZag", "CircularZigZag", "Circular"]: + if PTS[0] == 'KRB': + PTS.pop(0) + for i in range(len(PTS)-1, 0, -1): + if PTS[1] == 'KRB': + PTS.pop(i) + else: + Brk = True + break + + if obj.CutPattern == "Spiral": + if PTS[0] == 'KRB': + PTS.pop(0) + if len(PTS) > 1: + if PTS[-1] == 'KRB': + del PTS[-1] + Brk = True + # Efor else: for P in LN: @@ -1707,29 +1856,6 @@ def _planarMultipassPreProcess(self, obj, LN, prvDep, layDep): PTS.append(FreeCAD.Vector(P.x, P.y, P.z)) # Efor - if optLinTrans is True: - # Remove leading and trailing Hold Points - popList = [] - for i in range(0, len(PTS)): # identify leading string - if PTS[i].z == safe: - popList.append(i) - else: - break - popList.sort(reverse=True) - for p in popList: # Remove hold points - PTS.pop(p) - ALL.pop(p) - popList = [] - for i in range(len(PTS) - 1, -1, -1): # identify trailing string - if PTS[i].z == safe: - popList.append(i) - else: - break - popList.sort(reverse=True) - for p in popList: # Remove hold points - PTS.pop(p) - ALL.pop(p) - # Determine max Z height for remaining points on line lMax = obj.FinalDepth.Value if len(ALL) > 0: @@ -1738,7 +1864,7 @@ def _planarMultipassPreProcess(self, obj, LN, prvDep, layDep): if P.z > lMax: lMax = P.z - return (PTS, lMax) + return (PTS, lMax, Brk) def _planarMultipassProcess(self, obj, PNTS, lMax): output = [] @@ -1827,6 +1953,7 @@ def _stepTransitionCmds(self, obj, p1, p2, safePDC, tolrnc): cmds = [] rtpd = False height = obj.SafeHeight.Value + testlayDep = obj.OpStartDepth.Value - obj.StepDown.Value # Allow cutter-down transitions with a distance up to 2x cutter # diameter. We might be able to extend this further to the # full-retract-and-rapid break even point in the future, but this will @@ -1834,12 +1961,12 @@ def _stepTransitionCmds(self, obj, p1, p2, safePDC, tolrnc): # to avoid inadvertent cutting. maxXYDistanceSqrd = (self.cutter.getDiameter() * 2) ** 2 - if obj.OptimizeStepOverTransitions: + if obj.OptimizeStepOverTransitions and p2.z != testlayDep: if p1 and p2: # Short distance within step over xyDistanceSqrd = (p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2 # Try to keep cutting for short distances. - if xyDistanceSqrd <= maxXYDistanceSqrd: + if (xyDistanceSqrd <= maxXYDistanceSqrd) and not obj.OptimizeLeadInOut: # Try to keep cutting, following the model shape (transLine, minZ, maxZ) = self._getTransitionLine( safePDC, p1, p2, obj @@ -1887,7 +2014,7 @@ def _stepTransitionCmds(self, obj, p1, p2, safePDC, tolrnc): cmds.append( Path.Command("G0", {"X": p2.x, "Y": p2.y, "F": self.horizRapid}) ) - if rtpd is not False: # ReturnToPreviousDepth + if rtpd is not False and not obj.OptimizeLeadInOut: # ReturnToPreviousDepth cmds.append(Path.Command("G0", {"Z": rtpd, "F": self.vertRapid})) return cmds diff --git a/src/Mod/CAM/Path/Op/SurfaceSupport.py b/src/Mod/CAM/Path/Op/SurfaceSupport.py index 581d09e2d7a0..76c2ae40fd0c 100644 --- a/src/Mod/CAM/Path/Op/SurfaceSupport.py +++ b/src/Mod/CAM/Path/Op/SurfaceSupport.py @@ -491,7 +491,7 @@ class ProcessSelectedFaces: Calling the preProcessModel(module) method returns two compound objects as a tuple: (FACES, VOIDS) or False.""" - def __init__(self, JOB, obj): + def __init__(self, JOB, obj, exts, subexts): self.modelSTLs = [] self.profileShapes = [] self.tempGroup = False @@ -513,6 +513,8 @@ def __init__(self, JOB, obj): self.JOB = JOB self.obj = obj self.profileEdges = "None" + self.exts = exts + self.subexts = subexts if hasattr(obj, "ProfileEdges"): self.profileEdges = obj.ProfileEdges @@ -708,6 +710,18 @@ def _identifyFacesAndVoids(self, F, V): V[m].append((shape, faceIdx)) Path.Log.debug(".. Avoiding {}".format(sub)) hasVoid = True + if hasFace is True: + fIdx = 100 + for ext in self.exts: + F[m].append((ext, fIdx)) + fIdx += 1 + if hasFace is True and self.subexts: + fIdx = 200 + if V[m] is False: + V[m] = [] + for subext in self.subexts: + V[m].append((subext, fIdx)) + fIdx += 1 return (hasFace, hasVoid) def _preProcessFacesAndVoids(self, base, FCS, VDS): @@ -2503,6 +2517,12 @@ def __init__(self, ocl, obj, safe=False): if hasattr(self.tool, "ShapeName"): self.toolType = self.tool.ShapeName # Indicates ToolBit tool self.toolMode = "ToolBit" + if "endmill_flat" in self.toolType: + self.toolType = "endmill" + if "endmill_ball" in self.toolType: + self.toolType = "ballend" + if "endmill_rad" in self.toolType: + self.toolType = "bullnose" if self.toolType: Path.Log.debug( "OCL_Tool tool mode, type: {}, {}".format(self.toolMode, self.toolType) diff --git a/src/Mod/CAM/Path/Op/Waterline.py b/src/Mod/CAM/Path/Op/Waterline.py index ff779367e434..ca88cd176f13 100644 --- a/src/Mod/CAM/Path/Op/Waterline.py +++ b/src/Mod/CAM/Path/Op/Waterline.py @@ -921,7 +921,9 @@ def opExecute(self, obj): # ###### MAIN COMMANDS FOR OPERATION ###### # Begin processing obj.Base data and creating GCode - PSF = PathSurfaceSupport.ProcessSelectedFaces(JOB, obj) + exts = [] + subexts = [] + PSF = PathSurfaceSupport.ProcessSelectedFaces(JOB, obj, exts, subexts) PSF.setShowDebugObjects(tempGroup, self.showDebugObjects) PSF.radius = self.radius PSF.depthParams = self.depthParams diff --git a/src/Mod/CAM/libarea/Area.h b/src/Mod/CAM/libarea/Area.h index 9010806d2fe8..c2611562aecc 100644 --- a/src/Mod/CAM/libarea/Area.h +++ b/src/Mod/CAM/libarea/Area.h @@ -23,15 +23,17 @@ struct CAreaPocketParams double extra_offset; double stepover; bool from_center; - PocketMode mode; + bool finishing_offset; + PocketMode mode; double zig_angle; bool only_cut_first_offset; - CAreaPocketParams(double Tool_radius, double Extra_offset, double Stepover, bool From_center, PocketMode Mode, double Zig_angle) + CAreaPocketParams(double Tool_radius, double Extra_offset, double Stepover, bool From_center, bool Finishing_offset, PocketMode Mode, double Zig_angle) { tool_radius = Tool_radius; extra_offset = Extra_offset; stepover = Stepover; from_center = From_center; + finishing_offset = Finishing_offset; mode = Mode; zig_angle = Zig_angle; only_cut_first_offset = false; diff --git a/src/Mod/CAM/libarea/PythonStuff.cpp b/src/Mod/CAM/libarea/PythonStuff.cpp index 87f54a871272..e05a9f8b03bf 100644 --- a/src/Mod/CAM/libarea/PythonStuff.cpp +++ b/src/Mod/CAM/libarea/PythonStuff.cpp @@ -152,11 +152,11 @@ static boost::python::tuple nearest_point_to_curve(CCurve& c1, const CCurve& c2) return bp::make_tuple(p, dist); } -boost::python::list MakePocketToolpath(const CArea& a, double tool_radius, double extra_offset, double stepover, bool from_center, bool use_zig_zag, double zig_angle) +boost::python::list MakePocketToolpath(const CArea& a, double tool_radius, double extra_offset, double stepover, bool from_center, bool finishing_offset, bool use_zig_zag, double zig_angle) { std::list toolpath; - CAreaPocketParams params(tool_radius, extra_offset, stepover, from_center, use_zig_zag ? ZigZagPocketMode : SpiralPocketMode, zig_angle); + CAreaPocketParams params(tool_radius, extra_offset, stepover, from_center, finishing_offset, use_zig_zag ? ZigZagPocketMode : SpiralPocketMode, zig_angle); a.SplitAndMakePocketToolpath(toolpath, params); boost::python::list clist; diff --git a/src/Mod/CAM/libarea/pyarea.cpp b/src/Mod/CAM/libarea/pyarea.cpp index 1527c8c5957b..b0f659a582d2 100644 --- a/src/Mod/CAM/libarea/pyarea.cpp +++ b/src/Mod/CAM/libarea/pyarea.cpp @@ -121,11 +121,11 @@ static py::tuple nearest_point_to_curve(CCurve& c1, const CCurve& c2) return py::make_tuple(p, dist); } -std::list MakePocketToolpath(const CArea& a, double tool_radius, double extra_offset, double stepover, bool from_center, bool use_zig_zag, double zig_angle) +std::list MakePocketToolpath(const CArea& a, double tool_radius, double extra_offset, double stepover, bool from_center, bool finishing_offset, bool use_zig_zag, double zig_angle) { std::list toolpath; - CAreaPocketParams params(tool_radius, extra_offset, stepover, from_center, use_zig_zag ? ZigZagPocketMode : SpiralPocketMode, zig_angle); + CAreaPocketParams params(tool_radius, extra_offset, stepover, from_center, finishing_offset, use_zig_zag ? ZigZagPocketMode : SpiralPocketMode, zig_angle); a.SplitAndMakePocketToolpath(toolpath, params); return toolpath;