diff --git a/src/Mod/Path/PathScripts/PathSurface.py b/src/Mod/Path/PathScripts/PathSurface.py index 3c879430c3..bce1a33f5f 100644 --- a/src/Mod/Path/PathScripts/PathSurface.py +++ b/src/Mod/Path/PathScripts/PathSurface.py @@ -418,6 +418,20 @@ class ObjectSurface(PathOp.ObjectOp): obj.AvoidLastX_Faces = 100 PathLog.error(translate('PathSurface', 'AvoidLastX_Faces: Avoid last X faces count limited to 100.')) + def opUpdateDepths(self, obj): + if hasattr(obj, 'Base') and obj.Base: + base, sublist = obj.Base[0] + fbb = base.Shape.getElement(sublist[0]).BoundBox + zmin = fbb.ZMax + for base, sublist in obj.Base: + for sub in sublist: + try: + fbb = base.Shape.getElement(sub).BoundBox + zmin = min(zmin, fbb.ZMin) + except Part.OCCError as e: + PathLog.error(e) + obj.OpFinalDepth = zmin + def opExecute(self, obj): '''opExecute(obj) ... process surface operation''' PathLog.track() @@ -661,7 +675,7 @@ class ObjectSurface(PathOp.ObjectOp): else: exTime = str(round(execTime, 5)) + ' sec.' msg = translate('PathSurface', 'operation time is') - FreeCAD.Console.PrintMessage('3D Surface ' + msg + '{}\n'.format(exTime)) + FreeCAD.Console.PrintMessage('3D Surface ' + msg + ' {}\n'.format(exTime)) if self.cancelOperation: FreeCAD.ActiveDocument.openTransaction(translate("PathSurface", "Canceled 3D Surface operation.")) diff --git a/src/Mod/Path/PathScripts/PathSurfaceSupport.py b/src/Mod/Path/PathScripts/PathSurfaceSupport.py index 3e5856a1bc..900415e404 100644 --- a/src/Mod/Path/PathScripts/PathSurfaceSupport.py +++ b/src/Mod/Path/PathScripts/PathSurfaceSupport.py @@ -101,12 +101,6 @@ class PathGeometryGenerator: self._prepareConstants() def _prepareConstants(self): - # Apply drop cutter extra offset and set the max and min XY area of the operation - # xmin = self.shape.BoundBox.XMin - # xmax = self.shape.BoundBox.XMax - # ymin = self.shape.BoundBox.YMin - # ymax = self.shape.BoundBox.YMax - # Compute weighted center of mass of all faces combined if self.pattern in ['Circular', 'CircularZigZag', 'Spiral']: if self.obj.PatternCenterAt == 'CenterOfMass': @@ -235,20 +229,6 @@ class PathGeometryGenerator: centRot = FreeCAD.Vector(0.0, 0.0, 0.0) # Bottom left corner of face/selection/model cAng = math.atan(self.deltaX / self.deltaY) # BoundaryBox angle - # Determine end points and create top lines - # x1 = centRot.x - self.halfDiag - # x2 = centRot.x + self.halfDiag - # diag = None - # if self.obj.CutPatternAngle == 0 or self.obj.CutPatternAngle == 180: - # diag = self.deltaY - # elif self.obj.CutPatternAngle == 90 or self.obj.CutPatternAngle == 270: - # diag = self.deltaX - # else: - # perpDist = math.cos(cAng - math.radians(self.obj.CutPatternAngle)) * self.deltaC - # diag = perpDist - # y1 = centRot.y + diag - # y2 = y1 - # Create end points for set of lines to intersect with cross-section face pntTuples = list() for lc in range((-1 * (self.halfPasses - 1)), self.halfPasses + 1): @@ -576,16 +556,16 @@ class ProcessSelectedFaces: FreeCAD.Console.PrintError(msg) else: (fcShp, prflShp) = pPEB - if fcShp is not False: + if fcShp: if fcShp is True: PathLog.debug(' -fcShp is True.') fShapes[m] = True else: fShapes[m] = [fcShp] - if prflShp is not False: - if fcShp is not False: + if prflShp: + if fcShp: PathLog.debug('vShapes[{}]: {}'.format(m, vShapes[m])) - if vShapes[m] is not False: + if vShapes[m]: PathLog.debug(' -Cutting void from base profile shape.') adjPS = prflShp.cut(vShapes[m][0]) self.profileShapes[m] = [adjPS] @@ -696,7 +676,7 @@ class ProcessSelectedFaces: PathLog.debug('.. include Profile Edge') ofstVal = self._calculateOffsetValue(isHole) psOfst = extractFaceOffset(cfsL, ofstVal, self.wpc) - if psOfst is not False: + if psOfst: mPS = [psOfst] if self.profileEdges == 'Only': mFS = True @@ -721,7 +701,7 @@ class ProcessSelectedFaces: if cont: lenIfL = len(ifL) - if self.obj.InternalFeaturesCut is False: + if not self.obj.InternalFeaturesCut: if lenIfL == 0: PathLog.debug(' -No internal features saved.') else: @@ -737,7 +717,6 @@ class ProcessSelectedFaces: ofstVal = self._calculateOffsetValue(isHole=True) intOfstShp = extractFaceOffset(casL, ofstVal, self.wpc) mIFS.append(intOfstShp) - # faceOfstShp = faceOfstShp.cut(intOfstShp) mFS = [faceOfstShp] # Eif @@ -753,17 +732,19 @@ class ProcessSelectedFaces: FUR = FindUnifiedRegions([(fcshp, fcIdx)], self.JOB.GeometryTolerance.Value) if self.showDebugObjects: FUR.setTempGroup(self.tempGroup) - outerFace = FUR.getUnifiedRegions()[0] - if not self.obj.InternalFeaturesCut: - ifL = FUR.getInternalFeatures() + gUR = FUR.getUnifiedRegions() + if len(gUR) > 0: + outerFace = gUR[0] + if not self.obj.InternalFeaturesCut: + ifL = FUR.getInternalFeatures() - if outerFace is not False: + 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) - if psOfst is not False: + if psOfst: if mPS is False: mPS = list() mPS.append(psOfst) @@ -805,7 +786,7 @@ class ProcessSelectedFaces: for ifs in mIFS: mVS.append(ifs) - if VDS is not False: + if VDS: PathLog.debug('Processing avoid faces.') cont = True isHole = False @@ -908,7 +889,7 @@ class ProcessSelectedFaces: PathLog.debug(' -Attempting profile geometry for model base.') ofstVal = self._calculateOffsetValue(isHole) psOfst = extractFaceOffset(csFaceShape, ofstVal, self.wpc) - if psOfst is not False: + if psOfst: if self.profileEdges == 'Only': return (True, psOfst) prflShp = psOfst @@ -1220,7 +1201,7 @@ def _makeSafeSTL(self, JOB, obj, mdlIdx, faceShapes, voidShapes, ocl): B = Part.makeBox(bL, bW, bH, crnr, FreeCAD.Vector(0, 0, 1)) fuseShapes.append(B) - if voidShapes is not False: + if voidShapes: voidComp = Part.makeCompound(voidShapes) voidEnv = PathUtils.getEnvelope(partshape=voidComp, depthparams=self.depthParams) # Produces .Shape fuseShapes.append(voidEnv) @@ -1608,7 +1589,7 @@ def pathGeomToCircularPointSet(obj, compGeoShp, cutClimb, toolDiam, closedGap, g chkGap = False lst = None - if CONN is not False: + if CONN: (iE, iS) = CONN v1 = compGeoShp.Edges[iE].Vertexes[0] v2 = compGeoShp.Edges[iS].Vertexes[1] @@ -1883,6 +1864,7 @@ class FindUnifiedRegions: count[1] += 1 def _groupEdgesByLength(self): + PathLog.debug('_groupEdgesByLength()') cont = True threshold = self.geomToler grp = list() @@ -1933,6 +1915,7 @@ class FindUnifiedRegions: self.idGroups.append(grp) def _identifySharedEdgesByLength(self, grp): + PathLog.debug('_identifySharedEdgesByLength()') holds = list() cont = True specialIndexes = [] @@ -1992,6 +1975,7 @@ class FindUnifiedRegions: self.noSharedEdges = False def _extractWiresFromEdges(self): + PathLog.debug('_extractWiresFromEdges()') DATA = self.edgeData holds = list() lastEdge = None @@ -2090,16 +2074,34 @@ class FindUnifiedRegions: cont = False # Ewhile - if len(LOOPS) > 0: + numLoops = len(LOOPS) + PathLog.debug(' -numLoops: {}.'.format(numLoops)) + if numLoops > 0: FACES = list() - for Edges in LOOPS: + for li in range(0, numLoops): + Edges = LOOPS[li] + #for e in Edges: + # self._showShape(e, 'Loop_{}_Edge'.format(li)) wire = Part.Wire(Part.__sortEdges__(Edges)) if wire.isClosed(): - face = Part.Face(wire) + # This simple Part.Face() method fails to catch + # wires with tangent closed wires, or an external + # wire with one or more internal tangent wires. + # face = Part.Face(wire) + + # This method works with the complex tangent + # closed wires mentioned above. + extWire = wire.extrude(FreeCAD.Vector(0.0, 0.0, 2.0)) + wireSolid = Part.makeSolid(extWire) + extdBBFace1 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) + extdBBFace2 = makeExtendedBoundBox(wireSolid.BoundBox, 5.0, wireSolid.BoundBox.ZMin + 1.0) + inverse = extdBBFace1.cut(wireSolid) + face = extdBBFace2.cut(inverse) self.REGIONS.append(face) self.REGIONS.sort(key=faceArea, reverse=True) def _identifyInternalFeatures(self): + PathLog.debug('_identifyInternalFeatures()') remList = list() for (top, fcIdx) in self.topFaces: @@ -2121,6 +2123,7 @@ class FindUnifiedRegions: self.REGIONS.pop(ri) def _processNestedRegions(self): + PathLog.debug('_processNestedRegions()') cont = True hold = list() Ids = list() @@ -2222,6 +2225,7 @@ class FindUnifiedRegions: def getUnifiedRegions(self): '''getUnifiedRegions()... Returns a list of unified regions from list of tuples (faceShape, faceIndex) received at instantiation of the class object.''' + PathLog.debug('getUnifiedRegions()') self.INTERNALS = list() if len(self.FACES) == 0: msg = translate('PathSurfaceSupport', @@ -2231,7 +2235,6 @@ class FindUnifiedRegions: self._extractTopFaces() lenFaces = len(self.topFaces) - PathLog.debug('getUnifiedRegions() lenFaces: {}.'.format(lenFaces)) if lenFaces == 0: return [] @@ -2243,28 +2246,26 @@ class FindUnifiedRegions: lenWrs = len(topFace.Wires) if lenWrs > 1: for w in range(1, lenWrs): - # Any internal wires need to be flattened - # before appending to self.INTERNALS - # A problem exists that inner wires are not all recognized as wires. - # Some are single circular edges. - # Face.Edges.__len_() - Face.OuterWire.Edges.__len__() = edges for inner wire(s) - - # extWire = getExtrudedShape(wr) - # wCS = getCrossSection(extWire) - # wCS.translate(FreeCAD.Vector(0.0, 0.0, wr.BoundBox.ZMin)) wr = topFace.Wires[w] self.INTERNALS.append(Part.Face(wr)) # Flatten face and extract outer wire, then convert to face extWire = getExtrudedShape(topFace) wCS = getCrossSection(extWire) - wCS.translate(FreeCAD.Vector(0.0, 0.0, topFace.BoundBox.ZMin)) - face = Part.Face(wCS) - return [face] + if wCS: + face = Part.Face(wCS) + return [face] + else: + (faceShp, fcIdx) = self.FACES[0] + msg = translate('PathSurfaceSupport', + 'Failed to identify a horizontal cross-section for Face') + msg += '{}.\n'.format(fcIdx + 1) + FreeCAD.Console.PrintWarning(msg) + return [] # process multiple top faces, unifying if possible self._fuseTopFaces() - # for F in self.fusedFaces.Faces: - # self._showShape(F, 'TopFaceFused') + for F in self.fusedFaces.Faces: + self._showShape(F, 'TopFaceFused') self._getEdgesData() self._groupEdgesByLength() @@ -2272,7 +2273,7 @@ class FindUnifiedRegions: self._identifySharedEdgesByLength(grp) if self.noSharedEdges: - PathLog.debug('No shared edges by length detected.\n') + PathLog.debug('No shared edges by length detected.') return [topFace for (topFace, fcIdx) in self.topFaces] else: # Delete shared edges from edgeData list @@ -2283,8 +2284,8 @@ class FindUnifiedRegions: self._extractWiresFromEdges() self._identifyInternalFeatures() self._processNestedRegions() - for ri in range(0, len(self.REGIONS)): - self._showShape(self.REGIONS[ri], 'UnifiedRegion_{}'.format(ri)) + # for ri in range(0, len(self.REGIONS)): + # self._showShape(self.REGIONS[ri], 'UnifiedRegion_{}'.format(ri)) return self.REGIONS @@ -2298,3 +2299,18 @@ class FindUnifiedRegions: FreeCAD.Console.PrintError(msg + '\n') return False # Eclass + +# Support functions +def makeExtendedBoundBox(wBB, bbBfr, zDep): + PathLog.debug('makeExtendedBoundBox()') + p1 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMin - bbBfr, zDep) + p2 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMin - bbBfr, zDep) + p3 = FreeCAD.Vector(wBB.XMax + bbBfr, wBB.YMax + bbBfr, zDep) + p4 = FreeCAD.Vector(wBB.XMin - bbBfr, wBB.YMax + bbBfr, zDep) + + L1 = Part.makeLine(p1, p2) + L2 = Part.makeLine(p2, p3) + L3 = Part.makeLine(p3, p4) + L4 = Part.makeLine(p4, p1) + + return Part.Face(Part.Wire([L1, L2, L3, L4]))