diff --git a/src/Mod/Path/PathScripts/PathMillFace.py b/src/Mod/Path/PathScripts/PathMillFace.py index 073e104621..1204ebbe06 100644 --- a/src/Mod/Path/PathScripts/PathMillFace.py +++ b/src/Mod/Path/PathScripts/PathMillFace.py @@ -189,7 +189,9 @@ class ObjectFace(PathPocketBase.ObjectPocket): elif obj.BoundaryShape == 'Perimeter': if obj.ClearEdges: psZMin = planeshape.BoundBox.ZMin - ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.radius * 1.25, planeshape) + ofstShape = PathUtils.getOffsetArea(planeshape, + self.radius * 1.25, + plane=planeshape) ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin)) env = PathUtils.getEnvelope(partshape=ofstShape, depthparams=self.depthparams) else: @@ -198,7 +200,9 @@ class ObjectFace(PathPocketBase.ObjectPocket): import PathScripts.PathSurfaceSupport as PathSurfaceSupport baseShape = oneBase[0].Shape psZMin = planeshape.BoundBox.ZMin - ofstShape = PathSurfaceSupport.extractFaceOffset(planeshape, self.tool.Diameter * 1.1, planeshape) + ofstShape = PathUtils.getOffsetArea(planeshape, + self.tool.Diameter * 1.1, + plane=planeshape) ofstShape.translate(FreeCAD.Vector(0.0, 0.0, psZMin - ofstShape.BoundBox.ZMin)) custDepthparams = self._customDepthParams(obj, obj.StartDepth.Value + 0.1, obj.FinalDepth.Value - 0.1) # only an envelope diff --git a/src/Mod/Path/PathScripts/PathProfile.py b/src/Mod/Path/PathScripts/PathProfile.py index 31a79e9195..595a3fb0a1 100644 --- a/src/Mod/Path/PathScripts/PathProfile.py +++ b/src/Mod/Path/PathScripts/PathProfile.py @@ -1069,7 +1069,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): cent1 = FreeCAD.Vector(lstVrt.X, lstVrt.Y, fdv) # Calculate offset shape, containing cut region - ofstShp = self._extractFaceOffset(obj, cutShp, False) + ofstShp = self._getOffsetArea(obj, cutShp, False) # CHECK for ZERO area of offset shape try: @@ -1174,40 +1174,22 @@ class ObjectProfile(PathAreaOp.ObjectOp): return rtnWIRES - def _extractFaceOffset(self, obj, fcShape, isHole): - '''_extractFaceOffset(obj, fcShape, isHole) ... internal function. - Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. - Adjustments made based on notes by @sliptonic - https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' - PathLog.debug('_extractFaceOffset()') + def _getOffsetArea(self, obj, fcShape, isHole): + '''Get an offset area for a shape. Wrapper around + PathUtils.getOffsetArea.''' + PathLog.debug('_getOffsetArea()') - areaParams = {} - # JOB = PathUtils.findParentJob(obj) - # tolrnc = JOB.GeometryTolerance.Value - # if self.useComp: - # offset = self.ofstRadius # + tolrnc - # else: - # offset = self.offsetExtra # + tolrnc + JOB = PathUtils.findParentJob(obj) + tolerance = JOB.GeometryTolerance.Value offset = self.ofstRadius if isHole is False: offset = 0 - offset - areaParams['Offset'] = offset - areaParams['Fill'] = 1 - areaParams['Coplanar'] = 0 - areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections - areaParams['Reorient'] = True - areaParams['OpenMode'] = 0 - areaParams['MaxArcPoints'] = 400 # 400 - areaParams['Project'] = True - # areaParams['JoinType'] = 1 - - area = Path.Area() # Create instance of Area() class object - area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane - area.add(fcShape) # obj.Shape to use for extracting offset - area.setParams(**areaParams) # set parameters - - return area.getShape() + return PathUtils.getOffsetArea(fcShape, + offset, + plane=fcShape, + tolerance=tolerance) def _findNearestVertex(self, shape, point): PathLog.debug('_findNearestVertex()') @@ -1373,7 +1355,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): return (wireIdxs[0], wireIdxs[1]) def _makeCrossSection(self, shape, sliceZ, zHghtTrgt=False): - '''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... + '''_makeCrossSection(shape, sliceZ, zHghtTrgt=None)... Creates cross-section objectc from shape. Translates cross-section to zHghtTrgt if available. Makes face shape from cross-section object. Returns face shape at zHghtTrgt.''' PathLog.debug('_makeCrossSection()') @@ -1502,7 +1484,7 @@ class ObjectProfile(PathAreaOp.ObjectOp): # 2 6 # | | # | ----5----| - # | 4 + # | 4 # -----3-------| # positive dist in _makePerp2DVector() is CCW rotation p1 = E diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index 92e8146b93..2c377b499b 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -383,60 +383,18 @@ class PathGeometryGenerator: def _extractOffsetFaces(self): PathLog.debug('_extractOffsetFaces()') wires = list() - faces = list() - ofst = 0.0 # - self.cutOut shape = self.shape - cont = True - cnt = 0 - while cont: - ofstArea = self._getFaceOffset(shape, ofst) - if not ofstArea: - cont = False - True if cont else False # cont used for LGTM + offset = 0.0 # Start right at the edge of cut area + while True: + offsetArea = PathUtils.getOffsetArea(shape, offset, plane=self.wpc) + if not offsetArea: + # Area fully consumed break - for F in ofstArea.Faces: - faces.append(F) - for w in F.Wires: + for f in offsetArea.Faces: + for w in f.Wires: wires.append(w) - shape = ofstArea - if cnt == 0: - ofst = 0.0 - self.cutOut - cnt += 1 + offset -= self.cutOut return wires - - def _getFaceOffset(self, shape, offset): - '''_getFaceOffset(shape, offset) ... internal function. - Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. - Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' - areaParams = {} - - areaParams['Offset'] = offset - areaParams['Fill'] = 1 # 1 - areaParams['Coplanar'] = 0 - areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections - areaParams['Reorient'] = True - areaParams['OpenMode'] = 0 - areaParams['MaxArcPoints'] = 400 # 400 - areaParams['Project'] = True - - area = Path.Area() # Create instance of Area() class object - area.setPlane(PathUtils.makeWorkplane(self.wpc)) # Set working plane to normal at Z=1 - area.add(shape) - area.setParams(**areaParams) # set parameters - - offsetShape = area.getShape() - wCnt = len(offsetShape.Wires) - if wCnt == 0: - return False - elif wCnt == 1: - ofstFace = Part.Face(offsetShape.Wires[0]) - else: - W = list() - for wr in offsetShape.Wires: - W.append(Part.Face(wr)) - ofstFace = Part.makeCompound(W) - - return ofstFace # Eclass @@ -654,24 +612,17 @@ class ProcessSelectedFaces: isHole = False if self.obj.HandleMultipleFeatures == 'Collectively': cont = True - fsL = list() # face shape list - ifL = list() # avoid shape list - outFCS = list() + PathLog.debug( + 'Attempting to get cross-section of collective faces.') + outFCS, ifL = self.findUnifiedRegions(FCS) + if self.obj.InternalFeaturesCut and ifL: + ifL = list() # clear avoid shape list - # Use new face-unifying class - FUR = FindUnifiedRegions(FCS, self.JOB.GeometryTolerance.Value) - if self.showDebugObjects: - FUR.setTempGroup(self.tempGroup) - outFCS = FUR.getUnifiedRegions() - if not self.obj.InternalFeaturesCut: - gIF = FUR.getInternalFeatures() - if gIF: - ifL.extend(gIF) - - PathLog.debug('Attempting to get cross-section of collective faces.') if len(outFCS) == 0: - msg = translate('PathSurfaceSupport', - 'Cannot process selected faces. Check horizontal surface exposure.') + msg = translate( + 'PathSurfaceSupport', + 'Cannot process selected faces. Check horizontal ' + 'surface exposure.') FreeCAD.Console.PrintError(msg + '\n') cont = False else: @@ -681,7 +632,9 @@ class ProcessSelectedFaces: if cont and self.profileEdges != 'None': PathLog.debug('.. include Profile Edge') ofstVal = self._calculateOffsetValue(isHole) - psOfst = extractFaceOffset(cfsL, ofstVal, self.wpc) + psOfst = PathUtils.getOffsetArea(cfsL, + ofstVal, + plane=self.wpc) if psOfst: mPS = [psOfst] if self.profileEdges == 'Only': @@ -698,7 +651,8 @@ class ProcessSelectedFaces: self.tempGroup.addObject(T) ofstVal = self._calculateOffsetValue(isHole) - faceOfstShp = extractFaceOffset(cfsL, ofstVal, self.wpc) + faceOfstShp = PathUtils.getOffsetArea( + cfsL, ofstVal, plane=self.wpc) if not faceOfstShp: msg = translate('PathSurfaceSupport', 'Failed to create offset face.') + '\n' @@ -721,7 +675,8 @@ class ProcessSelectedFaces: C.purgeTouched() self.tempGroup.addObject(C) ofstVal = self._calculateOffsetValue(isHole=True) - intOfstShp = extractFaceOffset(casL, ofstVal, self.wpc) + intOfstShp = PathUtils.getOffsetArea( + casL, ofstVal, plane=self.wpc) mIFS.append(intOfstShp) mFS = [faceOfstShp] @@ -730,28 +685,22 @@ class ProcessSelectedFaces: elif self.obj.HandleMultipleFeatures == 'Individually': for (fcshp, fcIdx) in FCS: cont = True - ifL = list() # avoid shape list fNum = fcIdx + 1 outerFace = False - # Use new face-unifying class - FUR = FindUnifiedRegions([(fcshp, fcIdx)], self.JOB.GeometryTolerance.Value) - if self.showDebugObjects: - FUR.setTempGroup(self.tempGroup) - gUR = FUR.getUnifiedRegions() + gUR, ifL = self.findUnifiedRegions(FCS) if len(gUR) > 0: outerFace = gUR[0] - if not self.obj.InternalFeaturesCut: - gIF = FUR.getInternalFeatures() - if gIF: - ifL = gIF + if self.obj.InternalFeaturesCut: + ifL = list() # avoid shape list if outerFace: PathLog.debug('Attempting to create offset face of Face{}'.format(fNum)) if self.profileEdges != 'None': ofstVal = self._calculateOffsetValue(isHole) - psOfst = extractFaceOffset(outerFace, ofstVal, self.wpc) + psOfst = PathUtils.getOffsetArea( + outerFace, ofstVal, plane=self.wpc) if psOfst: if mPS is False: mPS = list() @@ -766,7 +715,8 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) - faceOfstShp = extractFaceOffset(outerFace, ofstVal, self.wpc) + faceOfstShp = PathUtils.getOffsetArea( + outerFace, ofstVal, plane=self.wpc) lenIfl = len(ifL) if self.obj.InternalFeaturesCut is False and lenIfl > 0: @@ -776,7 +726,8 @@ class ProcessSelectedFaces: casL = Part.makeCompound(ifL) ofstVal = self._calculateOffsetValue(isHole=True) - intOfstShp = extractFaceOffset(casL, ofstVal, self.wpc) + intOfstShp = PathUtils.getOffsetArea( + casL, ofstVal, plane=self.wpc) mIFS.append(intOfstShp) # faceOfstShp = faceOfstShp.cut(intOfstShp) @@ -801,20 +752,9 @@ class ProcessSelectedFaces: outFCS = list() intFEAT = list() - for (fcshp, fcIdx) in VDS: - fNum = fcIdx + 1 - - # Use new face-unifying class - FUR = FindUnifiedRegions([(fcshp, fcIdx)], self.JOB.GeometryTolerance.Value) - if self.showDebugObjects: - FUR.setTempGroup(self.tempGroup) - gUR = FUR.getUnifiedRegions() - if len(gUR) > 0: - outFCS.extend(gUR) - if not self.obj.InternalFeaturesCut: - gIF = FUR.getInternalFeatures() - if gIF: - intFEAT.extend(gIF) + outFCS, intFEAT = self.findUnifiedRegions(VDS) + if self.obj.InternalFeaturesCut: + intFEAT = list() lenOtFcs = len(outFCS) if lenOtFcs == 0: @@ -838,7 +778,9 @@ class ProcessSelectedFaces: P.purgeTouched() self.tempGroup.addObject(P) ofstVal = self._calculateOffsetValue(isHole, isVoid=True) - avdOfstShp = extractFaceOffset(avoid, ofstVal, self.wpc) + avdOfstShp = PathUtils.getOffsetArea(avoid, + ofstVal, + plane=self.wpc) if avdOfstShp is False: msg = translate('PathSurfaceSupport', 'Failed to create collective offset avoid face.') @@ -854,7 +796,9 @@ class ProcessSelectedFaces: else: ifc = intFEAT[0] ofstVal = self._calculateOffsetValue(isHole=True) - ifOfstShp = extractFaceOffset(ifc, ofstVal, self.wpc) + ifOfstShp = PathUtils.getOffsetArea(ifc, + ofstVal, + plane=self.wpc) if ifOfstShp is False: msg = translate('PathSurfaceSupport', 'Failed to create collective offset avoid internal features.') + '\n' @@ -900,7 +844,9 @@ class ProcessSelectedFaces: if cont and self.profileEdges != 'None': PathLog.debug(' -Attempting profile geometry for model base.') ofstVal = self._calculateOffsetValue(isHole) - psOfst = extractFaceOffset(csFaceShape, ofstVal, self.wpc) + psOfst = PathUtils.getOffsetArea(csFaceShape, + ofstVal, + plane=self.wpc) if psOfst: if self.profileEdges == 'Only': return (True, psOfst) @@ -910,9 +856,10 @@ class ProcessSelectedFaces: if cont: ofstVal = self._calculateOffsetValue(isHole) - faceOffsetShape = extractFaceOffset(csFaceShape, ofstVal, self.wpc) + faceOffsetShape = PathUtils.getOffsetArea(csFaceShape, ofstVal, + plane=self.wpc) if faceOffsetShape is False: - PathLog.debug('extractFaceOffset() failed for entire base.') + PathLog.debug('getOffsetArea() failed for entire base.') else: faceOffsetShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - faceOffsetShape.BoundBox.ZMin)) return (faceOffsetShape, prflShp) @@ -944,6 +891,55 @@ class ProcessSelectedFaces: offset += self.radius + tolrnc return offset + + def findUnifiedRegions( + self, + shapeAndIndexTuples, + useAreaImplementation=True): + """Wrapper around area and wire based region unification + implementations.""" + PathLog.debug('findUnifiedRegions()') + # Allow merging of faces within the LinearDeflection tolerance. + tolerance = self.obj.LinearDeflection.Value + # Default: normal to Z=1 (XY plane), at Z=0 + try: + # Use Area based implementation + shapes = Part.makeCompound([t[0] for t in shapeAndIndexTuples]) + outlineShape = PathUtils.getOffsetArea( + shapes, + # Make the outline very slightly smaller, to avoid creating + # small edges in the cut with the hole-preserving projection. + 0.0 - tolerance / 10, + removeHoles=True, # Outline has holes filled in + tolerance=tolerance, + plane=self.wpc) + projectionShape = PathUtils.getOffsetArea( + shapes, + # Make the projection very slightly larger + tolerance / 10, + removeHoles=False, # Projection has holes preserved + tolerance=tolerance, + plane=self.wpc) + internalShape = outlineShape.cut(projectionShape) + # Filter out tiny faces, usually artifacts around the perimeter of + # the cut. + minArea = (10 * tolerance)**2 + internalFaces = [ + f for f in internalShape.Faces if f.Area > minArea + ] + if internalFaces: + internalFaces = Part.makeCompound(internalFaces) + return ([outlineShape], [internalFaces]) + except Exception as e: + PathLog.warning( + "getOffsetArea failed: {}; Using FindUnifiedRegions.".format( + e)) + # Use face-unifying class + FUR = FindUnifiedRegions(shapeAndIndexTuples, tolerance) + if self.showDebugObjects: + FUR.setTempGroup(self.tempGroup) + return (FUR.getUnifiedRegions(), FUR.getInternalFeatures) + # Eclass @@ -1097,50 +1093,6 @@ def getSliceFromEnvelope(env): return tf -# Function to extract offset face from shape -def extractFaceOffset(fcShape, offset, wpc, makeComp=True): - '''extractFaceOffset(fcShape, offset) ... internal function. - Original _buildPathArea() version copied from PathAreaOp.py module. This version is modified. - Adjustments made based on notes by @sliptonic at this webpage: https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' - PathLog.debug('extractFaceOffset()') - - if fcShape.BoundBox.ZMin != 0.0: - fcShape.translate(FreeCAD.Vector(0.0, 0.0, 0.0 - fcShape.BoundBox.ZMin)) - - areaParams = {} - areaParams['Offset'] = offset - areaParams['Fill'] = 1 # 1 - areaParams['Coplanar'] = 0 - areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections - areaParams['Reorient'] = True - areaParams['OpenMode'] = 0 - areaParams['MaxArcPoints'] = 400 # 400 - areaParams['Project'] = True - - area = Path.Area() # Create instance of Area() class object - # area.setPlane(PathUtils.makeWorkplane(fcShape)) # Set working plane - area.setPlane(PathUtils.makeWorkplane(wpc)) # Set working plane to normal at Z=1 - area.add(fcShape) - area.setParams(**areaParams) # set parameters - - offsetShape = area.getShape() - wCnt = len(offsetShape.Wires) - if wCnt == 0: - return False - elif wCnt == 1: - ofstFace = Part.Face(offsetShape.Wires[0]) - if not makeComp: - ofstFace = [ofstFace] - else: - W = list() - for wr in offsetShape.Wires: - W.append(Part.Face(wr)) - if makeComp: - ofstFace = Part.makeCompound(W) - else: - ofstFace = W - - return ofstFace # offsetShape def _prepareModelSTLs(self, JOB, obj, m, ocl): @@ -1159,7 +1111,6 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): model, and avoided faces. Travel lines can be checked against this STL object to determine minimum travel height to clear stock and model.''' PathLog.debug('_makeSafeSTL()') - import MeshPart fuseShapes = list() Mdl = JOB.Model.Group[mdlIdx] @@ -1728,7 +1679,8 @@ def pathGeomToOffsetPointSet(obj, compGeoShp): optimize = obj.OptimizeLinearPaths ofstCnt = len(compGeoShp) - # Cycle through offeset loops + # Cycle through offset loops + iPOL = False for ei in range(0, ofstCnt): OS = compGeoShp[ei] lenOS = len(OS) @@ -2279,7 +2231,7 @@ class FindUnifiedRegions: face = Part.Face(wCS) return [face] else: - (faceShp, fcIdx) = self.FACES[0] + (faceShp, fcIdx) = self.FACES[0] msg = translate('PathSurfaceSupport', 'Failed to identify a horizontal cross-section for Face') msg += '{}.\n'.format(fcIdx + 1) @@ -2535,8 +2487,8 @@ class OCL_Tool(): if self.flatRadius == 0.0: self.flatRadius = self.diameter * 0.25 elif self.flatRadius > 0.0: - self.flatRadius = self.flatRadius * 1.25 - + self.flatRadius = self.flatRadius * 1.25 + def _oclCylCutter(self): # Standard End Mill, Slot cutter, or Fly cutter # OCL -> CylCutter::CylCutter(diameter, length) @@ -2680,3 +2632,5 @@ def makeExtendedBoundBox(wBB, bbBfr, zDep): L4 = Part.makeLine(p4, p1) return Part.Face(Part.Wire([L1, L2, L3, L4])) + + diff --git a/src/Mod/Path/PathScripts/PathUtils.py b/src/Mod/Path/PathScripts/PathUtils.py index 35ed2808db..47828c009e 100644 --- a/src/Mod/Path/PathScripts/PathUtils.py +++ b/src/Mod/Path/PathScripts/PathUtils.py @@ -336,6 +336,49 @@ def getEnvelope(partshape, subshape=None, depthparams=None): return envelopeshape +# Function to extract offset face from shape +def getOffsetArea(fcShape, + offset, + removeHoles=False, + # Default: XY plane + plane=Part.makeCircle(10), + tolerance=1e-4): + '''Make an offset area of a shape, projected onto a plane. + Positive offsets expand the area, negative offsets shrink it. + Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made + based on notes by @sliptonic at this webpage: + https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.''' + PathLog.debug('getOffsetArea()') + + areaParams = {} + areaParams['Offset'] = offset + areaParams['Fill'] = 1 # 1 + areaParams['Outline'] = removeHoles + areaParams['Coplanar'] = 0 + areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections + areaParams['Reorient'] = True + areaParams['OpenMode'] = 0 + areaParams['MaxArcPoints'] = 400 # 400 + areaParams['Project'] = True + areaParams['FitArcs'] = False # Can be buggy & expensive + areaParams['Deflection'] = tolerance + areaParams['Accuracy'] = tolerance + areaParams['Tolerance'] = 1e-5 # Equal point tolerance + areaParams['Simplify'] = True + areaParams['CleanDistance'] = tolerance / 5 + + area = Path.Area() # Create instance of Area() class object + # Set working plane normal to Z=1 + area.setPlane(makeWorkplane(plane)) + area.add(fcShape) + area.setParams(**areaParams) # set parameters + + offsetShape = area.getShape() + if not offsetShape.Faces: + return False + return offsetShape + + def reverseEdge(e): if DraftGeomUtils.geomType(e) == "Circle": arcstpt = e.valueAt(e.FirstParameter) diff --git a/src/Mod/Path/PathScripts/PathWaterline.py b/src/Mod/Path/PathScripts/PathWaterline.py index 3bb0960ab0..23374cc8fe 100644 --- a/src/Mod/Path/PathScripts/PathWaterline.py +++ b/src/Mod/Path/PathScripts/PathWaterline.py @@ -951,7 +951,7 @@ class ObjectWaterline(PathOp.ObjectOp): return commands def _waterlineDropCutScan(self, stl, smplInt, xmin, xmax, ymin, fd, numScanLines): - '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... + '''_waterlineDropCutScan(stl, smplInt, xmin, xmax, ymin, fd, numScanLines) ... Perform OCL scan for waterline purpose.''' pdc = ocl.PathDropCutter() # create a pdc pdc.setSTL(stl) @@ -1299,7 +1299,10 @@ class ObjectWaterline(PathOp.ObjectOp): activeArea = area.cut(trimFace) activeAreaWireCnt = len(activeArea.Wires) # first wire is boundFace wire self.showDebugObject(activeArea, 'ActiveArea_{}'.format(caCnt)) - ofstArea = PathSurfaceSupport.extractFaceOffset(activeArea, ofst, self.wpc, makeComp=False) + ofstArea = PathUtils.getOffsetArea(activeArea, + ofst, + self.wpc, + makeComp=False) if not ofstArea: data = FreeCAD.Units.Quantity(csHght, FreeCAD.Units.Length).UserString PathLog.debug('No offset area returned for cut area depth at {}.'.format(data)) @@ -1359,7 +1362,7 @@ class ObjectWaterline(PathOp.ObjectOp): CUTAREAS = list() isFirst = True lenDP = len(depthparams) - + # Cycle through layer depths for dp in range(0, lenDP): csHght = depthparams[dp] @@ -1472,7 +1475,10 @@ class ObjectWaterline(PathOp.ObjectOp): cont = True cnt = 0 while cont: - ofstArea = PathSurfaceSupport.extractFaceOffset(shape, ofst, self.wpc, makeComp=True) + ofstArea = PathUtils.getOffsetArea(shape, + ofst, + self.wpc, + makeComp=True) if not ofstArea: break for F in ofstArea.Faces: @@ -1584,7 +1590,7 @@ class ObjectWaterline(PathOp.ObjectOp): li = fIds.pop() low = csFaces[li] # senior face pIds = self._idInternalFeature(csFaces, fIds, pIds, li, low) - + for af in range(lenCsF - 1, -1, -1): # cycle from last item toward first prnt = pIds[af] if prnt == -1: