def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") self.removalshapes = [] self.horiz = [] vertical = [] for o in obj.Base: PathLog.debug("Base item: {}".format(o)) base = o[0] for sub in o[1]: if "Face" in sub: face = base.Shape.getElement(sub) if type(face.Surface) == Part.Plane and PathGeom.isVertical(face.Surface.Axis): # it's a flat horizontal face self.horiz.append(face) elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis): # vertical cylinder wall if any(e.isClosed() for e in face.Edges): # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) self.horiz.append(disk) else: # partial cylinder wall vertical.append(face) elif type(face.Surface) == Part.Plane and PathGeom.isHorizontal(face.Surface.Axis): vertical.append(face) else: PathLog.error(translate('PathPocket', "Pocket does not support shape %s.%s") % (base.Label, sub)) self.vertical = PathGeom.combineConnectedShapes(vertical) self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): PathLog.error(translate('PathPocket', 'Vertical faces do not form a loop - ignoring')) else: self.horiz.append(face) # move all horizontal faces to FinalDepth for f in self.horiz: f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin)) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() shape.tessellate(0.1) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) self.horizontal.append(Part.Face(wire)) else: self.horizontal.append(shape) # extrude all faces up to StartDepth and those are the removal shapes extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) self.removalshapes = [(face.extrude(extent), False) for face in self.horizontal] else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outline = Part.Face(TechDraw.findShapeOutline(self.baseobject.Shape, 1, FreeCAD.Vector(0, 0, 1))) stockBB = self.stock.Shape.BoundBox self.outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) self.body = self.outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.removalshapes = [(self.stock.Shape.cut(self.body), False)] for (shape,hole) in self.removalshapes: shape.tessellate(0.1) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("areaOpShapes() in PathPocketShape.py") def judgeFinalDepth(obj, fD): if obj.FinalDepth.Value >= fD: return obj.FinalDepth.Value else: return fD def judgeStartDepth(obj, sD): if obj.StartDepth.Value >= sD: return obj.StartDepth.Value else: return sD def analyzeVerticalFaces(self, obj, vertTuples): hT = [] # base = FreeCAD.ActiveDocument.getObject(self.modelName) # Separate elements, regroup by orientation (axis_angle combination) vTags = ['X34.2'] vGrps = [[(2.3, 3.4, 'X')]] for tup in vertTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in vTags: # Determine index of found string i = 0 for orn in vTags: if orn == tag: break i += 1 vGrps[i].append(tup) else: vTags.append(tag) # add orientation entry vGrps.append([tup]) # add orientation entry # Remove temp elements vTags.pop(0) vGrps.pop(0) # check all faces in each axis_angle group shpList = [] zmaxH = 0.0 for o in range(0, len(vTags)): shpList = [] zmaxH = vGrps[o][0].BoundBox.ZMax for (face, sub, angle, axis, tag, strDep, finDep, trans) in vGrps[o]: shpList.append(face) # Identify tallest face to use as zMax if face.BoundBox.ZMax > zmaxH: zmaxH = face.BoundBox.ZMax # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop self.vertical = PathGeom.combineConnectedShapes(shpList) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring') ) else: strDep = zmaxH + self.leadIn # base.Shape.BoundBox.ZMax finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, sub, angle, axis, tag, strDep, finDep, trans hT.append(tup) # Eol return hT if obj.Base: PathLog.debug('base items exist. Processing...') self.removalshapes = [] self.horiz = [] vertical = [] horizTuples = [] vertTuples = [] axis = 'X' angle = 0.0 reset = False resetPlacement = None trans = FreeCAD.Vector(0.0, 0.0, 0.0) for o in obj.Base: PathLog.debug('Base item: {}'.format(o)) base = o[0] # Limit sub faces to children of single Model object. if self.modelName is None: self.modelName = base.Name else: if base.Name != self.modelName: for sub in o[1]: PathLog.error(sub + " is not a part of Model object: " + self.modelName) o[1] = [] PathLog.error( "Only processing faces on a single Model object per operation." ) PathLog.error( "You will need to separate faces per Model object within the Job." ) startBase = FreeCAD.Vector(base.Placement.Base.x, base.Placement.Base.y, base.Placement.Base.z) startAngle = base.Placement.Rotation.Angle startAxis = base.Placement.Rotation.Axis startRotation = FreeCAD.Rotation(startAxis, startAngle) resetPlacement = FreeCAD.Placement(startBase, startRotation) for sub in o[1]: if 'Face' in sub: PathLog.debug('sub: {}'.format(sub)) # Determine angle of rotation needed to make normal vector = (0,0,1) strDep = obj.StartDepth.Value finDep = obj.FinalDepth.Value trans = FreeCAD.Vector(0.0, 0.0, 0.0) rtn = False if obj.UseRotation != 'Off': (rtn, angle, axis) = self.pocketRotationAnalysis(obj, base, sub, prnt=True) if rtn is True: reset = True PathLog.debug( str(sub) + ": rotating model to make face normal at (0,0,1) ..." ) if axis == 'X': bX = 0.0 bY = 0.0 bZ = math.sin(math.radians( angle)) * base.Placement.Base.y vect = FreeCAD.Vector(1, 0, 0) elif axis == 'Y': bX = 0.0 bY = 0.0 bZ = math.sin(math.radians( angle)) * base.Placement.Base.x if obj.B_AxisErrorOverride is True: bZ = -1 * bZ vect = FreeCAD.Vector(0, 1, 0) # Rotate base to such that Surface.Axis of pocket bottom is Z=1 base.Placement.Rotation = FreeCAD.Rotation( vect, angle) base.recompute() trans = FreeCAD.Vector(bX, bY, bZ) else: axis = 'X' angle = 0.0 tag = axis + str(round(angle, 7)) face = base.Shape.getElement(sub) if type(face.Surface ) == Part.Plane and PathGeom.isVertical( face.Surface.Axis): # it's a flat horizontal face PathLog.debug(" == Part.Plane: isVertical") # Adjust start and finish depths for pocket strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans horizTuples.append(tup) elif type(face.Surface ) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis): PathLog.debug("== Part.Cylinder") # vertical cylinder wall if any(e.isClosed() for e in face.Edges): PathLog.debug("e.isClosed()") # complete cylinder circle = Part.makeCircle( face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth( obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = disk, sub, angle, axis, tag, strDep, finDep, trans horizTuples.append(tup) else: # partial cylinder wall vertical.append(face) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth( obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans vertTuples.append(tup) PathLog.debug(sub + "is vertical after rotation.") elif type(face.Surface ) == Part.Plane and PathGeom.isHorizontal( face.Surface.Axis): vertical.append(face) # Adjust start and finish depths for pocket strDep = face.BoundBox.ZMax + self.leadIn # base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) # Over-write default final depth value, leaves manual override by user obj.StartDepth.Value = trans.z + strDep obj.FinalDepth.Value = trans.z + finDep tup = face, sub, angle, axis, tag, strDep, finDep, trans vertTuples.append(tup) PathLog.debug(sub + "is vertical after rotation.") else: PathLog.error( translate( 'PathPocket', 'Pocket does not support shape %s.%s') % (base.Label, sub)) if reset is True: base.Placement.Rotation = startRotation base.recompute() reset = False # End IF # End FOR base.Placement = resetPlacement base.recompute() # End FOR # Analyze vertical faces via PathGeom.combineConnectedShapes() # hT = analyzeVerticalFaces(self, obj, vertTuples) # horizTuples.extend(hT) # This section will be replaced analyzeVerticalFaces(self, obj, vertTuples) above self.vertical = PathGeom.combineConnectedShapes(vertical) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0.0, 0.0, 1.0)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring')) else: # self.horiz.append(face) strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, 'vertFace', 0.0, 'X', 'X0.0', strDep, finDep, FreeCAD.Vector( 0.0, 0.0, 0.0) horizTuples.append(tup) # add faces for extensions self.exts = [] for ext in self.getExtensions(obj): wire = Part.Face(ext.getWire()) if wire: face = Part.Face(wire) # self.horiz.append(face) strDep = base.Shape.BoundBox.ZMax + self.leadIn finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, 'vertFace', 0.0, 'X', 'X0.0', strDep, finDep, FreeCAD.Vector( 0.0, 0.0, 0.0) horizTuples.append(tup) self.exts.append(face) # move all horizontal faces to FinalDepth for (face, sub, angle, axis, tag, strDep, finDep, trans) in horizTuples: # face.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - face.BoundBox.ZMin)) if angle <= 0.0: if axis == 'X': face.translate( FreeCAD.Vector( 0, trans.z, trans.z + finDep - face.BoundBox.ZMin)) elif axis == 'Y': face.translate( FreeCAD.Vector( -1 * trans.z, 0, trans.z + finDep - face.BoundBox.ZMin)) else: if axis == 'X': face.translate( FreeCAD.Vector( 0, -1 * trans.z, trans.z + finDep - face.BoundBox.ZMin)) elif axis == 'Y': face.translate( FreeCAD.Vector( trans.z, 0, trans.z + finDep - face.BoundBox.ZMin)) # Separate elements, regroup by orientation (axis_angle combination) hTags = ['X34.2'] hGrps = [[(2.3, 3.4, 'X')]] for tup in horizTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in hTags: # Determine index of found string i = 0 for orn in hTags: if orn == tag: break i += 1 hGrps[i].append(tup) else: hTags.append(tag) # add orientation entry hGrps.append([tup]) # add orientation entry # Remove temp elements hTags.pop(0) hGrps.pop(0) # check all faces in each axis_angle group self.horizontal = [] shpList = [] for o in range(0, len(hTags)): PathLog.debug('hTag: {}'.format(hTags[o])) shpList = [] for (face, sub, angle, axis, tag, strDep, finDep, trans) in hGrps[o]: shpList.append(face) # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop for shape in PathGeom.combineConnectedShapes(shpList): shape.sewShape() # shape.tessellate(0.05) # Russ4262 0.1 original if obj.UseOutline: wire = TechDraw.findShapeOutline( shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate( FreeCAD.Vector( 0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) PathLog.debug( " -obj.UseOutline: obj.FinalDepth.Value" + str(obj.FinalDepth.Value)) PathLog.debug(" -obj.UseOutline: wire.BoundBox.ZMin" + str(wire.BoundBox.ZMin)) # shape.tessellate(0.05) # Russ4262 0.1 original face = Part.Face(wire) tup = face, sub, angle, axis, tag, strDep, finDep, trans self.horizontal.append(tup) else: # Re-pair shape to tuple set for (face, sub, angle, axis, tag, strDep, finDep, trans) in hGrps[o]: if shape is face: tup = face, sub, angle, axis, tag, strDep, finDep, trans self.horizontal.append(tup) break # Eol # extrude all faces up to StartDepth and those are the removal shapes for (face, sub, angle, axis, tag, strDep, finDep, trans) in self.horizontal: # extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) extent = FreeCAD.Vector(0, 0, strDep - finDep) shp = face.removeSplitter().extrude(extent) # tup = shp, False, sub, angle, axis, tag, strDep, finDep, trans tup = shp, False, sub, angle, axis # shape, isHole, sub, angle, axis self.removalshapes.append(tup) else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model ] stockBB = self.stock.Shape.BoundBox PathLog.debug(" -Using outlines; no obj.Base") self.removalshapes = [] self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude( FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append( (self.stock.Shape.cut(body), False, 'outline', 0.0, 'X')) for (shape, isHole, sub, angle, axis) in self.removalshapes: shape.tessellate(0.05) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def areaOpShapes(self, obj): """areaOpShapes(obj) ... return shapes representing the solids to be removed.""" PathLog.track() self.removalshapes = [] # self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.removalshapes = [] avoidFeatures = list() # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) for e in extensions: if e.avoid: avoidFeatures.append(e.feature) if obj.Base: PathLog.debug("base items exist. Processing...") self.horiz = [] self.vert = [] for (base, subList) in obj.Base: for sub in subList: if "Face" in sub: if sub not in avoidFeatures and not self.clasifySub( base, sub): PathLog.error( "Pocket does not support shape {}.{}".format( base.Label, sub)) # Convert horizontal faces to use outline only if requested if obj.UseOutline and self.horiz: horiz = [Part.Face(f.Wire1) for f in self.horiz] self.horiz = horiz # Check if selected vertical faces form a loop if len(self.vert) > 0: self.vertical = PathGeom.combineConnectedShapes(self.vert) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): PathLog.error( "Vertical faces do not form a loop - ignoring") else: self.horiz.append(face) # Add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in extensions: 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) # check all faces and see if they are touching/overlapping and combine and simplify self.horizontal = PathGeom.combineHorizontalFaces(self.horiz) # 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: buffer = 0.000001 else: buffer = self.job.GeometryTolerance.Value / 10.0 for h in self.horizontal: h.translate( FreeCAD.Vector( 0.0, 0.0, obj.FinalDepth.Value - h.BoundBox.ZMin - buffer)) # extrude all faces up to StartDepth plus buffer and those are the removal shapes extent = FreeCAD.Vector( 0, 0, obj.StartDepth.Value - obj.FinalDepth.Value + buffer) self.removalshapes = [(face.removeSplitter().extrude(extent), False) for face in self.horizontal] else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model ] stockBB = self.stock.Shape.BoundBox self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude( FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False)) # Tessellate all working faces # for (shape, hole) in self.removalshapes: # shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = Part.makeCompound( [tup[0] for tup in self.removalshapes]) return self.removalshapes
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("----- areaOpShapes() in PathPocketShape.py") baseSubsTuples = [] subCount = 0 allTuples = [] finalDepths = [] def planarFaceFromExtrusionEdges(face, trans): useFace = 'useFaceName' minArea = 0.0 fCnt = 0 clsd = [] planar = False # Identify closed edges for edg in face.Edges: if edg.isClosed(): PathLog.debug(' -e.isClosed()') clsd.append(edg) planar = True # Attempt to create planar faces and select that with smallest area for use as pocket base if planar is True: planar = False for edg in clsd: fCnt += 1 fName = sub + '_face_' + str(fCnt) # Create planar face from edge mFF = Part.Face(Part.Wire(Part.__sortEdges__([edg]))) if mFF.isNull(): PathLog.debug('Face(Part.Wire()) failed') else: if trans is True: mFF.translate( FreeCAD.Vector( 0, 0, face.BoundBox.ZMin - mFF.BoundBox.ZMin)) if FreeCAD.ActiveDocument.getObject(fName): FreeCAD.ActiveDocument.removeObject(fName) tmpFace = FreeCAD.ActiveDocument.addObject( 'Part::Feature', fName).Shape = mFF tmpFace = FreeCAD.ActiveDocument.getObject(fName) tmpFace.purgeTouched() if minArea == 0.0: minArea = tmpFace.Shape.Face1.Area useFace = fName planar = True elif tmpFace.Shape.Face1.Area < minArea: minArea = tmpFace.Shape.Face1.Area FreeCAD.ActiveDocument.removeObject(useFace) useFace = fName else: FreeCAD.ActiveDocument.removeObject(fName) if useFace != 'useFaceName': self.useTempJobClones(useFace) return (planar, useFace) def clasifySub(self, bs, sub): face = bs.Shape.getElement(sub) if type(face.Surface) == Part.Plane: PathLog.debug('type() == Part.Plane') if PathGeom.isVertical(face.Surface.Axis): PathLog.debug(' -isVertical()') # it's a flat horizontal face self.horiz.append(face) return True elif PathGeom.isHorizontal(face.Surface.Axis): PathLog.debug(' -isHorizontal()') self.vert.append(face) return True else: return False elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis): PathLog.debug('type() == Part.Cylinder') # vertical cylinder wall if any(e.isClosed() for e in face.Edges): PathLog.debug(' -e.isClosed()') # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) disk.translate( FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin)) self.horiz.append(disk) return True else: PathLog.debug(' -none isClosed()') # partial cylinder wall self.vert.append(face) return True elif type(face.Surface) == Part.SurfaceOfExtrusion: # extrusion wall PathLog.debug('type() == Part.SurfaceOfExtrusion') # Attempt to extract planar face from surface of extrusion (planar, useFace) = planarFaceFromExtrusionEdges(face, trans=True) # Save face object to self.horiz for processing or display error if planar is True: uFace = FreeCAD.ActiveDocument.getObject(useFace) self.horiz.append(uFace.Shape.Faces[0]) msg = translate( 'Path', "<b>Verify depth of pocket for '{}'.</b>".format(sub)) msg += translate( 'Path', "\n<br>Pocket is based on extruded surface.") msg += translate( 'Path', "\n<br>Bottom of pocket might be non-planar and/or not normal to spindle axis." ) msg += translate( 'Path', "\n<br>\n<br><i>3D pocket bottom is NOT available in this operation</i>." ) PathLog.info(msg) title = translate('Path', 'Depth Warning') self.guiMessage(title, msg, False) else: PathLog.error( translate( "Path", "Failed to create a planar face from edges in {}.". format(sub))) else: PathLog.debug(' -type(face.Surface): {}'.format( type(face.Surface))) return False if obj.Base: PathLog.debug('Processing... obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init # ---------------------------------------------------------------------- if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: baseSubsTuples.append((base, subList, 0.0, 'X', stock)) else: for p in range(0, len(obj.Base)): (base, subsList) = obj.Base[p] isLoop = False # First, check all subs collectively for loop of faces if len(subsList) > 2: (isLoop, norm, surf) = self.checkForFacesLoop(base, subsList) if isLoop is True: PathLog.info( "Common Surface.Axis or normalAt() value found for loop faces." ) rtn = False subCount += 1 (rtn, angle, axis, praInfo) = self.faceRotationAnalysis(obj, norm, surf) # pylint: disable=unused-variable PathLog.info("angle: {}; axis: {}".format( angle, axis)) if rtn is True: faceNums = "" for f in subsList: faceNums += '_' + f.replace('Face', '') (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis( obj, base, angle, axis, faceNums) # pylint: disable=unused-variable # Verify faces are correctly oriented - InverseAngle might be necessary PathLog.debug( "Checking if faces are oriented correctly after rotation..." ) for sub in subsList: face = clnBase.Shape.getElement(sub) if type(face.Surface) == Part.Plane: if not PathGeom.isHorizontal( face.Surface.Axis): rtn = False break if rtn is False: if obj.AttemptInverseAngle is True and obj.InverseAngle is False: (clnBase, clnStock, angle) = self.applyInverseAngle( obj, clnBase, clnStock, axis, angle) else: PathLog.info( translate( "Path", "Consider toggling the InverseAngle property and recomputing the operation." )) tup = clnBase, subsList, angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: PathLog.debug("No rotation used") axis = 'X' angle = 0.0 stock = PathUtils.findParentJob(obj).Stock tup = base, subsList, angle, axis, stock # Eif allTuples.append(tup) baseSubsTuples.append(tup) # Eif if isLoop is False: PathLog.debug( translate('Path', "Processing subs individually ...")) for sub in subsList: subCount += 1 if 'Face' in sub: rtn = False face = base.Shape.getElement(sub) if type(face.Surface ) == Part.SurfaceOfExtrusion: # extrusion wall PathLog.debug( 'analyzing type() == Part.SurfaceOfExtrusion' ) # Attempt to extract planar face from surface of extrusion (planar, useFace) = planarFaceFromExtrusionEdges( face, trans=False) # Save face object to self.horiz for processing or display error if planar is True: base = FreeCAD.ActiveDocument.getObject( useFace) sub = 'Face1' PathLog.debug( ' -successful face created: {}'. format(useFace)) else: PathLog.error( translate( "Path", "Failed to create a planar face from edges in {}." .format(sub))) (norm, surf) = self.getFaceNormAndSurf(face) (rtn, angle, axis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) # pylint: disable=unused-variable if rtn is True: faceNum = sub.replace('Face', '') (clnBase, angle, clnStock, tag) = self.applyRotationalAnalysis( obj, base, angle, axis, faceNum) # Verify faces are correctly oriented - InverseAngle might be necessary faceIA = clnBase.Shape.getElement(sub) (norm, surf) = self.getFaceNormAndSurf(faceIA) (rtn, praAngle, praAxis, praInfo) = self.faceRotationAnalysis( obj, norm, surf) # pylint: disable=unused-variable if rtn is True: PathLog.debug( "Face not aligned after initial rotation." ) if obj.AttemptInverseAngle is True and obj.InverseAngle is False: (clnBase, clnStock, angle) = self.applyInverseAngle( obj, clnBase, clnStock, axis, angle) else: PathLog.info( translate( "Path", "Consider toggling the InverseAngle property and recomputing the operation." )) else: PathLog.debug( "Face appears to be oriented correctly." ) tup = clnBase, [sub], angle, axis, clnStock else: if self.warnDisabledAxis(obj, axis) is False: PathLog.debug( str(sub) + ": No rotation used") axis = 'X' angle = 0.0 stock = PathUtils.findParentJob(obj).Stock tup = base, [sub], angle, axis, stock # Eif allTuples.append(tup) baseSubsTuples.append(tup) else: ignoreSub = base.Name + '.' + sub PathLog.error( translate( 'Path', "Selected feature is not a Face. Ignoring: {}" .format(ignoreSub))) for o in baseSubsTuples: self.horiz = [] # pylint: disable=attribute-defined-outside-init self.vert = [] # pylint: disable=attribute-defined-outside-init subBase = o[0] subsList = o[1] angle = o[2] axis = o[3] stock = o[4] for sub in subsList: if 'Face' in sub: if clasifySub(self, subBase, sub) is False: PathLog.error( translate( 'PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub)) if obj.EnableRotation != 'Off': PathLog.info( translate( 'PathPocket', 'Face might not be within rotation accessibility limits.' )) # Determine final depth as highest value of bottom boundbox of vertical face, # in case of uneven faces on bottom if len(self.vert) > 0: vFinDep = self.vert[0].BoundBox.ZMin for vFace in self.vert: if vFace.BoundBox.ZMin > vFinDep: vFinDep = vFace.BoundBox.ZMin # Determine if vertical faces for a loop: Extract planar loop wire as new horizontal face. self.vertical = PathGeom.combineConnectedShapes(self.vert) # pylint: disable=attribute-defined-outside-init self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] # pylint: disable=attribute-defined-outside-init for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): msg = translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring') PathLog.error(msg) # title = translate("Path", "Face Selection Warning") # self.guiMessage(title, msg, True) else: face.translate( FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin)) self.horiz.append(face) msg = translate( 'Path', 'Verify final depth of pocket shaped by vertical faces.' ) PathLog.error(msg) title = translate('Path', 'Depth Warning') self.guiMessage(title, msg, False) # add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in self.getExtensions(obj): wire = ext.getWire() if wire: face = Part.Face(wire) self.horiz.append(face) self.exts.append(face) # move all horizontal faces to FinalDepth for f in self.horiz: finDep = max(obj.FinalDepth.Value, f.BoundBox.ZMin) f.translate(FreeCAD.Vector(0, 0, finDep - f.BoundBox.ZMin)) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] # pylint: disable=attribute-defined-outside-init for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() # shape.tessellate(0.1) if obj.UseOutline: wire = TechDraw.findShapeOutline( shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate( FreeCAD.Vector( 0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) self.horizontal.append(Part.Face(wire)) else: self.horizontal.append(shape) for face in self.horizontal: # extrude all faces up to StartDepth and those are the removal shapes (strDep, finDep) = self.calculateStartFinalDepths( obj, face, stock) finalDepths.append(finDep) extent = FreeCAD.Vector(0, 0, strDep - finDep) self.removalshapes.append( (face.removeSplitter().extrude(extent), False, 'pathPocketShape', angle, axis, strDep, finDep)) PathLog.debug( "Extent depths are str: {}, and fin: {}".format( strDep, finDep)) # Efor face # Adjust obj.FinalDepth.Value as needed. if len(finalDepths) > 0: finalDep = min(finalDepths) if subCount == 1: obj.FinalDepth.Value = finalDep else: # process the job base object as a whole PathLog.debug(translate("Path", 'Processing model as a whole ...')) finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value self.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model ] # pylint: disable=attribute-defined-outside-init stockBB = self.stock.Shape.BoundBox self.removalshapes = [] # pylint: disable=attribute-defined-outside-init self.bodies = [] # pylint: disable=attribute-defined-outside-init for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude( FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append( (self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep)) for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes: # pylint: disable=unused-variable shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") self.removalshapes = [] self.horiz = [] vertical = [] for o in obj.Base: PathLog.debug("Base item: {}".format(o)) base = o[0] for sub in o[1]: if "Face" in sub: face = base.Shape.getElement(sub) if type(face.Surface) == Part.Plane and PathGeom.isVertical(face.Surface.Axis): # it's a flat horizontal face self.horiz.append(face) elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical(face.Surface.Axis): # vertical cylinder wall if any(e.isClosed() for e in face.Edges): # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) self.horiz.append(disk) else: # partial cylinder wall vertical.append(face) elif type(face.Surface) == Part.Plane and PathGeom.isHorizontal(face.Surface.Axis): vertical.append(face) else: PathLog.error(translate('PathPocket', "Pocket does not support shape %s.%s") % (base.Label, sub)) self.vertical = PathGeom.combineConnectedShapes(vertical) self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): PathLog.error(translate('PathPocket', 'Vertical faces do not form a loop - ignoring')) else: self.horiz.append(face) # move all horizontal faces to FinalDepth for f in self.horiz: f.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin)) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() shape.tessellate(0.1) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) self.horizontal.append(Part.Face(wire)) else: self.horizontal.append(shape) # extrude all faces up to StartDepth and those are the removal shapes extent = FreeCAD.Vector(0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) self.removalshapes = [(face.extrude(extent), False) for face in self.horizontal] else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] stockBB = self.stock.Shape.BoundBox self.removalshapes = [] self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False)) for (shape,hole) in self.removalshapes: shape.tessellate(0.1) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def analyzeVerticalFaces(self, obj, vertTuples): hT = [] # base = FreeCAD.ActiveDocument.getObject(self.modelName) # Separate elements, regroup by orientation (axis_angle combination) vTags = ['X34.2'] vGrps = [[(2.3, 3.4, 'X')]] for tup in vertTuples: (face, sub, angle, axis, tag, strDep, finDep, trans) = tup if tag in vTags: # Determine index of found string i = 0 for orn in vTags: if orn == tag: break i += 1 vGrps[i].append(tup) else: vTags.append(tag) # add orientation entry vGrps.append([tup]) # add orientation entry # Remove temp elements vTags.pop(0) vGrps.pop(0) # check all faces in each axis_angle group shpList = [] zmaxH = 0.0 for o in range(0, len(vTags)): shpList = [] zmaxH = vGrps[o][0].BoundBox.ZMax for (face, sub, angle, axis, tag, strDep, finDep, trans) in vGrps[o]: shpList.append(face) # Identify tallest face to use as zMax if face.BoundBox.ZMax > zmaxH: zmaxH = face.BoundBox.ZMax # check all faces and see if they are touching/overlapping and combine those into a compound # Original Code in For loop self.vertical = PathGeom.combineConnectedShapes(shpList) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.05) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring') ) else: strDep = zmaxH + self.leadIn # base.Shape.BoundBox.ZMax finDep = judgeFinalDepth(obj, face.BoundBox.ZMin) tup = face, sub, angle, axis, tag, strDep, finDep, trans hT.append(tup) # Eol return hT
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() PathLog.debug("----- areaOpShapes() in PathPocketShape.py") self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False baseSubsTuples = [] allTuples = [] subCount = 0 if obj.Base: PathLog.debug('Processing obj.Base') self.removalshapes = [] # pylint: disable=attribute-defined-outside-init if obj.EnableRotation == 'Off': stock = PathUtils.findParentJob(obj).Stock for (base, subList) in obj.Base: tup = (base, subList, 0.0, 'X', stock) baseSubsTuples.append(tup) else: PathLog.debug('... Rotation is active') # method call here for p in range(0, len(obj.Base)): (bst, at) = self.process_base_geometry_with_rotation(obj, p, subCount) allTuples.extend(at) baseSubsTuples.extend(bst) for o in baseSubsTuples: self.horiz = [] # pylint: disable=attribute-defined-outside-init self.vert = [] # pylint: disable=attribute-defined-outside-init subBase = o[0] subsList = o[1] angle = o[2] axis = o[3] # stock = o[4] for sub in subsList: if 'Face' in sub: if not self.clasifySub(subBase, sub): PathLog.error(translate('PathPocket', 'Pocket does not support shape %s.%s') % (subBase.Label, sub)) if obj.EnableRotation != 'Off': PathLog.warning(translate('PathPocket', 'Face might not be within rotation accessibility limits.')) # Determine final depth as highest value of bottom boundbox of vertical face, # in case of uneven faces on bottom if len(self.vert) > 0: vFinDep = self.vert[0].BoundBox.ZMin for vFace in self.vert: if vFace.BoundBox.ZMin > vFinDep: vFinDep = vFace.BoundBox.ZMin # Determine if vertical faces for a loop: Extract planar loop wire as new horizontal face. self.vertical = PathGeom.combineConnectedShapes(self.vert) # pylint: disable=attribute-defined-outside-init self.vWires = [TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical] # pylint: disable=attribute-defined-outside-init for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) # face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): msg = translate('PathPocket', 'Vertical faces do not form a loop - ignoring') PathLog.error(msg) else: face.translate(FreeCAD.Vector(0, 0, vFinDep - face.BoundBox.ZMin)) self.horiz.append(face) # add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in self.getExtensions(obj): wire = ext.getWire() if wire: face = Part.Face(wire) self.horiz.append(face) self.exts.append(face) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = [] # pylint: disable=attribute-defined-outside-init for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() # shape.tessellate(0.1) shpZMin = shape.BoundBox.ZMin PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(shape.BoundBox.ZMin)) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wFace = Part.Face(wire) if wFace.BoundBox.ZMin != shpZMin: wFace.translate(FreeCAD.Vector(0, 0, shpZMin - wFace.BoundBox.ZMin)) self.horizontal.append(wFace) PathLog.debug('PathGeom.combineConnectedShapes shape.BoundBox.ZMin: {}'.format(wFace.BoundBox.ZMin)) else: self.horizontal.append(shape) # move all horizontal faces to FinalDepth # extrude all faces up to StartDepth and those are the removal shapes start_dep = obj.StartDepth.Value clrnc = 0.5 # self._addDebugObject('subBase', subBase.Shape) for face in self.horizontal: isFaceUp = True invZ = 0.0 useAngle = angle faceZMin = face.BoundBox.ZMin adj_final_dep = obj.FinalDepth.Value trans = obj.FinalDepth.Value - face.BoundBox.ZMin PathLog.debug('face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin)) if obj.EnableRotation != 'Off': PathLog.debug('... running isFaceUp()') isFaceUp = self.isFaceUp(subBase, face) # Determine if face is really oriented toward Z+ (rotational purposes) # ignore for cylindrical faces if not isFaceUp: PathLog.debug('... NOT isFaceUp') useAngle += 180.0 invZ = (-2 * face.BoundBox.ZMin) face.translate(FreeCAD.Vector(0.0, 0.0, invZ)) faceZMin = face.BoundBox.ZMin # reset faceZMin PathLog.debug('... face.BoundBox.ZMin: {}'.format(face.BoundBox.ZMin)) else: PathLog.debug('... isFaceUp') if useAngle > 180.0: useAngle -= 360.0 # Apply LimitDepthToFace property for rotational operations if obj.LimitDepthToFace: if obj.FinalDepth.Value < face.BoundBox.ZMin: PathLog.debug('obj.FinalDepth.Value < face.BoundBox.ZMin') # Raise FinalDepth to face depth adj_final_dep = faceZMin # face.BoundBox.ZMin # faceZMin # Ensure StartDepth is above FinalDepth if start_dep <= adj_final_dep: start_dep = adj_final_dep + 1.0 msg = translate('PathPocketShape', 'Start Depth is lower than face depth. Setting to:') PathLog.warning(msg + ' {} mm.'.format(start_dep)) PathLog.debug('LimitDepthToFace adj_final_dep: {}'.format(adj_final_dep)) # Eif face.translate(FreeCAD.Vector(0.0, 0.0, adj_final_dep - faceZMin - clrnc)) zExtVal = start_dep - adj_final_dep + (2 * clrnc) extShp = face.removeSplitter().extrude(FreeCAD.Vector(0, 0, zExtVal)) self.removalshapes.append((extShp, False, 'pathPocketShape', useAngle, axis, start_dep, adj_final_dep)) PathLog.debug("Extent values are strDep: {}, finDep: {}, extrd: {}".format(start_dep, adj_final_dep, zExtVal)) # Efor face # Efor else: # process the job base object as a whole PathLog.debug(translate("Path", 'Processing model as a whole ...')) finDep = obj.FinalDepth.Value strDep = obj.StartDepth.Value self.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] # pylint: disable=attribute-defined-outside-init stockBB = self.stock.Shape.BoundBox self.removalshapes = [] # pylint: disable=attribute-defined-outside-init self.bodies = [] # pylint: disable=attribute-defined-outside-init for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False, 'pathPocketShape', 0.0, 'X', strDep, finDep)) for (shape, hole, sub, angle, axis, strDep, finDep) in self.removalshapes: # pylint: disable=unused-variable shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes